trying to add gitea
This commit is contained in:
@@ -46,10 +46,12 @@ def create_app(config_class=Config):
|
|||||||
from .routes import main
|
from .routes import main
|
||||||
from .blueprints.slides import slides_bp
|
from .blueprints.slides import slides_bp
|
||||||
from .blueprints.admin import admin_bp
|
from .blueprints.admin import admin_bp
|
||||||
|
from .blueprints.internal import internal_bp
|
||||||
|
|
||||||
app.register_blueprint(main)
|
app.register_blueprint(main)
|
||||||
app.register_blueprint(slides_bp)
|
app.register_blueprint(slides_bp)
|
||||||
app.register_blueprint(admin_bp)
|
app.register_blueprint(admin_bp)
|
||||||
|
app.register_blueprint(internal_bp)
|
||||||
|
|
||||||
@app.template_filter('md')
|
@app.template_filter('md')
|
||||||
def render_markdown(text):
|
def render_markdown(text):
|
||||||
|
|||||||
5
app/blueprints/internal/__init__.py
Normal file
5
app/blueprints/internal/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
from flask import Blueprint
|
||||||
|
|
||||||
|
internal_bp = Blueprint('internal', __name__, url_prefix='/internal')
|
||||||
|
|
||||||
|
from . import routes #noqa
|
||||||
87
app/blueprints/internal/routes.py
Normal file
87
app/blueprints/internal/routes.py
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
import hmac
|
||||||
|
from datetime import datetime
|
||||||
|
import pytz
|
||||||
|
from flask import request, jsonify, current_app
|
||||||
|
from app.models import db, Deck, Course, Password
|
||||||
|
from . import internal_bp
|
||||||
|
|
||||||
|
|
||||||
|
@internal_bp.route('/register-deck', methods=['POST'])
|
||||||
|
def register_deck():
|
||||||
|
# Verify shared secret
|
||||||
|
token = request.headers.get('X-Internal-Token', '')
|
||||||
|
if not hmac.compare_digest(token, current_app.config['INTERNAL_TOKEN']):
|
||||||
|
return jsonify({'error': 'Unauthorized'}), 401
|
||||||
|
|
||||||
|
data = request.get_json()
|
||||||
|
if not data:
|
||||||
|
return jsonify({'error': 'Invalid JSON'}), 400
|
||||||
|
|
||||||
|
for field in ('course', 'deck', 'name'):
|
||||||
|
if not data.get(field):
|
||||||
|
return jsonify({'error': f'Missing required field: {field}'}), 400
|
||||||
|
|
||||||
|
# Look up course by folder name
|
||||||
|
course = db.session.execute(
|
||||||
|
db.select(Course).where(Course.folder == data['course'])
|
||||||
|
).scalar_one_or_none()
|
||||||
|
if not course:
|
||||||
|
return jsonify({'error': f"Course '{data['course']}' not found"}), 404
|
||||||
|
|
||||||
|
# Upsert deck
|
||||||
|
# NOTE: Deck.__init__ takes course= (object), not course_id=
|
||||||
|
deck = db.session.execute(
|
||||||
|
db.select(Deck).where(
|
||||||
|
Deck.deck == data['deck'],
|
||||||
|
Deck.course_id == course.id
|
||||||
|
)
|
||||||
|
).scalar_one_or_none()
|
||||||
|
|
||||||
|
if deck:
|
||||||
|
deck.name = data['name']
|
||||||
|
deck.description = data['description']
|
||||||
|
deck.export_file = data.get('export_file') or None
|
||||||
|
else:
|
||||||
|
deck = Deck(
|
||||||
|
name = data['name'],
|
||||||
|
deck = data['deck'],
|
||||||
|
course = course, # pass object, not course_id
|
||||||
|
description = data['description'],
|
||||||
|
index_file = data.get('index_file', 'index.html'),
|
||||||
|
export_file = data.get('export_file') or None,
|
||||||
|
)
|
||||||
|
db.session.add(deck)
|
||||||
|
db.session.flush() # populate deck.id before linking password
|
||||||
|
|
||||||
|
# Upsert password if provided
|
||||||
|
if data.get('password'):
|
||||||
|
expires_at = None
|
||||||
|
if data.get('password_expires'):
|
||||||
|
try:
|
||||||
|
# Parse ISO string and convert to UTC naive (matches UTCDateTime model)
|
||||||
|
expires_at = datetime.fromisoformat(
|
||||||
|
data['password_expires'].replace('Z', '+00:00')
|
||||||
|
).astimezone(pytz.utc)
|
||||||
|
except ValueError:
|
||||||
|
current_app.logger.warning(
|
||||||
|
f"Invalid password_expires format: {data['password_expires']}"
|
||||||
|
)
|
||||||
|
|
||||||
|
existing_pw = db.session.execute(
|
||||||
|
db.select(Password).where(Password.value == data['password'])
|
||||||
|
).scalar_one_or_none()
|
||||||
|
|
||||||
|
if existing_pw:
|
||||||
|
existing_pw.expires_at = expires_at
|
||||||
|
if deck not in existing_pw.decks:
|
||||||
|
existing_pw.decks.append(deck)
|
||||||
|
else:
|
||||||
|
pw = Password(value=data['password'], expires_at=expires_at)
|
||||||
|
pw.decks.append(deck)
|
||||||
|
db.session.add(pw)
|
||||||
|
|
||||||
|
db.session.commit()
|
||||||
|
current_app.logger.info(
|
||||||
|
f"Registered deck '{data['deck']}' for course '{data['course']}'"
|
||||||
|
)
|
||||||
|
return jsonify({'status': 'ok', 'deck': data['deck']}), 200
|
||||||
@@ -8,6 +8,7 @@ class Config:
|
|||||||
SQLALCHEMY_DATABASE_URI = os.environ.get("DATABASE_URL")
|
SQLALCHEMY_DATABASE_URI = os.environ.get("DATABASE_URL")
|
||||||
SQLALCHEMY_TRACK_MODIFICATIONS = False
|
SQLALCHEMY_TRACK_MODIFICATIONS = False
|
||||||
ADMIN_PASSWORD = os.environ.get("ADMIN_PASSWORD")
|
ADMIN_PASSWORD = os.environ.get("ADMIN_PASSWORD")
|
||||||
|
INTERNAL_TOKEN = os.environ.get("INTERNAL_TOKEN")
|
||||||
COURSE_MAP = {
|
COURSE_MAP = {
|
||||||
"GRNVS25": "GRNVS Tutorium SS 25",
|
"GRNVS25": "GRNVS Tutorium SS 25",
|
||||||
"IT-Sec25": "IT-Sec Tutorium WS 25/26",
|
"IT-Sec25": "IT-Sec Tutorium WS 25/26",
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ class Deck(db.Model):
|
|||||||
id: Mapped[int] = mapped_column(primary_key=True)
|
id: Mapped[int] = mapped_column(primary_key=True)
|
||||||
name: Mapped[str] = mapped_column(String(60), unique=True, nullable=False)
|
name: Mapped[str] = mapped_column(String(60), unique=True, nullable=False)
|
||||||
deck: Mapped[str] = mapped_column(String(60), nullable=False)
|
deck: Mapped[str] = mapped_column(String(60), nullable=False)
|
||||||
description: Mapped[str] = mapped_column(String(60))
|
description: Mapped[str] = mapped_column(String(200))
|
||||||
index_file: Mapped[str] = mapped_column(String(40), nullable=False)
|
index_file: Mapped[str] = mapped_column(String(40), nullable=False)
|
||||||
export_file: Mapped[Optional[str]] = mapped_column(String(40), nullable=True)
|
export_file: Mapped[Optional[str]] = mapped_column(String(40), nullable=True)
|
||||||
|
|
||||||
|
|||||||
@@ -75,3 +75,18 @@ services:
|
|||||||
depends_on:
|
depends_on:
|
||||||
- prometheus
|
- prometheus
|
||||||
- db
|
- db
|
||||||
|
|
||||||
|
act-runner:
|
||||||
|
image: gitea/act_runner:latest
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
- GITEA_INSTANCE_URL=https://gitea.cato447.de
|
||||||
|
- GITEA_RUNNER_REGISTRATION_TOKEN=${ACT_RUNNER_TOKEN}
|
||||||
|
- GITEA_RUNNER_NAME=tutor-runner
|
||||||
|
- GITEA_RUNNER_LABELS=ubuntu-latest:docker://node:22
|
||||||
|
volumes:
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
- ./data/act_runner:/data
|
||||||
|
- ./data/slides:/slides
|
||||||
|
- ./data/exports:/exports
|
||||||
|
mem_limit: 2g
|
||||||
|
|||||||
Reference in New Issue
Block a user