This commit is contained in:
2025-12-09 00:13:03 +01:00
parent 86d0da5ca7
commit daffc4f458
3 changed files with 149 additions and 30 deletions

View File

@@ -10,7 +10,7 @@ import pytz
from flask import (
Blueprint,
abort,
current_app,
current_app, # Used for logging
flash,
make_response,
redirect,
@@ -51,6 +51,7 @@ slidev_all_pattern = re.compile(r"^\/slides\/([^\/]+)\/([^\/]+)(?:\/presenter)?\
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.after_request
def inject_confused_button(response):
@@ -101,9 +102,8 @@ def index():
if not token:
return redirect(url_for("main.set_token"))
else:
for deck in get_unlocked_decks(token):
print(str(deck) + " | " + str(deck.export_file))
# Use current_app.logger for debug info
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())
def set_cookie(token):
@@ -141,10 +141,10 @@ def set_token():
if not token:
flash(f'The username "{user_name}" does not exist.', 'error')
current_app.logger.warning(f"Login failed: Username '{user_name}' not found.")
return render_template('set_token.html')
print(f"Set token {session}")
current_app.logger.info(f"User logged in: {user_name}")
return set_cookie(token)
return render_template('set_token.html')
@@ -177,7 +177,7 @@ def register():
# If all validation passes, "register" the user
token = create_token(user_name)
print(f"User registered: {user_name}")
current_app.logger.info(f"New user registered: {user_name}")
return set_cookie(token)
@@ -186,6 +186,10 @@ def register():
@main.route('/logout', methods=['POST'])
def logout():
token = get_token()
user_name = token.name if token else "Unknown"
current_app.logger.info(f"User logout: {user_name}")
resp = make_response(redirect(url_for('main.index'))) # Redirect to a safe page, e.g., index or login
resp.set_cookie('access_token', '', expires=0) # Clears the cookie
return resp
@@ -202,6 +206,9 @@ def switch_course(new_course: int):
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"))
@@ -217,6 +224,7 @@ def admin_login():
user = user_loader(user_name)
if user and check_password(user, password):
flask_login.login_user(user)
current_app.logger.info(f"Admin login successful: {user_name}")
next = request.args.get('next')
if next is None:
return redirect(url_for('main.index'))
@@ -226,18 +234,18 @@ def admin_login():
return redirect(next)
else:
current_app.logger.warning(f"Failed admin login attempt: {user_name}")
flash('Incorrect username or password. Please try again.', 'error')
return render_template("admin_login.html", form=form)
def process_access_code(access_code: str):
token = get_token()
print(f"Process {token}")
if not token:
# remember where the user wanted to go
session['next_url'] = url_for('main.access_get', access_code=access_code)
print(f"Process {session}")
current_app.logger.debug("Access code used without session. Redirecting to login.")
return redirect(url_for('main.set_token'))
password = db.session.execute(
@@ -247,10 +255,13 @@ def process_access_code(access_code: str):
if password is not None:
if not password.is_expired():
update_token(password.decks)
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'))
@@ -271,6 +282,10 @@ def access_get(access_code: str):
@main.route('/confused', methods=['POST'])
def confused():
token = get_token()
user = token.name if token else "Anonymous"
current_app.logger.info(f"Confused button clicked by: {user}")
socketio.emit(
"confused",
{
@@ -280,34 +295,52 @@ def confused():
)
return ("", 204)
def log_404(reason: str, deck: str, course: str, path: str):
"""
Helper function to log 404 errors with the user's identity.
Uses current_app.logger to ensure logs are handled by the app's config.
"""
token = get_token()
user_identity = f"User: {token.name} (ID: {token.id})" if token else "User: Anonymous"
current_app.logger.warning(f"404 Not Found [{reason}] | {user_identity} | Resource: {course}/{deck}/{path}")
@main.route('/slides/<course_folder>/<deck>/')
@main.route('/slides/<course_folder>/<deck>/<path:path>')
def serve_deck(deck, course_folder, path='index.html'):
course = db.session.execute(db.select(Course).where(Course.folder==course_folder)).scalar()
if not course:
log_404("Course not found", deck, course_folder, path)
abort(404)
assert isinstance(course, Course)
deck = db.session.execute(db.select(Deck).where(Deck.deck==deck and Deck.course_id == course.id)).scalar()
if not deck:
deck_obj = db.session.execute(db.select(Deck).where(Deck.deck==deck and Deck.course_id == course.id)).scalar()
if not deck_obj:
log_404("Deck not in database", deck, course_folder, path)
abort(404)
assert isinstance(deck, Deck)
assert isinstance(deck_obj, Deck)
if not is_deck_unlocked(deck.deck):
if not is_deck_unlocked(deck_obj.deck):
# We generally do not log redirects for locked content as 404s,
# but you could log a warning here if desired.
current_app.logger.info(f"Access denied (locked) for user {get_token().name if get_token() else 'Anon'} -> {deck}")
return redirect(url_for('main.index'))
slides_dir = os.path.join(current_app.config['SLIDES_DIR'], course.folder)
deck_path = os.path.join(slides_dir, deck.deck)
deck_path = os.path.join(slides_dir, deck_obj.deck)
if not os.path.exists(deck_path):
log_404("Deck directory missing on disk", deck, course_folder, path)
return f"Deck '{deck}' not found.", 404
if slidev_all_pattern.match(request.path):
return send_from_directory(deck_path, deck.index_file)
return send_from_directory(deck_path, deck_obj.index_file)
requested_file = os.path.join(deck_path, path)
if os.path.isfile(requested_file):
return send_from_directory(deck_path, path)
log_404("File missing within deck", deck, course_folder, path)
abort(404)
@@ -315,20 +348,26 @@ def serve_deck(deck, course_folder, path='index.html'):
def serve_export(deck, course_folder, path):
course = db.session.execute(db.select(Course).where(Course.folder==course_folder)).scalar()
if not course:
log_404("Export: Course not found", deck, course_folder, path)
abort(404)
assert isinstance(course, Course)
deck = db.session.execute(db.select(Deck).where(Deck.deck==deck and Deck.course_id == course.id)).scalar()
if not deck:
deck_obj = db.session.execute(db.select(Deck).where(Deck.deck==deck and Deck.course_id == course.id)).scalar()
if not deck_obj:
log_404("Export: Deck not in database", deck, course_folder, path)
abort(404)
assert isinstance(deck, Deck)
assert isinstance(deck_obj, Deck)
if not is_deck_unlocked(deck.deck):
if not is_deck_unlocked(deck_obj.deck):
return redirect(url_for('main.index'))
exports_dir = os.path.join(current_app.config['EXPORTS_DIR'], course.folder)
deck_path = os.path.join(exports_dir, deck.deck)
deck_path = os.path.join(exports_dir, deck_obj.deck)
requested_file = os.path.join(deck_path, path)
if os.path.isfile(requested_file):
return send_from_directory(deck_path, path)
log_404("Export file missing", deck, course_folder, path)
abort(404)
@@ -337,6 +376,7 @@ def serve_export(deck, course_folder, path):
def admin():
active_course = get_course(flask_login.current_user.active_course_id)
if active_course is None:
current_app.logger.error(f"Admin user {flask_login.current_user.id} has invalid active_course_id.")
raise Exception
deck_form = DeckForm()
password_form = PasswordForm()
@@ -357,6 +397,7 @@ def admin():
)
db.session.add(deck)
db.session.commit()
current_app.logger.info(f"Admin created deck: {deck.name}")
if "submit_password" in request.form:
if password_form.validate():
@@ -374,11 +415,12 @@ def admin():
password.decks.extend(decks)
db.session.add(password)
db.session.commit()
current_app.logger.info(f"Admin created password: {value} (Expires: {expires_at})")
else:
# Form was submitted but validation failed
for field, errors in password_form.errors.items():
for error in errors:
print(f"Error in {getattr(password_form, field).label.text}: {error}", flush=True)
current_app.logger.warning(f"Admin form validation error ({field}): {error}")
return redirect(url_for('main.admin'))
return render_template("admin.html", deck_form=deck_form, password_form=password_form, decks=decks, passwords=get_passwords(active_course), courses=get_all_courses(), active_course_name=active_course.name)
@@ -389,6 +431,7 @@ def switch_course_admin(new_course: int):
user = flask_login.current_user
if get_course(new_course):
user.active_course_id = new_course
current_app.logger.info(f"Admin {user.id} switched to course ID {new_course}")
return redirect(url_for("main.admin"))
@main.route('/admin/banner', methods=['POST', 'GET'])
@@ -401,12 +444,14 @@ def banner():
banner = InfoBanner(content=content)
db.session.add(banner)
db.session.commit()
current_app.logger.info(f"Admin added banner: {content[:30]}...")
return render_template("banner.html", banner_form=banner_form, banners=db.session.execute(db.select(InfoBanner)).scalars())
@main.route('/admin/decks/<int:deck_id>/delete', methods=['POST'])
@flask_login.login_required
def delete_deck(deck_id: int):
deck = Deck.query.get_or_404(deck_id)
current_app.logger.info(f"Admin deleted deck: {deck.name}")
db.session.delete(deck)
db.session.commit()
return redirect(url_for('main.admin'))
@@ -415,6 +460,7 @@ def delete_deck(deck_id: int):
@flask_login.login_required
def delete_password(password_id: int):
password = Password.query.get_or_404(password_id)
current_app.logger.info(f"Admin deleted password ID {password_id}")
db.session.delete(password)
db.session.commit()
return redirect(url_for('main.admin'))
@@ -430,6 +476,7 @@ def set_active_banner(banner_id):
active_banner.is_active = False
banner.is_active = True
db.session.commit()
current_app.logger.info(f"Admin activated banner ID {banner_id}")
return redirect(url_for('main.banner'))
@main.route('/admin/banner/hide', methods=['POST'])
@@ -439,6 +486,7 @@ def hide_active_banner():
if banner:
banner.is_active = False
db.session.commit()
current_app.logger.info("Admin hid active banner")
return redirect(url_for('main.banner'))
@main.route('/admin/confused')