made things prettier
This commit is contained in:
@@ -1,5 +1,7 @@
|
|||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
|
import markdown
|
||||||
|
from markupsafe import Markup
|
||||||
from logging.handlers import WatchedFileHandler
|
from logging.handlers import WatchedFileHandler
|
||||||
from flask import Flask
|
from flask import Flask
|
||||||
from flask_socketio import SocketIO
|
from flask_socketio import SocketIO
|
||||||
@@ -49,4 +51,10 @@ def create_app(config_class=Config):
|
|||||||
app.register_blueprint(slides_bp)
|
app.register_blueprint(slides_bp)
|
||||||
app.register_blueprint(admin_bp)
|
app.register_blueprint(admin_bp)
|
||||||
|
|
||||||
|
@app.template_filter('md')
|
||||||
|
def render_markdown(text):
|
||||||
|
if not text:
|
||||||
|
return ''
|
||||||
|
return Markup(markdown.markdown(text, extensions=['nl2br']))
|
||||||
|
|
||||||
return app
|
return app
|
||||||
|
|||||||
744
app/package-lock.json
generated
744
app/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -15,6 +15,7 @@
|
|||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"type": "commonjs",
|
"type": "commonjs",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@tailwindcss/typography": "^0.5.19",
|
||||||
"autoprefixer": "^10.4.21",
|
"autoprefixer": "^10.4.21",
|
||||||
"flowbite": "^3.1.2",
|
"flowbite": "^3.1.2",
|
||||||
"postcss": "^8.5.6",
|
"postcss": "^8.5.6",
|
||||||
|
|||||||
@@ -8,82 +8,178 @@ bidict==0.23.1
|
|||||||
# via python-socketio
|
# via python-socketio
|
||||||
blinker==1.9.0
|
blinker==1.9.0
|
||||||
# via flask
|
# via flask
|
||||||
|
brotli==1.2.0
|
||||||
|
# via geventhttpclient
|
||||||
|
cachetools==6.2.6 ; python_full_version < '3.10'
|
||||||
|
# via tutor-tool
|
||||||
|
cachetools==7.0.1 ; python_full_version >= '3.10'
|
||||||
|
# via tutor-tool
|
||||||
|
certifi==2026.1.4
|
||||||
|
# via
|
||||||
|
# geventhttpclient
|
||||||
|
# requests
|
||||||
|
cffi==2.0.0 ; (implementation_name == 'pypy' and platform_python_implementation != 'CPython') or (implementation_name == 'pypy' and sys_platform != 'win32') or (platform_python_implementation == 'CPython' and sys_platform == 'win32')
|
||||||
|
# via
|
||||||
|
# gevent
|
||||||
|
# pyzmq
|
||||||
|
charset-normalizer==3.4.4
|
||||||
|
# via requests
|
||||||
click==8.1.8 ; python_full_version < '3.10'
|
click==8.1.8 ; python_full_version < '3.10'
|
||||||
# via flask
|
# via flask
|
||||||
click==8.2.1 ; python_full_version >= '3.10'
|
click==8.2.1 ; python_full_version >= '3.10'
|
||||||
# via flask
|
# via flask
|
||||||
colorama==0.4.6 ; sys_platform == 'win32'
|
colorama==0.4.6 ; sys_platform == 'win32'
|
||||||
# via click
|
# via
|
||||||
|
# click
|
||||||
|
# pytest
|
||||||
|
configargparse==1.7.1
|
||||||
|
# via locust
|
||||||
dnspython==2.7.0 ; python_full_version < '3.10'
|
dnspython==2.7.0 ; python_full_version < '3.10'
|
||||||
# via eventlet
|
# via eventlet
|
||||||
dnspython==2.8.0 ; python_full_version >= '3.10'
|
dnspython==2.8.0 ; python_full_version >= '3.10'
|
||||||
# via eventlet
|
# via eventlet
|
||||||
eventlet==0.40.3
|
eventlet==0.40.3
|
||||||
# via tutor-tool
|
# via tutor-tool
|
||||||
|
exceptiongroup==1.3.1 ; python_full_version == '3.10.*'
|
||||||
|
# via pytest
|
||||||
flask==3.1.2
|
flask==3.1.2
|
||||||
# via
|
# via
|
||||||
# flask-bcrypt
|
# flask-bcrypt
|
||||||
|
# flask-cors
|
||||||
# flask-login
|
# flask-login
|
||||||
# flask-socketio
|
# flask-socketio
|
||||||
# flask-sqlalchemy
|
# flask-sqlalchemy
|
||||||
# flask-wtf
|
# flask-wtf
|
||||||
|
# locust
|
||||||
# tutor-tool
|
# tutor-tool
|
||||||
flask-bcrypt==1.0.1
|
flask-bcrypt==1.0.1
|
||||||
# via tutor-tool
|
# via tutor-tool
|
||||||
|
flask-cors==6.0.2
|
||||||
|
# via locust
|
||||||
flask-login==0.6.3
|
flask-login==0.6.3
|
||||||
# via tutor-tool
|
# via
|
||||||
|
# locust
|
||||||
|
# tutor-tool
|
||||||
flask-socketio==5.5.1
|
flask-socketio==5.5.1
|
||||||
# via tutor-tool
|
# via tutor-tool
|
||||||
flask-sqlalchemy==3.1.1
|
flask-sqlalchemy==3.1.1
|
||||||
# via tutor-tool
|
# via tutor-tool
|
||||||
flask-wtf==1.2.2
|
flask-wtf==1.2.2
|
||||||
# via tutor-tool
|
# via tutor-tool
|
||||||
|
gevent==25.9.1
|
||||||
|
# via
|
||||||
|
# geventhttpclient
|
||||||
|
# locust
|
||||||
|
geventhttpclient==2.3.7
|
||||||
|
# via locust
|
||||||
greenlet==3.2.4
|
greenlet==3.2.4
|
||||||
# via
|
# via
|
||||||
# eventlet
|
# eventlet
|
||||||
|
# gevent
|
||||||
# sqlalchemy
|
# sqlalchemy
|
||||||
gunicorn==23.0.0
|
gunicorn==23.0.0
|
||||||
# via tutor-tool
|
# via tutor-tool
|
||||||
h11==0.16.0
|
h11==0.16.0
|
||||||
# via wsproto
|
# via wsproto
|
||||||
|
idna==3.11
|
||||||
|
# via requests
|
||||||
importlib-metadata==8.7.0 ; python_full_version < '3.10'
|
importlib-metadata==8.7.0 ; python_full_version < '3.10'
|
||||||
# via flask
|
# via
|
||||||
|
# flask
|
||||||
|
# markdown
|
||||||
|
iniconfig==2.3.0 ; python_full_version >= '3.10'
|
||||||
|
# via pytest
|
||||||
itsdangerous==2.2.0
|
itsdangerous==2.2.0
|
||||||
# via
|
# via
|
||||||
# flask
|
# flask
|
||||||
# flask-wtf
|
# flask-wtf
|
||||||
jinja2==3.1.6
|
jinja2==3.1.6
|
||||||
# via flask
|
# via flask
|
||||||
|
locust==2.34.0 ; python_full_version < '3.10'
|
||||||
|
# via tutor-tool
|
||||||
|
locust==2.43.3 ; python_full_version >= '3.10'
|
||||||
|
# via tutor-tool
|
||||||
|
markdown==3.9 ; python_full_version < '3.10'
|
||||||
|
# via tutor-tool
|
||||||
|
markdown==3.10.2 ; python_full_version >= '3.10'
|
||||||
|
# via tutor-tool
|
||||||
markupsafe==3.0.2
|
markupsafe==3.0.2
|
||||||
# via
|
# via
|
||||||
# flask
|
# flask
|
||||||
# jinja2
|
# jinja2
|
||||||
# werkzeug
|
# werkzeug
|
||||||
# wtforms
|
# wtforms
|
||||||
|
msgpack==1.1.2
|
||||||
|
# via locust
|
||||||
packaging==25.0
|
packaging==25.0
|
||||||
# via gunicorn
|
# via
|
||||||
|
# gunicorn
|
||||||
|
# pytest
|
||||||
|
pluggy==1.6.0 ; python_full_version >= '3.10'
|
||||||
|
# via pytest
|
||||||
|
psutil==7.2.2
|
||||||
|
# via locust
|
||||||
psycopg2-binary==2.9.11
|
psycopg2-binary==2.9.11
|
||||||
# via tutor-tool
|
# via tutor-tool
|
||||||
|
pycparser==2.23 ; (python_full_version < '3.10' and implementation_name == 'pypy' and platform_python_implementation != 'CPython') or (python_full_version < '3.10' and implementation_name != 'PyPy' and platform_python_implementation == 'CPython' and sys_platform == 'win32') or (python_full_version < '3.10' and implementation_name == 'pypy' and sys_platform != 'win32')
|
||||||
|
# via cffi
|
||||||
|
pycparser==3.0 ; (python_full_version >= '3.10' and implementation_name == 'pypy' and platform_python_implementation != 'CPython') or (python_full_version >= '3.10' and implementation_name != 'PyPy' and platform_python_implementation == 'CPython' and sys_platform == 'win32') or (python_full_version >= '3.10' and implementation_name == 'pypy' and sys_platform != 'win32')
|
||||||
|
# via cffi
|
||||||
|
pygments==2.19.2 ; python_full_version >= '3.10'
|
||||||
|
# via pytest
|
||||||
|
pytest==9.0.2 ; python_full_version >= '3.10'
|
||||||
|
# via locust
|
||||||
python-engineio==4.12.2
|
python-engineio==4.12.2
|
||||||
# via python-socketio
|
# via
|
||||||
|
# locust
|
||||||
|
# python-socketio
|
||||||
python-socketio==5.13.0
|
python-socketio==5.13.0
|
||||||
# via flask-socketio
|
# via
|
||||||
|
# flask-socketio
|
||||||
|
# locust
|
||||||
pytz==2025.2
|
pytz==2025.2
|
||||||
# via tutor-tool
|
# via tutor-tool
|
||||||
|
pywin32==311 ; sys_platform == 'win32'
|
||||||
|
# via locust
|
||||||
|
pyzmq==27.1.0
|
||||||
|
# via locust
|
||||||
redis==7.0.1 ; python_full_version < '3.10'
|
redis==7.0.1 ; python_full_version < '3.10'
|
||||||
# via tutor-tool
|
# via tutor-tool
|
||||||
redis==7.2.0 ; python_full_version >= '3.10'
|
redis==7.2.0 ; python_full_version >= '3.10'
|
||||||
# via tutor-tool
|
# via tutor-tool
|
||||||
|
requests==2.32.5
|
||||||
|
# via
|
||||||
|
# locust
|
||||||
|
# python-socketio
|
||||||
|
setuptools==82.0.0 ; python_full_version < '3.10'
|
||||||
|
# via
|
||||||
|
# locust
|
||||||
|
# zope-event
|
||||||
simple-websocket==1.1.0
|
simple-websocket==1.1.0
|
||||||
# via python-engineio
|
# via python-engineio
|
||||||
sqlalchemy==2.0.43
|
sqlalchemy==2.0.43
|
||||||
# via flask-sqlalchemy
|
# via flask-sqlalchemy
|
||||||
|
tomli==2.4.0 ; python_full_version < '3.11'
|
||||||
|
# via
|
||||||
|
# locust
|
||||||
|
# pytest
|
||||||
typing-extensions==4.15.0
|
typing-extensions==4.15.0
|
||||||
# via sqlalchemy
|
# via
|
||||||
|
# exceptiongroup
|
||||||
|
# locust
|
||||||
|
# sqlalchemy
|
||||||
|
urllib3==2.6.3
|
||||||
|
# via
|
||||||
|
# geventhttpclient
|
||||||
|
# requests
|
||||||
|
websocket-client==1.9.0 ; python_full_version >= '3.10'
|
||||||
|
# via python-socketio
|
||||||
werkzeug==3.1.3
|
werkzeug==3.1.3
|
||||||
# via
|
# via
|
||||||
# flask
|
# flask
|
||||||
|
# flask-cors
|
||||||
# flask-login
|
# flask-login
|
||||||
|
# locust
|
||||||
wsproto==1.2.0
|
wsproto==1.2.0
|
||||||
# via simple-websocket
|
# via simple-websocket
|
||||||
wtforms==3.2.1
|
wtforms==3.2.1
|
||||||
@@ -92,3 +188,11 @@ xkcdpass==1.20.0
|
|||||||
# via tutor-tool
|
# via tutor-tool
|
||||||
zipp==3.23.0 ; python_full_version < '3.10'
|
zipp==3.23.0 ; python_full_version < '3.10'
|
||||||
# via importlib-metadata
|
# via importlib-metadata
|
||||||
|
zope-event==6.0 ; python_full_version < '3.10'
|
||||||
|
# via gevent
|
||||||
|
zope-event==6.1 ; python_full_version >= '3.10'
|
||||||
|
# via gevent
|
||||||
|
zope-interface==8.0.1 ; python_full_version < '3.10'
|
||||||
|
# via gevent
|
||||||
|
zope-interface==8.2 ; python_full_version >= '3.10'
|
||||||
|
# via gevent
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
require('flowbite/plugin')
|
require('flowbite/plugin'),
|
||||||
|
require('@tailwindcss/typography')
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,14 +11,14 @@
|
|||||||
{% block body %}
|
{% block body %}
|
||||||
|
|
||||||
{# ── Header ────────────────────────────────────────────────────── #}
|
{# ── Header ────────────────────────────────────────────────────── #}
|
||||||
<header class="bg-surface border-b border-border shadow-sm">
|
<header class="w-full px-4 pt-4 pb-2 md:px-6 md:py-3">
|
||||||
<div class="w-full px-6 py-3 flex items-center justify-between gap-4">
|
|
||||||
|
|
||||||
{# Left: user + logout #}
|
{# ── Desktop: single row ───────────────────────────── #}
|
||||||
<div class="flex items-center gap-2 min-w-0">
|
<div class="hidden md:grid md:grid-cols-3 items-center gap-4">
|
||||||
<span class="text-gray-700 dark:text-gray-200 text-sm font-medium truncate">
|
|
||||||
{{ user_name }}
|
{# Col 1: user + logout (left-aligned) #}
|
||||||
</span>
|
<div class="flex items-center gap-2">
|
||||||
|
<span class="text-gray-700 dark:text-gray-200 text-sm font-medium">{{ user_name }}</span>
|
||||||
<form method="POST" action="/logout">
|
<form method="POST" action="/logout">
|
||||||
<button type="submit"
|
<button type="submit"
|
||||||
class="text-xs text-white bg-red-600 px-2.5 py-1 rounded-md hover:bg-red-700 transition">
|
class="text-xs text-white bg-red-600 px-2.5 py-1 rounded-md hover:bg-red-700 transition">
|
||||||
@@ -27,12 +27,40 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{# Center: course switcher #}
|
{# Col 2: course switcher (perfectly centered) #}
|
||||||
<div class="flex justify-center">
|
<div class="flex justify-center">
|
||||||
{{ course_switcher(courses, active_course_name, '/switch_course') }}
|
{{ course_switcher(courses, active_course_name, '/switch_course') }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{# Right: social links + admin #}
|
{# Col 3: social + admin (right-aligned) #}
|
||||||
|
<div class="flex items-center justify-end gap-3">
|
||||||
|
<a href="https://zulip.in.tum.de/#narrow/channel/3234-ITSec-Tutorium-Simon-Bu.C3.9Fmann" target="_blank">
|
||||||
|
<img src="{{ url_for('static', filename='zulip_icon.svg') }}" class="h-7 w-7">
|
||||||
|
</a>
|
||||||
|
<a href="https://discordapp.com/users/306086176585154560" target="_blank">
|
||||||
|
<img src="{{ url_for('static', filename='discord_icon.svg') }}" class="h-7 w-7">
|
||||||
|
</a>
|
||||||
|
<a href="{{ url_for('admin.admin') }}">
|
||||||
|
<button class="bg-primary hover:bg-blue-700 text-white text-sm px-3 py-1 rounded-md transition">
|
||||||
|
Admin
|
||||||
|
</button>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{# ── Mobile: single row only ──────────────────────── #}
|
||||||
|
<div class="flex flex-col gap-3 md:hidden">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<span class="text-gray-700 dark:text-gray-200 text-sm font-medium">{{ user_name }}</span>
|
||||||
|
<form method="POST" action="/logout">
|
||||||
|
<button type="submit"
|
||||||
|
class="text-xs text-white bg-red-600 px-2.5 py-1 rounded-md hover:bg-red-700 transition">
|
||||||
|
Logout
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<a href="https://zulip.in.tum.de/#narrow/channel/3234-ITSec-Tutorium-Simon-Bu.C3.9Fmann" target="_blank">
|
<a href="https://zulip.in.tum.de/#narrow/channel/3234-ITSec-Tutorium-Simon-Bu.C3.9Fmann" target="_blank">
|
||||||
<img src="{{ url_for('static', filename='zulip_icon.svg') }}" class="h-7 w-7">
|
<img src="{{ url_for('static', filename='zulip_icon.svg') }}" class="h-7 w-7">
|
||||||
@@ -47,6 +75,8 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
{# ── Main Content ──────────────────────────────────────────────── #}
|
{# ── Main Content ──────────────────────────────────────────────── #}
|
||||||
@@ -60,6 +90,9 @@
|
|||||||
</section>
|
</section>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="flex justify-center md:hidden">
|
||||||
|
{{ course_switcher(courses, active_course_name, '/switch_course') }}
|
||||||
|
</div>
|
||||||
|
|
||||||
{# ── Password Form ─────────────────────────────────────────── #}
|
{# ── Password Form ─────────────────────────────────────────── #}
|
||||||
<section class="flex justify-center">
|
<section class="flex justify-center">
|
||||||
@@ -110,7 +143,9 @@
|
|||||||
<a href="/slides/{{ deck.course.folder }}/{{ deck.deck }}/{{ deck.index_file }}"
|
<a href="/slides/{{ deck.course.folder }}/{{ deck.deck }}/{{ deck.index_file }}"
|
||||||
class="flex-grow block hover:text-primary transition">
|
class="flex-grow block hover:text-primary transition">
|
||||||
<h3 class="text-lg font-semibold mb-1">{{ deck.name }}</h3>
|
<h3 class="text-lg font-semibold mb-1">{{ deck.name }}</h3>
|
||||||
<p class="text-muted text-sm">{{ deck.description }}</p>
|
<div class="prose prose-sm dark:prose-invert prose-p:text-muted prose-li:text-muted">
|
||||||
|
{{ deck.description | md }}
|
||||||
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
{% if deck.export_file %}
|
{% if deck.export_file %}
|
||||||
|
|||||||
@@ -81,8 +81,11 @@
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onclick="toggleCourseSwitcher()"
|
onclick="toggleCourseSwitcher()"
|
||||||
class="flex items-center gap-2 px-4 py-1.5 rounded-lg border border-border bg-surface
|
class="flex items-center gap-2 px-4 py-1.5 rounded-full
|
||||||
text-sm font-medium text-text hover:border-primary hover:text-primary transition whitespace-nowrap">
|
bg-surface shadow-md ring-1 ring-border
|
||||||
|
text-sm font-medium text-text
|
||||||
|
hover:ring-primary hover:text-primary
|
||||||
|
transition whitespace-nowrap">
|
||||||
<span id="course-switcher-label">{{ active_course_name }}</span>
|
<span id="course-switcher-label">{{ active_course_name }}</span>
|
||||||
<svg class="w-3.5 h-3.5 text-muted transition-transform duration-200" id="course-switcher-chevron"
|
<svg class="w-3.5 h-3.5 text-muted transition-transform duration-200" id="course-switcher-chevron"
|
||||||
xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 10 6">
|
xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 10 6">
|
||||||
|
|||||||
17
app/utils.py
17
app/utils.py
@@ -1,11 +1,12 @@
|
|||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
|
from cachetools import TTLCache, cached
|
||||||
|
from eventlet.semaphore import Semaphore
|
||||||
from flask import request, session, redirect, url_for, current_app, abort, make_response
|
from flask import request, session, redirect, url_for, current_app, abort, make_response
|
||||||
from typing import cast
|
from typing import cast
|
||||||
from itsdangerous import BadSignature, URLSafeSerializer
|
from itsdangerous import BadSignature, URLSafeSerializer
|
||||||
from xkcdpass import xkcd_password
|
from xkcdpass import xkcd_password
|
||||||
from functools import wraps, lru_cache
|
from functools import wraps
|
||||||
from urllib.parse import urlparse, urljoin
|
from urllib.parse import urlparse, urljoin
|
||||||
|
|
||||||
from .models import Deck, InfoBanner, Token, db, Course
|
from .models import Deck, InfoBanner, Token, db, Course
|
||||||
@@ -13,7 +14,8 @@ from .models import Deck, InfoBanner, Token, db, Course
|
|||||||
|
|
||||||
# Create a module-level logger per Flask/Python standards
|
# Create a module-level logger per Flask/Python standards
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
_deck_cache = TTLCache(maxsize=1000, ttl=10)
|
||||||
|
_deck_cache_lock = Semaphore()
|
||||||
|
|
||||||
|
|
||||||
def init_password_generator():
|
def init_password_generator():
|
||||||
@@ -190,12 +192,14 @@ def token_required(f):
|
|||||||
return decorated_function
|
return decorated_function
|
||||||
|
|
||||||
|
|
||||||
# Cache the last 1000 lookups per worker.
|
|
||||||
@lru_cache(maxsize=1000)
|
|
||||||
|
|
||||||
|
@cached(_deck_cache, lock=_deck_cache_lock)
|
||||||
def get_cached_deck_info(token_id, course_folder, deck_slug):
|
def get_cached_deck_info(token_id, course_folder, deck_slug):
|
||||||
"""
|
"""
|
||||||
Returns a dictionary of primitive types for route authorization and routing.
|
Returns a dictionary of primitive types for route authorization and routing.
|
||||||
Never return raw SQLAlchemy models from an lru_cache!
|
Never return raw SQLAlchemy models from a cache!
|
||||||
"""
|
"""
|
||||||
# 1. Fetch Token
|
# 1. Fetch Token
|
||||||
token = db.session.get(Token, token_id)
|
token = db.session.get(Token, token_id)
|
||||||
@@ -221,7 +225,6 @@ def get_cached_deck_info(token_id, course_folder, deck_slug):
|
|||||||
# 4. Check Authorization
|
# 4. Check Authorization
|
||||||
is_unlocked = deck_slug in [d.deck for d in token.decks]
|
is_unlocked = deck_slug in [d.deck for d in token.decks]
|
||||||
|
|
||||||
# Return only primitive data (bools, strings) so the cache is thread-safe!
|
|
||||||
return {
|
return {
|
||||||
"auth": is_unlocked,
|
"auth": is_unlocked,
|
||||||
"course_exists": True,
|
"course_exists": True,
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ description = "Add your description here"
|
|||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.9"
|
requires-python = ">=3.9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"cachetools>=6.2.6",
|
||||||
"eventlet>=0.40.0",
|
"eventlet>=0.40.0",
|
||||||
"flask>=3.1.1",
|
"flask>=3.1.1",
|
||||||
"flask-bcrypt>=1.0.1",
|
"flask-bcrypt>=1.0.1",
|
||||||
@@ -14,6 +15,7 @@ dependencies = [
|
|||||||
"flask-wtf>=1.2.2",
|
"flask-wtf>=1.2.2",
|
||||||
"gunicorn>=23.0.0",
|
"gunicorn>=23.0.0",
|
||||||
"locust>=2.34.0",
|
"locust>=2.34.0",
|
||||||
|
"markdown>=3.9",
|
||||||
"psycopg2-binary>=2.9.11",
|
"psycopg2-binary>=2.9.11",
|
||||||
"pytz>=2025.2",
|
"pytz>=2025.2",
|
||||||
"redis>=7.0.1",
|
"redis>=7.0.1",
|
||||||
|
|||||||
57
uv.lock
generated
57
uv.lock
generated
@@ -171,6 +171,30 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/1b/df/e7c780e463ee7bd7951770692bbea5a605f56b9809ec7f6ce751d7b2ee88/brotli-1.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:1ce223652fd4ed3eb2b7f78fbea31c52314baecfac68db44037bb4167062a937", size = 369008, upload-time = "2025-11-05T18:39:41.515Z" },
|
{ url = "https://files.pythonhosted.org/packages/1b/df/e7c780e463ee7bd7951770692bbea5a605f56b9809ec7f6ce751d7b2ee88/brotli-1.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:1ce223652fd4ed3eb2b7f78fbea31c52314baecfac68db44037bb4167062a937", size = 369008, upload-time = "2025-11-05T18:39:41.515Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cachetools"
|
||||||
|
version = "6.2.6"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
resolution-markers = [
|
||||||
|
"python_full_version < '3.10'",
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/39/91/d9ae9a66b01102a18cd16db0cf4cd54187ffe10f0865cc80071a4104fbb3/cachetools-6.2.6.tar.gz", hash = "sha256:16c33e1f276b9a9c0b49ab5782d901e3ad3de0dd6da9bf9bcd29ac5672f2f9e6", size = 32363, upload-time = "2026-01-27T20:32:59.956Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/90/45/f458fa2c388e79dd9d8b9b0c99f1d31b568f27388f2fdba7bb66bbc0c6ed/cachetools-6.2.6-py3-none-any.whl", hash = "sha256:8c9717235b3c651603fff0076db52d6acbfd1b338b8ed50256092f7ce9c85bda", size = 11668, upload-time = "2026-01-27T20:32:58.527Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cachetools"
|
||||||
|
version = "7.0.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
resolution-markers = [
|
||||||
|
"python_full_version >= '3.10'",
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/d4/07/56595285564e90777d758ebd383d6b0b971b87729bbe2184a849932a3736/cachetools-7.0.1.tar.gz", hash = "sha256:e31e579d2c5b6e2944177a0397150d312888ddf4e16e12f1016068f0c03b8341", size = 36126, upload-time = "2026-02-10T22:24:05.03Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ed/9e/5faefbf9db1db466d633735faceda1f94aa99ce506ac450d232536266b32/cachetools-7.0.1-py3-none-any.whl", hash = "sha256:8f086515c254d5664ae2146d14fc7f65c9a4bce75152eb247e5a9c5e6d7b2ecf", size = 13484, upload-time = "2026-02-10T22:24:03.741Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "certifi"
|
name = "certifi"
|
||||||
version = "2026.1.4"
|
version = "2026.1.4"
|
||||||
@@ -930,6 +954,33 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/3d/d2/dc5379876d3a481720803653ea4d219f0c26f2d2b37c9243baaa16d0bc79/locust-2.43.3-py3-none-any.whl", hash = "sha256:e032c119b54a9d984cb74a936ee83cfd7d68b3c76c8f308af63d04f11396b553", size = 1463473, upload-time = "2026-02-12T09:55:31.727Z" },
|
{ url = "https://files.pythonhosted.org/packages/3d/d2/dc5379876d3a481720803653ea4d219f0c26f2d2b37c9243baaa16d0bc79/locust-2.43.3-py3-none-any.whl", hash = "sha256:e032c119b54a9d984cb74a936ee83cfd7d68b3c76c8f308af63d04f11396b553", size = 1463473, upload-time = "2026-02-12T09:55:31.727Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "markdown"
|
||||||
|
version = "3.9"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
resolution-markers = [
|
||||||
|
"python_full_version < '3.10'",
|
||||||
|
]
|
||||||
|
dependencies = [
|
||||||
|
{ name = "importlib-metadata", marker = "python_full_version < '3.10'" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/8d/37/02347f6d6d8279247a5837082ebc26fc0d5aaeaf75aa013fcbb433c777ab/markdown-3.9.tar.gz", hash = "sha256:d2900fe1782bd33bdbbd56859defef70c2e78fc46668f8eb9df3128138f2cb6a", size = 364585, upload-time = "2025-09-04T20:25:22.885Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/70/ae/44c4a6a4cbb496d93c6257954260fe3a6e91b7bed2240e5dad2a717f5111/markdown-3.9-py3-none-any.whl", hash = "sha256:9f4d91ed810864ea88a6f32c07ba8bee1346c0cc1f6b1f9f6c822f2a9667d280", size = 107441, upload-time = "2025-09-04T20:25:21.784Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "markdown"
|
||||||
|
version = "3.10.2"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
resolution-markers = [
|
||||||
|
"python_full_version >= '3.10'",
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/2b/f4/69fa6ed85ae003c2378ffa8f6d2e3234662abd02c10d216c0ba96081a238/markdown-3.10.2.tar.gz", hash = "sha256:994d51325d25ad8aa7ce4ebaec003febcce822c3f8c911e3b17c52f7f589f950", size = 368805, upload-time = "2026-02-09T14:57:26.942Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl", hash = "sha256:e91464b71ae3ee7afd3017d9f358ef0baf158fd9a298db92f1d4761133824c36", size = 108180, upload-time = "2026-02-09T14:57:25.787Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "markupsafe"
|
name = "markupsafe"
|
||||||
version = "3.0.2"
|
version = "3.0.2"
|
||||||
@@ -1569,6 +1620,8 @@ name = "tutor-tool"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = { virtual = "." }
|
source = { virtual = "." }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
{ name = "cachetools", version = "6.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
|
||||||
|
{ name = "cachetools", version = "7.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
|
||||||
{ name = "eventlet" },
|
{ name = "eventlet" },
|
||||||
{ name = "flask" },
|
{ name = "flask" },
|
||||||
{ name = "flask-bcrypt" },
|
{ name = "flask-bcrypt" },
|
||||||
@@ -1579,6 +1632,8 @@ dependencies = [
|
|||||||
{ name = "gunicorn" },
|
{ name = "gunicorn" },
|
||||||
{ name = "locust", version = "2.34.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
|
{ name = "locust", version = "2.34.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
|
||||||
{ name = "locust", version = "2.43.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
|
{ name = "locust", version = "2.43.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
|
||||||
|
{ name = "markdown", version = "3.9", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
|
||||||
|
{ name = "markdown", version = "3.10.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
|
||||||
{ name = "psycopg2-binary" },
|
{ name = "psycopg2-binary" },
|
||||||
{ name = "pytz" },
|
{ name = "pytz" },
|
||||||
{ name = "redis", version = "7.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
|
{ name = "redis", version = "7.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
|
||||||
@@ -1588,6 +1643,7 @@ dependencies = [
|
|||||||
|
|
||||||
[package.metadata]
|
[package.metadata]
|
||||||
requires-dist = [
|
requires-dist = [
|
||||||
|
{ name = "cachetools", specifier = ">=6.2.6" },
|
||||||
{ name = "eventlet", specifier = ">=0.40.0" },
|
{ name = "eventlet", specifier = ">=0.40.0" },
|
||||||
{ name = "flask", specifier = ">=3.1.1" },
|
{ name = "flask", specifier = ">=3.1.1" },
|
||||||
{ name = "flask-bcrypt", specifier = ">=1.0.1" },
|
{ name = "flask-bcrypt", specifier = ">=1.0.1" },
|
||||||
@@ -1597,6 +1653,7 @@ requires-dist = [
|
|||||||
{ name = "flask-wtf", specifier = ">=1.2.2" },
|
{ name = "flask-wtf", specifier = ">=1.2.2" },
|
||||||
{ name = "gunicorn", specifier = ">=23.0.0" },
|
{ name = "gunicorn", specifier = ">=23.0.0" },
|
||||||
{ name = "locust", specifier = ">=2.34.0" },
|
{ name = "locust", specifier = ">=2.34.0" },
|
||||||
|
{ name = "markdown", specifier = ">=3.9" },
|
||||||
{ name = "psycopg2-binary", specifier = ">=2.9.11" },
|
{ name = "psycopg2-binary", specifier = ">=2.9.11" },
|
||||||
{ name = "pytz", specifier = ">=2025.2" },
|
{ name = "pytz", specifier = ">=2025.2" },
|
||||||
{ name = "redis", specifier = ">=7.0.1" },
|
{ name = "redis", specifier = ">=7.0.1" },
|
||||||
|
|||||||
Reference in New Issue
Block a user