- Add comprehensive chat system with modern UI design - Implement admin-based password reset system - Fix template syntax errors and 500 server errors - Add chat routes, API endpoints, and database models - Enhance user interface with Tailwind CSS card-based design - Implement community guidelines and quick action features - Add responsive design for mobile and desktop compatibility - Create support chat functionality with admin integration - Fix JavaScript inheritance in base template - Add database migration for chat system tables Features: ✅ Modern chat interface with room management ✅ Admin-based password reset workflow ✅ Real-time chat with mobile app support ✅ Professional UI with gradient cards and hover effects ✅ Community guidelines and safety features ✅ Responsive design for all devices ✅ Error-free template rendering
238 lines
9.6 KiB
Python
238 lines
9.6 KiB
Python
from flask import Blueprint, render_template, request, redirect, url_for, flash
|
|
from flask_login import login_user, logout_user, login_required, current_user
|
|
from werkzeug.security import check_password_hash
|
|
from app.models import User, db
|
|
from app.routes.reset_password import RequestResetForm, ResetPasswordForm
|
|
from flask_mail import Message
|
|
from app.routes.mail import mail
|
|
from app.utils.token import generate_reset_token, verify_reset_token
|
|
from datetime import datetime
|
|
import re
|
|
from flask_wtf import FlaskForm
|
|
from wtforms import StringField, PasswordField, BooleanField, SubmitField
|
|
from wtforms.validators import DataRequired, Email, EqualTo, Length
|
|
|
|
auth = Blueprint('auth', __name__)
|
|
|
|
class LoginForm(FlaskForm):
|
|
email = StringField('Email', validators=[DataRequired(), Email()])
|
|
password = PasswordField('Password', validators=[DataRequired()])
|
|
remember_me = BooleanField('Remember Me')
|
|
submit = SubmitField('Sign In')
|
|
|
|
class RegisterForm(FlaskForm):
|
|
nickname = StringField('Nickname', validators=[DataRequired(), Length(min=3, max=32)])
|
|
email = StringField('Email', validators=[DataRequired(), Email()])
|
|
password = PasswordField('Password', validators=[DataRequired(), Length(min=8)])
|
|
password2 = PasswordField('Confirm Password', validators=[DataRequired(), EqualTo('password')])
|
|
submit = SubmitField('Register')
|
|
|
|
class ForgotPasswordForm(FlaskForm):
|
|
email = StringField('Email', validators=[DataRequired(), Email()])
|
|
submit = SubmitField('Request Password Reset')
|
|
|
|
@auth.route('/login', methods=['GET', 'POST'])
|
|
def login():
|
|
"""User login page"""
|
|
if current_user.is_authenticated:
|
|
return redirect(url_for('main.index'))
|
|
|
|
form = LoginForm()
|
|
if form.validate_on_submit():
|
|
user = User.query.filter_by(email=form.email.data).first()
|
|
|
|
if user and user.check_password(form.password.data):
|
|
login_user(user, remember=form.remember_me.data)
|
|
next_page = request.args.get('next')
|
|
if not next_page or not next_page.startswith('/'):
|
|
next_page = url_for('community.index')
|
|
flash(f'Welcome back, {user.nickname}!', 'success')
|
|
return redirect(next_page)
|
|
else:
|
|
flash('Invalid email or password.', 'error')
|
|
|
|
return render_template('auth/login.html', form=form)
|
|
|
|
@auth.route('/register', methods=['GET', 'POST'])
|
|
def register():
|
|
"""User registration page"""
|
|
if current_user.is_authenticated:
|
|
return redirect(url_for('main.index'))
|
|
|
|
form = RegisterForm()
|
|
if form.validate_on_submit():
|
|
# Check if user already exists
|
|
if User.query.filter_by(email=form.email.data).first():
|
|
flash('Email address already registered.', 'error')
|
|
return render_template('auth/register.html', form=form)
|
|
|
|
if User.query.filter_by(nickname=form.nickname.data).first():
|
|
flash('Nickname already taken.', 'error')
|
|
return render_template('auth/register.html', form=form)
|
|
|
|
# Validate password strength
|
|
if not is_valid_password(form.password.data):
|
|
flash('Password must be at least 8 characters long and contain at least one letter and one number.', 'error')
|
|
return render_template('auth/register.html', form=form)
|
|
|
|
# Create new user
|
|
user = User(
|
|
nickname=form.nickname.data,
|
|
email=form.email.data
|
|
)
|
|
user.set_password(form.password.data)
|
|
|
|
try:
|
|
db.session.add(user)
|
|
db.session.commit()
|
|
login_user(user)
|
|
flash('Registration successful! Welcome to the community!', 'success')
|
|
return redirect(url_for('community.index'))
|
|
except Exception as e:
|
|
db.session.rollback()
|
|
flash('An error occurred during registration. Please try again.', 'error')
|
|
|
|
return render_template('auth/register.html', form=form)
|
|
|
|
@auth.route('/logout')
|
|
@login_required
|
|
def logout():
|
|
"""User logout"""
|
|
logout_user()
|
|
flash('You have been logged out.', 'info')
|
|
return redirect(url_for('main.index'))
|
|
|
|
@auth.route('/forgot-password', methods=['GET', 'POST'])
|
|
def forgot_password():
|
|
"""Forgot password page - sends message to admin instead of email"""
|
|
if current_user.is_authenticated:
|
|
return redirect(url_for('main.index'))
|
|
form = RequestResetForm()
|
|
if form.validate_on_submit():
|
|
user = User.query.filter_by(email=form.email.data).first()
|
|
|
|
# Create password reset user if it doesn't exist
|
|
reset_user = User.query.filter_by(email='reset_password@motoadventure.local').first()
|
|
if not reset_user:
|
|
reset_user = User(
|
|
nickname='PasswordReset',
|
|
email='reset_password@motoadventure.local',
|
|
is_active=False # This is a system user
|
|
)
|
|
reset_user.set_password('temp_password') # Won't be used
|
|
db.session.add(reset_user)
|
|
db.session.commit()
|
|
|
|
# Find admin support room
|
|
from app.models import ChatRoom, ChatMessage
|
|
admin_room = ChatRoom.query.filter_by(room_type='support').first()
|
|
if not admin_room:
|
|
# Create admin support room if it doesn't exist
|
|
system_user = User.query.filter_by(email='system@motoadventure.local').first()
|
|
admin_room = ChatRoom(
|
|
name='Technical Support',
|
|
description='Administrative support and password resets',
|
|
room_type='support',
|
|
is_private=False,
|
|
is_active=True,
|
|
created_by_id=system_user.id if system_user else 1
|
|
)
|
|
db.session.add(admin_room)
|
|
db.session.commit()
|
|
|
|
# Create the password reset message
|
|
if user:
|
|
message_content = f"A user with email '{user.email}' (nickname: {user.nickname}) needs their password to be changed. Please assist with password reset."
|
|
else:
|
|
message_content = f"Someone with email '{form.email.data}' requested a password reset, but no account exists with this email. Please check if this user needs assistance creating an account."
|
|
|
|
reset_message = ChatMessage(
|
|
content=message_content,
|
|
room_id=admin_room.id,
|
|
sender_id=reset_user.id,
|
|
is_system_message=True
|
|
)
|
|
db.session.add(reset_message)
|
|
|
|
# Update room activity
|
|
admin_room.last_activity = datetime.utcnow()
|
|
db.session.commit()
|
|
|
|
flash('Your password reset request has been sent to administrators. They will contact you soon to help reset your password.', 'info')
|
|
return redirect(url_for('auth.login'))
|
|
return render_template('auth/forgot_password.html', form=form)
|
|
# Password reset route
|
|
@auth.route('/reset-password/<token>', methods=['GET', 'POST'])
|
|
def reset_password(token):
|
|
if current_user.is_authenticated:
|
|
return redirect(url_for('main.index'))
|
|
email = verify_reset_token(token)
|
|
if not email:
|
|
flash('Invalid or expired reset link.', 'danger')
|
|
return redirect(url_for('auth.forgot_password'))
|
|
user = User.query.filter_by(email=email).first()
|
|
if not user:
|
|
flash('Invalid or expired reset link.', 'danger')
|
|
return redirect(url_for('auth.forgot_password'))
|
|
form = ResetPasswordForm()
|
|
if form.validate_on_submit():
|
|
user.set_password(form.password.data)
|
|
db.session.commit()
|
|
flash('Your password has been reset. You can now log in.', 'success')
|
|
return redirect(url_for('auth.login'))
|
|
return render_template('auth/reset_password.html', form=form)
|
|
|
|
@auth.route('/change-password', methods=['POST'])
|
|
@login_required
|
|
def change_password():
|
|
"""Change user password"""
|
|
current_password = request.form.get('current_password')
|
|
new_password = request.form.get('new_password')
|
|
confirm_password = request.form.get('confirm_password')
|
|
|
|
# Validate inputs
|
|
if not all([current_password, new_password, confirm_password]):
|
|
flash('All password fields are required.', 'error')
|
|
return redirect(url_for('community.profile'))
|
|
|
|
# Check current password
|
|
if not current_user.check_password(current_password):
|
|
flash('Current password is incorrect.', 'error')
|
|
return redirect(url_for('community.profile'))
|
|
|
|
# Validate new password
|
|
if len(new_password) < 6:
|
|
flash('New password must be at least 6 characters long.', 'error')
|
|
return redirect(url_for('community.profile'))
|
|
|
|
# Check password confirmation
|
|
if new_password != confirm_password:
|
|
flash('New password and confirmation do not match.', 'error')
|
|
return redirect(url_for('community.profile'))
|
|
|
|
# Check if new password is different from current
|
|
if current_user.check_password(new_password):
|
|
flash('New password must be different from your current password.', 'error')
|
|
return redirect(url_for('community.profile'))
|
|
|
|
try:
|
|
# Update password
|
|
current_user.set_password(new_password)
|
|
db.session.commit()
|
|
flash('Password updated successfully!', 'success')
|
|
except Exception as e:
|
|
db.session.rollback()
|
|
flash('An error occurred while updating your password. Please try again.', 'error')
|
|
|
|
return redirect(url_for('community.profile'))
|
|
|
|
def is_valid_password(password):
|
|
"""Validate password strength"""
|
|
if len(password) < 8:
|
|
return False
|
|
if not re.search(r'[A-Za-z]', password):
|
|
return False
|
|
if not re.search(r'\d', password):
|
|
return False
|
|
return True
|