Features Added: 🔥 Modern Chat System: - Real-time messaging with modern Tailwind CSS design - Post-linked discussions for adventure sharing - Chat categories (general, technical-support, adventure-planning) - Mobile-responsive interface with gradient backgrounds - JavaScript polling for live message updates 🎯 Comprehensive Admin Panel: - Chat room management with merge capabilities - Password reset system with email templates - User management with admin controls - Chat statistics and analytics dashboard - Room binding to posts and categorization �� Mobile API Integration: - RESTful API endpoints at /api/v1/chat - Session-based authentication for mobile apps - Comprehensive endpoints for rooms, messages, users - Mobile app compatibility (React Native, Flutter) 🛠️ Technical Improvements: - Enhanced database models with ChatRoom categories - Password reset token system with email verification - Template synchronization fixes for Docker deployment - Migration scripts for database schema updates - Improved error handling and validation 🎨 UI/UX Enhancements: - Modern card-based layouts matching app design - Consistent styling across chat and admin interfaces - Mobile-optimized touch interactions - Professional gradient designs and glass morphism effects 📚 Documentation: - Updated README with comprehensive API documentation - Added deployment instructions for Docker (port 8100) - Configuration guide for production environments - Mobile integration examples and endpoints This update transforms the platform into a comprehensive motorcycle adventure community with modern chat capabilities and professional admin management tools.
277 lines
9.2 KiB
Python
277 lines
9.2 KiB
Python
"""
|
|
Chat web interface routes
|
|
Provides HTML templates and endpoints for web-based chat
|
|
"""
|
|
from flask import Blueprint, render_template, request, redirect, url_for, flash, jsonify
|
|
from flask_login import login_required, current_user
|
|
from sqlalchemy import desc, and_
|
|
from datetime import datetime, timedelta
|
|
|
|
from app.extensions import db
|
|
from app.models import ChatRoom, ChatMessage, ChatParticipant, Post
|
|
|
|
# Create blueprint
|
|
chat = Blueprint('chat', __name__)
|
|
|
|
@chat.route('/')
|
|
@login_required
|
|
def index():
|
|
"""Chat main page with room list and rules"""
|
|
# Get user's recent chat rooms
|
|
user_rooms = db.session.query(ChatRoom).join(ChatParticipant).filter(
|
|
and_(
|
|
ChatParticipant.user_id == current_user.id,
|
|
ChatRoom.is_active == True
|
|
)
|
|
).order_by(desc(ChatRoom.last_activity)).limit(10).all()
|
|
|
|
# Get public rooms that are active
|
|
public_rooms = ChatRoom.query.filter(
|
|
ChatRoom.is_active == True,
|
|
ChatRoom.is_private == False
|
|
).order_by(desc(ChatRoom.last_activity)).limit(10).all()
|
|
|
|
return render_template('chat/index.html',
|
|
user_rooms=user_rooms,
|
|
public_rooms=public_rooms)
|
|
|
|
@chat.route('/room/<int:room_id>')
|
|
@login_required
|
|
def room(room_id):
|
|
"""Chat room interface"""
|
|
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:
|
|
flash('You do not have access to this chat room.', 'error')
|
|
return redirect(url_for('chat.index'))
|
|
|
|
# Get or create participant record
|
|
participant = ChatParticipant.query.filter_by(
|
|
room_id=room_id,
|
|
user_id=current_user.id
|
|
).first()
|
|
|
|
if not participant and not room.is_private:
|
|
# Auto-join public rooms
|
|
participant = ChatParticipant(
|
|
room_id=room_id,
|
|
user_id=current_user.id,
|
|
role='member'
|
|
)
|
|
db.session.add(participant)
|
|
db.session.commit()
|
|
|
|
# Get recent messages
|
|
messages = ChatMessage.query.filter_by(
|
|
room_id=room_id,
|
|
is_deleted=False
|
|
).order_by(ChatMessage.created_at).limit(50).all()
|
|
|
|
# Get participants
|
|
participants = ChatParticipant.query.filter_by(room_id=room_id).all()
|
|
|
|
return render_template('chat/room.html',
|
|
room=room,
|
|
messages=messages,
|
|
participants=participants,
|
|
current_participant=participant)
|
|
|
|
@chat.route('/create')
|
|
@login_required
|
|
def create_room_form():
|
|
"""Show create room form"""
|
|
# Get available posts for post discussions
|
|
recent_posts = Post.query.filter_by(published=True).order_by(
|
|
desc(Post.created_at)
|
|
).limit(20).all()
|
|
|
|
# Check if a specific post was requested
|
|
pre_selected_post = request.args.get('post_id')
|
|
if pre_selected_post:
|
|
try:
|
|
pre_selected_post = int(pre_selected_post)
|
|
except ValueError:
|
|
pre_selected_post = None
|
|
|
|
return render_template('chat/create_room.html', posts=recent_posts, pre_selected_post=pre_selected_post)
|
|
|
|
|
|
@chat.route('/create', methods=['POST'])
|
|
@login_required
|
|
def create_room():
|
|
"""Create a new chat room"""
|
|
room_name = request.form.get('room_name')
|
|
description = request.form.get('description', '')
|
|
room_type = request.form.get('room_type', 'general')
|
|
is_private = bool(request.form.get('is_private'))
|
|
related_post_id = request.form.get('related_post_id')
|
|
|
|
if not room_name:
|
|
flash('Room name is required.', 'error')
|
|
return redirect(url_for('chat.create_room_form'))
|
|
|
|
# Convert to integer if post ID is provided
|
|
if related_post_id:
|
|
try:
|
|
related_post_id = int(related_post_id)
|
|
# Verify the post exists
|
|
related_post = Post.query.get(related_post_id)
|
|
if not related_post:
|
|
flash('Selected post does not exist.', 'error')
|
|
return redirect(url_for('chat.create_room_form'))
|
|
# If post is selected, set room type to post_discussion
|
|
room_type = 'post_discussion'
|
|
except ValueError:
|
|
related_post_id = None
|
|
else:
|
|
related_post_id = None
|
|
# If no post selected, ensure it's general discussion
|
|
if room_type == 'post_discussion':
|
|
room_type = 'general'
|
|
|
|
# Check if room name already exists
|
|
existing_room = ChatRoom.query.filter_by(name=room_name).first()
|
|
if existing_room:
|
|
flash('A room with that name already exists.', 'error')
|
|
return redirect(url_for('chat.create_room_form'))
|
|
|
|
try:
|
|
# Create the room
|
|
room = ChatRoom(
|
|
name=room_name,
|
|
description=description,
|
|
room_type=room_type,
|
|
is_private=is_private,
|
|
is_active=True,
|
|
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',
|
|
joined_at=datetime.utcnow()
|
|
)
|
|
db.session.add(participant)
|
|
|
|
# Add welcome message
|
|
if related_post_id:
|
|
welcome_content = f"Welcome to the discussion for '{related_post.title}'! This room was created by {current_user.nickname} to discuss this post."
|
|
else:
|
|
welcome_content = f"Welcome to {room_name}! This room was created by {current_user.nickname}."
|
|
|
|
welcome_message = ChatMessage(
|
|
content=welcome_content,
|
|
room_id=room.id,
|
|
sender_id=current_user.id,
|
|
is_system_message=True
|
|
)
|
|
db.session.add(welcome_message)
|
|
|
|
# Update room activity
|
|
room.last_activity = datetime.utcnow()
|
|
room.message_count = 1
|
|
|
|
db.session.commit()
|
|
|
|
if related_post_id:
|
|
flash(f'Chat room "{room_name}" created successfully and linked to the post!', 'success')
|
|
else:
|
|
flash(f'Chat room "{room_name}" created successfully!', 'success')
|
|
return redirect(url_for('chat.room', room_id=room.id))
|
|
|
|
except Exception as e:
|
|
db.session.rollback()
|
|
flash(f'Error creating room: {str(e)}', 'error')
|
|
return redirect(url_for('chat.create_room_form'))
|
|
|
|
@chat.route('/support')
|
|
@login_required
|
|
def support():
|
|
"""Admin support page"""
|
|
# Get user's recent support tickets (rooms they created for support)
|
|
recent_tickets = ChatRoom.query.filter(
|
|
ChatRoom.room_type == 'admin_support',
|
|
ChatRoom.created_by_id == current_user.id
|
|
).order_by(desc(ChatRoom.created_at)).limit(5).all()
|
|
|
|
return render_template('chat/support.html',
|
|
recent_tickets=recent_tickets)
|
|
|
|
@chat.route('/embed/<int:post_id>')
|
|
@login_required
|
|
def embed_post_chat(post_id):
|
|
"""Embedded chat widget for post pages"""
|
|
post = Post.query.get_or_404(post_id)
|
|
|
|
# Find existing discussion room
|
|
discussion_room = ChatRoom.query.filter(
|
|
and_(
|
|
ChatRoom.room_type == 'post_discussion',
|
|
ChatRoom.related_post_id == post_id,
|
|
ChatRoom.is_active == True
|
|
)
|
|
).first()
|
|
|
|
return render_template('chat/embed.html',
|
|
post=post,
|
|
discussion_room=discussion_room)
|
|
|
|
|
|
@chat.route('/post-discussions')
|
|
@login_required
|
|
def post_discussions():
|
|
"""View all chat rooms related to posts"""
|
|
page = request.args.get('page', 1, type=int)
|
|
|
|
# Get all rooms that are linked to posts
|
|
post_rooms = ChatRoom.query.filter(
|
|
ChatRoom.related_post_id.isnot(None),
|
|
ChatRoom.is_active == True
|
|
).join(Post).order_by(ChatRoom.last_activity.desc()).paginate(
|
|
page=page, per_page=20, error_out=False
|
|
)
|
|
|
|
# Get statistics
|
|
total_post_discussions = ChatRoom.query.filter(
|
|
ChatRoom.related_post_id.isnot(None),
|
|
ChatRoom.is_active == True
|
|
).count()
|
|
|
|
active_discussions = ChatRoom.query.filter(
|
|
ChatRoom.related_post_id.isnot(None),
|
|
ChatRoom.is_active == True,
|
|
ChatRoom.last_activity >= datetime.utcnow() - timedelta(days=7)
|
|
).count()
|
|
|
|
return render_template('chat/post_discussions.html',
|
|
rooms=post_rooms,
|
|
total_discussions=total_post_discussions,
|
|
active_discussions=active_discussions)
|
|
|
|
|
|
@chat.route('/post/<int:post_id>/discussions')
|
|
@login_required
|
|
def post_specific_discussions(post_id):
|
|
"""View all chat rooms for a specific post"""
|
|
post = Post.query.get_or_404(post_id)
|
|
|
|
# Get all rooms for this specific post
|
|
rooms = ChatRoom.query.filter(
|
|
ChatRoom.related_post_id == post_id,
|
|
ChatRoom.is_active == True
|
|
).order_by(ChatRoom.last_activity.desc()).all()
|
|
|
|
return render_template('chat/post_specific_discussions.html',
|
|
post=post, rooms=rooms)
|