from datetime import datetime import flask_login from flask_socketio import join_room from flask import ( Blueprint, current_app, # Used for logging flash, make_response, redirect, render_template, request, url_for, ) from . import socketio from .auth import user_loader from .models import Password, db from .utils import ( create_token, get_active_banner, get_all_courses, get_token, get_unlocked_decks, retrieve_token, update_token, get_course, token_required, set_cookie, ) main = Blueprint("main", __name__) @socketio.on("connect") def on_connect(): if flask_login.current_user.is_authenticated: join_room("instructor") current_app.logger.info( f"Instructor connected to socket: {flask_login.current_user.id}" ) @main.route("/") @token_required def index(token): current_app.logger.debug( f"User {token.name} is viewing index with {len(token.decks)} decks unlocked." ) return render_template( "index.html", active_banner=get_active_banner(), decks=get_unlocked_decks(token), user_name=token.name, active_course_name=token.active_course.name, courses=get_all_courses(), ) @main.route("/set_token", methods=["POST", "GET"]) def set_token(): """ Handles. GET: Renders the registration form. POST: Processes the form submission. """ if request.method == "POST": # Get form data username = request.form.get("username") # Simple validation if not username: flash("Username is required.", "error") return render_template("set_token.html") if user_loader(username): current_app.logger.info( f"Admin username '{username}' entered in set_token. Redirecting to admin login." ) return redirect(url_for("admin.admin_login")) token = retrieve_token(username) if not token: flash(f'The username "{username}" does not exist.', "error") current_app.logger.warning( f"Login failed: Username '{username}' not found." ) return render_template("set_token.html") current_app.logger.info(f"User logged in: {username}") return set_cookie(token) return render_template("set_token.html") @main.route("/register", methods=["POST", "GET"]) def register(): """ Handles user registration. GET: Renders the registration form. POST: Processes the form submission. """ if request.method == "POST": # Get form data user_name = request.form.get("username") acknowledge = request.form.get("acknowledge") # Simple validation if not user_name: flash("Username is required.", "error") current_app.logger.debug("Username is required for registering") return render_template("register.html") if retrieve_token(user_name): flash(f'The username "{user_name}" is already taken.', "error") current_app.logger.debug(f"Username {user_name} is already registered") return render_template("register.html") if not acknowledge: flash("You must acknowledge the important information to proceed.", "error") current_app.logger.debug("User did not acknowledge register information") return render_template("register.html") # If all validation passes, "register" the user token = create_token(user_name) current_app.logger.info(f"New user registered: {user_name}") return set_cookie(token) # If GET request, render the form return render_template("register.html") @main.route("/logout", methods=["POST"]) def logout(): token = get_token() user_name = token.name if token else "Unknown" if flask_login.current_user.is_authenticated: current_app.logger.info(f"Admin logout: {flask_login.current_user.id}") flask_login.logout_user() current_app.logger.info(f"User logout: {user_name}") resp = make_response(redirect(url_for("main.index"))) resp.set_cookie("access_token", "", expires=0) return resp @main.route("/switch_course/", methods=["POST"]) @token_required def switch_course(new_course: int, token): switched = get_course(new_course) if switched: token.active_course = switched db.session.commit() current_app.logger.info( f"User {token.name} switched to course: {switched.name}" ) else: current_app.logger.warning( f"User {token.name} attempted to switch to invalid course ID: {new_course}" ) return redirect(url_for("main.index")) def process_access_code(access_code: str, token): password = db.session.execute( db.select(Password).where(Password.value == access_code) ).scalar_one_or_none() if password is not None: if not password.is_expired(): update_token(password.decks) current_app.logger.info(f"User {token.name} used valid access code.") else: current_app.logger.info(f"User {token.name} used expired access code.") flash("Password has expired. Please obtain a new one.", "expired") else: current_app.logger.warning( f"User {token.name} used invalid access code: {access_code}" ) flash("Password is not valid. Please check the code and try again.", "error") return redirect(url_for("main.index")) @main.route("/access", methods=["POST"]) @token_required def access_post(token): access_code = request.form.get("password") if not access_code: return redirect(url_for("main.index")) return process_access_code(access_code, token) # Route for direct link/QR code access (GET) @main.route("/access/", methods=["GET"]) @token_required def access_get(access_code: str, token): return process_access_code(access_code, token) @main.route("/confused", methods=["POST"]) def confused(): socketio.emit("confused", {"time": datetime.utcnow().isoformat()}, to="instructor") return ("", 204)