""" Chat API routes for mobile app compatibility Provides RESTful endpoints for chat functionality """ from flask import Blueprint, request, jsonify, current_app from flask_login import login_required, current_user from sqlalchemy import desc, and_, or_ from datetime import datetime, timedelta import re from app.extensions import db from app.models import ChatRoom, ChatMessage, ChatParticipant, Post, User # Create blueprint chat_api = Blueprint('chat_api', __name__) # Chat rules and guidelines CHAT_RULES = [ "Be respectful and courteous to all community members", "No offensive language, harassment, or personal attacks", "Stay on topic - use post-specific chats for discussions about routes", "No spam or promotional content without permission", "Share useful tips and experiences about motorcycle adventures", "Help newcomers and answer questions when you can", "Report inappropriate behavior to administrators", "Keep conversations constructive and helpful" ] # Profanity filter (basic implementation) BLOCKED_WORDS = [ 'spam', 'scam', 'fake', 'stupid', 'idiot', 'hate' # Add more words as needed ] def contains_blocked_content(text): """Check if text contains blocked words""" text_lower = text.lower() return any(word in text_lower for word in BLOCKED_WORDS) @chat_api.route('/rules', methods=['GET']) def get_chat_rules(): """Get chat rules and guidelines""" return jsonify({ 'success': True, 'rules': CHAT_RULES }) @chat_api.route('/rooms', methods=['GET']) @login_required def get_chat_rooms(): """Get list of available chat rooms for the user""" page = request.args.get('page', 1, type=int) per_page = request.args.get('per_page', 20, type=int) room_type = request.args.get('type', None) # Base query - only active rooms the user can access query = ChatRoom.query.filter(ChatRoom.is_active == True) # Filter by type if specified if room_type: query = query.filter(ChatRoom.room_type == room_type) # Order by last activity query = query.order_by(desc(ChatRoom.last_activity)) # Paginate rooms = query.paginate( page=page, per_page=per_page, error_out=False ) return jsonify({ 'success': True, 'rooms': [room.to_dict() for room in rooms.items], 'pagination': { 'page': page, 'pages': rooms.pages, 'per_page': per_page, 'total': rooms.total, 'has_next': rooms.has_next, 'has_prev': rooms.has_prev } }) @chat_api.route('/rooms', methods=['POST']) @login_required def create_chat_room(): """Create a new chat room""" data = request.get_json() if not data or not data.get('name'): return jsonify({ 'success': False, 'error': 'Room name is required' }), 400 # Validate input name = data.get('name', '').strip() description = data.get('description', '').strip() room_type = data.get('room_type', 'general') related_post_id = data.get('related_post_id') if len(name) < 3 or len(name) > 100: return jsonify({ 'success': False, 'error': 'Room name must be between 3 and 100 characters' }), 400 # Check if room already exists existing_room = ChatRoom.query.filter_by(name=name).first() if existing_room: return jsonify({ 'success': False, 'error': 'A room with this name already exists' }), 400 # Validate related post if specified if related_post_id: post = Post.query.get(related_post_id) if not post: return jsonify({ 'success': False, 'error': 'Related post not found' }), 404 try: # Create room room = ChatRoom( name=name, description=description, room_type=room_type, created_by_id=current_user.id, related_post_id=related_post_id ) db.session.add(room) db.session.flush() # Get the room ID # Add creator as participant with admin role participant = ChatParticipant( room_id=room.id, user_id=current_user.id, role='admin' ) db.session.add(participant) # Add system message system_message = ChatMessage( room_id=room.id, user_id=current_user.id, content=f"Welcome to {name}! This chat room was created for motorcycle adventure discussions.", message_type='system' ) db.session.add(system_message) db.session.commit() return jsonify({ 'success': True, 'room': room.to_dict() }), 201 except Exception as e: db.session.rollback() current_app.logger.error(f"Error creating chat room: {str(e)}") return jsonify({ 'success': False, 'error': 'Failed to create chat room' }), 500 @chat_api.route('/rooms/', methods=['GET']) @login_required def get_chat_room(room_id): """Get chat room details""" room = ChatRoom.query.get_or_404(room_id) # Check if user has access if room.is_private: participant = ChatParticipant.query.filter_by( room_id=room_id, user_id=current_user.id ).first() if not participant: return jsonify({ 'success': False, 'error': 'Access denied' }), 403 return jsonify({ 'success': True, 'room': room.to_dict() }) @chat_api.route('/rooms//join', methods=['POST']) @login_required def join_chat_room(room_id): """Join a chat room""" room = ChatRoom.query.get_or_404(room_id) # Check if already a participant existing_participant = ChatParticipant.query.filter_by( room_id=room_id, user_id=current_user.id ).first() if existing_participant: return jsonify({ 'success': True, 'message': 'Already a member of this room' }) try: # Add user as participant participant = ChatParticipant( room_id=room_id, user_id=current_user.id, role='member' ) db.session.add(participant) # Add system message system_message = ChatMessage( room_id=room_id, user_id=current_user.id, content=f"{current_user.nickname} joined the chat", message_type='system' ) db.session.add(system_message) # Update room activity room.last_activity = datetime.utcnow() db.session.commit() return jsonify({ 'success': True, 'message': 'Successfully joined the room' }) except Exception as e: db.session.rollback() current_app.logger.error(f"Error joining chat room: {str(e)}") return jsonify({ 'success': False, 'error': 'Failed to join room' }), 500 @chat_api.route('/rooms//messages', methods=['GET']) @login_required def get_chat_messages(room_id): """Get messages from a chat room""" room = ChatRoom.query.get_or_404(room_id) # Check access if room.is_private: participant = ChatParticipant.query.filter_by( room_id=room_id, user_id=current_user.id ).first() if not participant: return jsonify({ 'success': False, 'error': 'Access denied' }), 403 page = request.args.get('page', 1, type=int) per_page = request.args.get('per_page', 50, type=int) # Get messages (newest first for mobile scrolling) messages = ChatMessage.query.filter_by( room_id=room_id, is_deleted=False ).order_by(desc(ChatMessage.created_at)).paginate( page=page, per_page=per_page, error_out=False ) # Update user's last seen participant = ChatParticipant.query.filter_by( room_id=room_id, user_id=current_user.id ).first() if participant: participant.last_seen = datetime.utcnow() db.session.commit() return jsonify({ 'success': True, 'messages': [msg.to_dict() for msg in reversed(messages.items)], 'pagination': { 'page': page, 'pages': messages.pages, 'per_page': per_page, 'total': messages.total, 'has_next': messages.has_next, 'has_prev': messages.has_prev } }) @chat_api.route('/rooms//messages', methods=['POST']) @login_required def send_message(room_id): """Send a message to a chat room""" room = ChatRoom.query.get_or_404(room_id) data = request.get_json() if not data or not data.get('content'): return jsonify({ 'success': False, 'error': 'Message content is required' }), 400 content = data.get('content', '').strip() message_type = data.get('message_type', 'text') reply_to_id = data.get('reply_to_id') # Validate content if len(content) < 1 or len(content) > 2000: return jsonify({ 'success': False, 'error': 'Message must be between 1 and 2000 characters' }), 400 # Check for blocked content if contains_blocked_content(content): return jsonify({ 'success': False, 'error': 'Message contains inappropriate content' }), 400 # Check if user is participant participant = ChatParticipant.query.filter_by( room_id=room_id, user_id=current_user.id ).first() if not participant: return jsonify({ 'success': False, 'error': 'You must join the room first' }), 403 if participant.is_muted: return jsonify({ 'success': False, 'error': 'You are muted in this room' }), 403 try: # Create message message = ChatMessage( room_id=room_id, user_id=current_user.id, content=content, message_type=message_type, reply_to_id=reply_to_id ) db.session.add(message) # Update room activity room.last_activity = datetime.utcnow() # Update participant last seen participant.last_seen = datetime.utcnow() db.session.commit() return jsonify({ 'success': True, 'message': message.to_dict() }), 201 except Exception as e: db.session.rollback() current_app.logger.error(f"Error sending message: {str(e)}") return jsonify({ 'success': False, 'error': 'Failed to send message' }), 500 @chat_api.route('/rooms//participants', methods=['GET']) @login_required def get_room_participants(room_id): """Get participants of a chat room""" room = ChatRoom.query.get_or_404(room_id) participants = ChatParticipant.query.filter_by(room_id=room_id).all() return jsonify({ 'success': True, 'participants': [p.to_dict() for p in participants] }) @chat_api.route('/admin-support', methods=['POST']) @login_required def create_admin_support_chat(): """Create a chat room for admin support (e.g., password reset)""" data = request.get_json() reason = data.get('reason', 'general_support') description = data.get('description', '') # Check if user already has an active admin support chat existing_room = ChatRoom.query.filter( and_( ChatRoom.room_type == 'admin_support', ChatRoom.created_by_id == current_user.id, ChatRoom.is_active == True ) ).first() if existing_room: return jsonify({ 'success': True, 'room': existing_room.to_dict(), 'message': 'Using existing support chat' }) try: # Create admin support room room_name = f"Support - {current_user.nickname} - {reason}" room = ChatRoom( name=room_name, description=f"Admin support chat for {current_user.nickname}. Reason: {reason}", room_type='admin_support', is_private=True, created_by_id=current_user.id ) db.session.add(room) db.session.flush() # Add user as participant participant = ChatParticipant( room_id=room.id, user_id=current_user.id, role='member' ) db.session.add(participant) # Add all admins as participants admins = User.query.filter_by(is_admin=True).all() for admin in admins: if admin.id != current_user.id: admin_participant = ChatParticipant( room_id=room.id, user_id=admin.id, role='admin' ) db.session.add(admin_participant) # Add initial message initial_message = ChatMessage( room_id=room.id, user_id=current_user.id, content=f"Hello! I need help with: {reason}. {description}", message_type='text' ) db.session.add(initial_message) db.session.commit() return jsonify({ 'success': True, 'room': room.to_dict() }), 201 except Exception as e: db.session.rollback() current_app.logger.error(f"Error creating admin support chat: {str(e)}") return jsonify({ 'success': False, 'error': 'Failed to create support chat' }), 500 @chat_api.route('/post//discussion', methods=['POST']) @login_required def create_post_discussion(post_id): """Create or get discussion chat for a specific post""" post = Post.query.get_or_404(post_id) # Check if discussion already exists existing_room = ChatRoom.query.filter( and_( ChatRoom.room_type == 'post_discussion', ChatRoom.related_post_id == post_id, ChatRoom.is_active == True ) ).first() if existing_room: # Join the existing room if not already a participant participant = ChatParticipant.query.filter_by( room_id=existing_room.id, user_id=current_user.id ).first() if not participant: participant = ChatParticipant( room_id=existing_room.id, user_id=current_user.id, role='member' ) db.session.add(participant) db.session.commit() return jsonify({ 'success': True, 'room': existing_room.to_dict(), 'message': 'Joined existing discussion' }) try: # Create new discussion room room_name = f"Discussion: {post.title}" room = ChatRoom( name=room_name, description=f"Discussion about the post: {post.title}", room_type='post_discussion', created_by_id=current_user.id, related_post_id=post_id ) db.session.add(room) db.session.flush() # Add creator as participant participant = ChatParticipant( room_id=room.id, user_id=current_user.id, role='moderator' ) db.session.add(participant) # Add post author as participant if different if post.author_id != current_user.id: author_participant = ChatParticipant( room_id=room.id, user_id=post.author_id, role='moderator' ) db.session.add(author_participant) # Add initial message initial_message = ChatMessage( room_id=room.id, user_id=current_user.id, content=f"Started discussion about: {post.title}", message_type='system' ) db.session.add(initial_message) db.session.commit() return jsonify({ 'success': True, 'room': room.to_dict() }), 201 except Exception as e: db.session.rollback() current_app.logger.error(f"Error creating post discussion: {str(e)}") return jsonify({ 'success': False, 'error': 'Failed to create discussion' }), 500