finished backend upgrades to allow multiple courses

This commit is contained in:
2025-10-19 13:50:46 +02:00
parent 02fc60151f
commit 01f5e8ab08
8 changed files with 218 additions and 118 deletions

View File

@@ -9,6 +9,7 @@ from flask_socketio import join_room
import pytz
from flask import (
Blueprint,
Response,
abort,
current_app,
flash,
@@ -24,13 +25,14 @@ from . import socketio
from .auth import check_password, user_loader
from .forms import DeckForm, InfoBannerForm, LoginForm, PasswordForm
from .models import Deck, InfoBanner, Password, db
from .models import Course, Deck, InfoBanner, Password, db
from .utils import (
create_token,
generate_password,
get_active_banner,
get_all_courses,
get_available_decks,
get_passwords,
get_token,
get_unlocked_decks,
is_deck_unlocked,
@@ -43,7 +45,7 @@ from .utils import (
main = Blueprint('main', __name__)
slidev_deck_pattern = re.compile(r"^\/slides\/[^\/]+\/\d*(?:\?.*)?$")
slidev_all_pattern = re.compile(r"^\/slides\/[^\/]+(?:\/presenter)?\/\d+(?:\?.*)?$")
slidev_all_pattern = re.compile(r"^\/slides\/([^\/]+)\/([^\/]+)(?:\/presenter)?\/(\d+)(?:\?.*)?$")
@socketio.on("connect")
def on_connect():
@@ -100,7 +102,7 @@ def index():
if not token:
return redirect(url_for("main.set_token"))
else:
return render_template('index.html', active_banner=get_active_banner(), decks=get_unlocked_decks(token), user_name=token.name, active_course_id=token.active_course_id, courses=get_all_courses())
return render_template('index.html', active_banner=get_active_banner(), decks=get_unlocked_decks(token), user_name=token.name, active_course=token.active_course, courses=get_all_courses())
@main.route('/set_token', methods=['POST', 'GET'])
def set_token():
@@ -190,6 +192,7 @@ def switch_course(new_course: int):
return redirect(url_for("main.index"))
@main.route('/admin/login', methods=['POST', 'GET'])
def admin_login():
form = LoginForm()
@@ -214,27 +217,34 @@ def admin_login():
return render_template("admin_login.html", form=form)
def process_access_code(access_code: str):
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)
else:
flash('Password has expired. Please obtain a new one.', 'expired')
else:
flash('Password is not valid. Please check the code and try again.', 'error')
return redirect(url_for('main.index'))
@main.route('/access', methods=['POST'])
def access():
password = db.session.execute(db.select(Password).where(Password.value == request.form.get('password'))).scalar()
assert isinstance(password, Password|None)
token = get_token()
if password is not None and not password.is_expired():
update_token(password.decks)
return redirect(url_for('main.index'))
return render_template('index.html', error="Invalid password.", decks=get_unlocked_decks(token))
def access_post():
access_code = request.form.get('password')
if not access_code:
return redirect(url_for("main.index"))
return process_access_code(access_code)
# Route for direct link/QR code access (GET)
@main.route('/access/<access_code>', methods=['GET'])
def access_link(access_code: str):
password = db.session.execute(db.select(Password).where(Password.value == access_code)).scalar()
assert isinstance(password, Password|None)
token = get_token()
if password is not None and not password.is_expired():
update_token(password.decks)
return redirect(url_for('main.index'))
return render_template('index.html', error="Invalid password.", decks=get_unlocked_decks(token))
def access_get(access_code: str):
return process_access_code(access_code)
@main.route('/confused', methods=['POST'])
@@ -248,25 +258,29 @@ def confused():
)
return ("", 204)
@main.route('/slides/<deck>/')
@main.route('/slides/<deck>/<path:path>')
def serve_deck(deck, path='index.html'):
if not db.session.execute(db.select(Deck).where(Deck.deck==deck)).scalar():
@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:
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:
abort(404)
assert isinstance(deck, Deck)
if not is_deck_unlocked(deck):
if not is_deck_unlocked(deck.deck):
return redirect(url_for('main.index'))
slides_dir = current_app.config['SLIDES_DIR']
deck_path = os.path.join(slides_dir, deck)
slides_dir = os.path.join(current_app.config['SLIDES_DIR'], course.folder)
deck_path = os.path.join(slides_dir, deck.deck)
if not os.path.exists(deck_path):
return f"Deck '{deck}' not found.", 404
if slidev_all_pattern.match(request.path):
return send_from_directory(deck_path, 'index.html')
if deck in ["tut-08","tut-11"] and path == 'index.html':
return send_from_directory(deck_path, 'slides.pdf')
return send_from_directory(deck_path, deck.index_file)
requested_file = os.path.join(deck_path, path)
if os.path.isfile(requested_file):
@@ -277,13 +291,16 @@ def serve_deck(deck, path='index.html'):
@main.route('/admin', methods=['GET', 'POST'])
@flask_login.login_required
def admin():
active_course_id = flask_login.current_user.active_course_id
active_course = get_course(active_course_id)
if active_course is None:
raise Exception
deck_form = DeckForm()
password_form = PasswordForm()
if request.method == 'GET':
password_form.value.data = generate_password()
deck_form.deck.choices = get_available_decks(current_app)
decks = db.session.scalars(db.select(Deck).order_by(Deck.name)).all()
passwords = db.session.scalars(db.select(Password)).all()
deck_form.deck.choices = get_available_decks(current_app, active_course)
decks = active_course.decks
password_form.decks.choices = [(deck.id, deck.name) for deck in decks]
@@ -292,6 +309,7 @@ def admin():
deck = Deck(
name=cast(str,deck_form.name.data),
deck=deck_form.deck.data,
course=active_course,
description=cast(str,deck_form.description.data),
)
db.session.add(deck)
@@ -320,7 +338,15 @@ def admin():
print(f"Error in {getattr(password_form, field).label.text}: {error}", flush=True)
return redirect(url_for('main.admin'))
return render_template("admin.html", deck_form=deck_form, password_form=password_form, decks=decks, passwords=passwords)
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_id=active_course_id)
@main.route('/admin/switch_course/<new_course>', methods=['POST'])
@flask_login.login_required
def switch_course_admin(new_course: int):
user = flask_login.current_user
if get_course(new_course):
user.active_course_id = new_course
return redirect(url_for("main.admin"))
@main.route('/admin/banner', methods=['POST', 'GET'])
@flask_login.login_required
@@ -340,7 +366,6 @@ def delete_deck(deck_id: int):
deck = Deck.query.get_or_404(deck_id)
db.session.delete(deck)
db.session.commit()
flash(f'Deck "{deck.name}" deleted.', 'success')
return redirect(url_for('main.admin'))
@main.route('/admin/passwords/<int:password_id>/delete', methods=['POST'])
@@ -349,7 +374,6 @@ def delete_password(password_id: int):
password = Password.query.get_or_404(password_id)
db.session.delete(password)
db.session.commit()
flash(f'Password "{password.value}" deleted.', 'success')
return redirect(url_for('main.admin'))
@main.route('/admin/banner/show/<int:banner_id>', methods=['POST'])