From 66d987857a78337db33b9c86d9be173cc8dc4de9 Mon Sep 17 00:00:00 2001 From: Till Tomczak Date: Sun, 27 Apr 2025 07:46:48 +0200 Subject: [PATCH] Remove deprecated database management scripts and admin user creation functionality: Delete create_admin.py, fix_db.py, rebuild_db.py, and test_db.py to streamline the project structure and eliminate unused code. Update README.md with installation instructions and management tools for improved user guidance. --- website/README.md | 38 +++++ website/TOOLS.py | 125 ++++++++++++++ website/create_admin.py | 44 ----- website/templates/mindmap.html | 103 +++++++++++- website/test_db.py | 30 ---- website/utils/__init__.py | 34 ++++ .../__pycache__/__init__.cpython-311.pyc | Bin 0 -> 986 bytes .../utils/__pycache__/db_fix.cpython-311.pyc | Bin 0 -> 4502 bytes .../__pycache__/db_rebuild.cpython-311.pyc | Bin 0 -> 4104 bytes .../utils/__pycache__/db_test.cpython-311.pyc | Bin 0 -> 6857 bytes .../utils/__pycache__/server.cpython-311.pyc | Bin 0 -> 2106 bytes .../__pycache__/user_manager.cpython-311.pyc | Bin 0 -> 9847 bytes website/{fix_db.py => utils/db_fix.py} | 8 + .../{rebuild_db.py => utils/db_rebuild.py} | 8 + website/utils/db_test.py | 120 +++++++++++++ website/utils/server.py | 34 ++++ website/utils/user_manager.py | 159 ++++++++++++++++++ 17 files changed, 620 insertions(+), 83 deletions(-) create mode 100755 website/TOOLS.py delete mode 100644 website/create_admin.py delete mode 100644 website/test_db.py create mode 100755 website/utils/__init__.py create mode 100644 website/utils/__pycache__/__init__.cpython-311.pyc create mode 100644 website/utils/__pycache__/db_fix.cpython-311.pyc create mode 100644 website/utils/__pycache__/db_rebuild.cpython-311.pyc create mode 100644 website/utils/__pycache__/db_test.cpython-311.pyc create mode 100644 website/utils/__pycache__/server.cpython-311.pyc create mode 100644 website/utils/__pycache__/user_manager.cpython-311.pyc rename website/{fix_db.py => utils/db_fix.py} (92%) rename website/{rebuild_db.py => utils/db_rebuild.py} (91%) mode change 100644 => 100755 create mode 100755 website/utils/db_test.py create mode 100755 website/utils/server.py create mode 100755 website/utils/user_manager.py diff --git a/website/README.md b/website/README.md index 46e5eae..cf56801 100644 --- a/website/README.md +++ b/website/README.md @@ -12,6 +12,44 @@ Das MindMapProjekt ist eine interaktive Plattform zum Visualisieren, Erforschen - **Datenbank**: SQLite mit SQLAlchemy - **KI-Integration**: OpenAI API für intelligente Assistenz +## Installation und Verwendung + +### Installation +1. Repository klonen +2. Virtuelle Umgebung erstellen: `python -m venv venv` +3. Virtuelle Umgebung aktivieren: + - Windows: `venv\Scripts\activate` + - Unix/MacOS: `source venv/bin/activate` +4. Abhängigkeiten installieren: `pip install -r requirements.txt` +5. Datenbank initialisieren: `python TOOLS.py db:rebuild` +6. Admin-Benutzer erstellen: `python TOOLS.py user:admin` +7. Server starten: `python TOOLS.py server:run` + +### Standardbenutzer +- **Admin-Benutzer**: Username: `admin` / Passwort: `admin` +- **Testbenutzer**: Username: `user` / Passwort: `user` + +### Verwaltungswerkzeuge mit TOOLS.py +Das Projekt enthält ein zentrales Verwaltungsskript `TOOLS.py`, das verschiedene Hilfsfunktionen bietet: + +#### Datenbankverwaltung +- `python TOOLS.py db:fix` - Reparieren der Datenbankstruktur +- `python TOOLS.py db:rebuild` - Datenbank neu aufbauen (löscht alle Daten!) +- `python TOOLS.py db:test` - Datenbankverbindung und Modelle testen +- `python TOOLS.py db:stats` - Datenbankstatistiken anzeigen + +#### Benutzerverwaltung +- `python TOOLS.py user:list` - Alle Benutzer anzeigen +- `python TOOLS.py user:create -u USERNAME -e EMAIL -p PASSWORD [-a]` - Neuen Benutzer erstellen +- `python TOOLS.py user:admin` - Admin-Benutzer erstellen (admin/admin) +- `python TOOLS.py user:reset-pw -u USERNAME -p NEWPASSWORD` - Benutzerpasswort zurücksetzen +- `python TOOLS.py user:delete -u USERNAME` - Benutzer löschen + +#### Serververwaltung +- `python TOOLS.py server:run [--host HOST] [--port PORT] [--no-debug]` - Entwicklungsserver starten + +Für detaillierte Hilfe: `python TOOLS.py -h` + ## Roadmap der Überarbeitung ### Phase 1: Grundlegende Infrastruktur ✅ diff --git a/website/TOOLS.py b/website/TOOLS.py new file mode 100755 index 0000000..ebea075 --- /dev/null +++ b/website/TOOLS.py @@ -0,0 +1,125 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +TOOLS.py - Main utility script for the website application. + +This script provides a command-line interface to all utilities +for database management, user management, and server administration. + +Usage: + python3 TOOLS.py [command] [options] + +Available commands: + - db:fix Fix database schema + - db:rebuild Completely rebuild the database + - db:test Test database connection and models + - db:stats Show database statistics + + - user:list List all users + - user:create Create a new user + - user:admin Create admin user (username: admin, password: admin) + - user:reset-pw Reset user password + - user:delete Delete a user + + - server:run Run the development server + +Examples: + python3 TOOLS.py db:rebuild + python3 TOOLS.py user:admin + python3 TOOLS.py server:run --port 8080 +""" + +import os +import sys +import argparse +from utils import ( + fix_database_schema, rebuild_database, run_all_tests, print_database_stats, + list_users, create_user, reset_password, delete_user, create_admin_user, + run_development_server +) + +def parse_args(): + parser = argparse.ArgumentParser( + description='Website Administration Tools', + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=__doc__ + ) + + # Main command argument + parser.add_argument('command', help='Command to execute') + + # Additional arguments + parser.add_argument('--username', '-u', help='Username for user commands') + parser.add_argument('--email', '-e', help='Email for user creation') + parser.add_argument('--password', '-p', help='Password for user creation/reset') + parser.add_argument('--admin', '-a', action='store_true', help='Make user an admin') + parser.add_argument('--host', help='Host for server (default: 127.0.0.1)') + parser.add_argument('--port', type=int, help='Port for server (default: 5000)') + parser.add_argument('--no-debug', action='store_true', help='Disable debug mode for server') + + return parser.parse_args() + +def main(): + args = parse_args() + + # Database commands + if args.command == 'db:fix': + fix_database_schema() + + elif args.command == 'db:rebuild': + print("WARNING: This will delete all data in the database!") + confirm = input("Are you sure you want to continue? (y/n): ").lower() + if confirm == 'y': + rebuild_database() + else: + print("Aborted.") + + elif args.command == 'db:test': + run_all_tests() + + elif args.command == 'db:stats': + print_database_stats() + + # User commands + elif args.command == 'user:list': + list_users() + + elif args.command == 'user:create': + if not args.username or not args.email or not args.password: + print("Error: Username, email, and password are required.") + print("Example: python3 TOOLS.py user:create -u username -e email -p password [-a]") + sys.exit(1) + create_user(args.username, args.email, args.password, args.admin) + + elif args.command == 'user:admin': + create_admin_user() + + elif args.command == 'user:reset-pw': + if not args.username or not args.password: + print("Error: Username and password are required.") + print("Example: python3 TOOLS.py user:reset-pw -u username -p new_password") + sys.exit(1) + reset_password(args.username, args.password) + + elif args.command == 'user:delete': + if not args.username: + print("Error: Username is required.") + print("Example: python3 TOOLS.py user:delete -u username") + sys.exit(1) + delete_user(args.username) + + # Server commands + elif args.command == 'server:run': + host = args.host or '127.0.0.1' + port = args.port or 5000 + debug = not args.no_debug + run_development_server(host=host, port=port, debug=debug) + + else: + print(f"Unknown command: {args.command}") + print("Run 'python3 TOOLS.py -h' for usage information") + sys.exit(1) + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/website/create_admin.py b/website/create_admin.py deleted file mode 100644 index 3de3c2d..0000000 --- a/website/create_admin.py +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -from app import app -from models import db, User -from datetime import datetime - -def create_admin_user(): - """Create an admin user in the database.""" - with app.app_context(): - try: - # Check if admin user already exists - admin = User.query.filter_by(username='admin').first() - if admin: - print("Admin user already exists") - return - - # Create admin user - admin = User( - username='admin', - email='admin@example.com', - is_admin=True, - created_at=datetime.utcnow() - ) - admin.set_password('admin') - - # Add and commit - db.session.add(admin) - db.session.commit() - - print("Admin user created successfully!") - - # Verify user was added - user = User.query.filter_by(username='admin').first() - if user: - print(f"Verified: Admin user exists with ID {user.id}") - else: - print("WARNING: Failed to verify admin user creation") - - except Exception as e: - print(f"Error creating admin user: {e}") - -if __name__ == "__main__": - create_admin_user() \ No newline at end of file diff --git a/website/templates/mindmap.html b/website/templates/mindmap.html index c06af0f..958e7cb 100644 --- a/website/templates/mindmap.html +++ b/website/templates/mindmap.html @@ -730,18 +730,53 @@ document.addEventListener('DOMContentLoaded', function() { tooltipEnabled: true, onNodeClick: function(node) { console.log('Node clicked:', node); - // Hier könnten wir weitere Informationen zum Knoten anzeigen - // oder mehr Daten vom Server abrufen - // Beispiel: Gedanken zu diesem Knoten laden + // Gedanken zu diesem Knoten laden fetch(`/api/nodes/${node.id}/thoughts`) - .then(response => response.json()) + .then(response => { + if (!response.ok) { + throw new Error('Netzwerkantwort war nicht ok'); + } + return response.json(); + }) .then(data => { console.log('Gedanken zu diesem Knoten:', data); - // Hier könnte die Anzeige aktualisiert werden + + // Gedanken im Seitenbereich anzeigen + const thoughtsContainer = document.getElementById('thoughts-container'); + if (thoughtsContainer) { + thoughtsContainer.innerHTML = ''; + + if (data.thoughts && data.thoughts.length > 0) { + data.thoughts.forEach(thought => { + const thoughtElement = document.createElement('div'); + thoughtElement.className = 'thought-item bg-gray-800 rounded-lg p-4 mb-3'; + thoughtElement.innerHTML = ` +

${thought.title}

+

${thought.content}

+
+ ${new Date(thought.created_at).toLocaleDateString('de-DE')} + +
+ `; + thoughtsContainer.appendChild(thoughtElement); + }); + } else { + thoughtsContainer.innerHTML = '

Keine Gedanken für diesen Knoten vorhanden.

'; + } + } + + // Aktualisiere das Formular zum Hinzufügen von Gedanken + document.getElementById('thought-node-id').value = node.id; }) .catch(error => { console.error('Fehler beim Laden der Gedanken:', error); + // Benutzer über den Fehler informieren + if (window.mindmap && window.mindmap.showFlash) { + window.mindmap.showFlash('Fehler beim Laden der Gedanken', 'error'); + } }); } }; @@ -858,8 +893,30 @@ document.addEventListener('DOMContentLoaded', function() { }) .then(response => response.json()) .then(data => { - alert('Gedanke wurde hinzugefügt!'); - // Optional: Knoten neu laden oder Zähler aktualisieren + // Erfolgsmeldung anzeigen + const notification = document.createElement('div'); + notification.className = 'fixed top-4 right-4 bg-green-600 text-white p-4 rounded-lg shadow-lg z-50 animate-fade-in'; + notification.innerHTML = ` +
+ +

Gedanke wurde erfolgreich hinzugefügt!

+
+ `; + document.body.appendChild(notification); + + // Notification nach 3 Sekunden ausblenden + setTimeout(() => { + notification.classList.add('animate-fade-out'); + setTimeout(() => document.body.removeChild(notification), 500); + }, 3000); + + // Aktualisiere den Gedankenzähler am Knoten, falls vorhanden + const nodeElement = document.getElementById(`node-${window.mindmap.selectedNode.id}`); + const countElement = nodeElement.querySelector('.thought-count'); + if (countElement) { + const currentCount = parseInt(countElement.textContent); + countElement.textContent = (currentCount + 1).toString(); + } }) .catch(error => { console.error('Fehler beim Hinzufügen des Gedankens:', error); @@ -874,9 +931,37 @@ document.addEventListener('DOMContentLoaded', function() { document.getElementById('connect-btn').addEventListener('click', function() { if (window.mindmap && window.mindmap.selectedNode) { - alert('Bitte wähle einen weiteren Knoten aus, um eine Verbindung herzustellen.'); - // Hier könnte ein spezieller Modus aktiviert werden, der auf den nächsten Klick wartet + // Speichere den ersten ausgewählten Knoten + window.mindmap.sourceNode = window.mindmap.selectedNode; + + // Visuelles Feedback für den Benutzer + const selectedCircle = d3.select(`#node-${window.mindmap.selectedNode.id} circle`); + selectedCircle.classed('connection-source', true); + + // Benutzerfreundlichere Benachrichtigung mit Statusanzeige + const notification = document.createElement('div'); + notification.id = 'connection-notification'; + notification.className = 'fixed top-4 right-4 bg-purple-600 text-white p-4 rounded-lg shadow-lg z-50'; + notification.innerHTML = ` +

Verbindungsmodus aktiv

+

Wähle einen zweiten Knoten aus, um eine Verbindung herzustellen

+ + `; + document.body.appendChild(notification); + + // Abbrechen-Button-Funktionalität + document.getElementById('cancel-connection').addEventListener('click', function() { + window.mindmap.connectMode = false; + window.mindmap.sourceNode = null; + selectedCircle.classed('connection-source', false); + document.body.removeChild(notification); + }); + + // Aktiviere den Verbindungsmodus window.mindmap.connectMode = true; + + // Cursor-Stil ändern, um den Verbindungsmodus anzuzeigen + document.getElementById('mindmap-container').style.cursor = 'crosshair'; } else { alert('Bitte wähle zuerst einen Knoten aus.'); } diff --git a/website/test_db.py b/website/test_db.py deleted file mode 100644 index 683f354..0000000 --- a/website/test_db.py +++ /dev/null @@ -1,30 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -from app import app -from models import db, User - -def test_user_query(): - """Test if we can query the user table.""" - with app.app_context(): - try: - # Try to query all users - users = User.query.all() - print(f"Found {len(users)} users") - - for user in users: - print(f"User: {user.username}, Email: {user.email}") - - # Try to query by username - this is where the error happens - user = User.query.filter_by(username='admin').first() - if user: - print(f"Found admin user: {user.username}") - else: - print("Admin user not found") - - print("All queries completed successfully!") - except Exception as e: - print(f"Error: {e}") - -if __name__ == "__main__": - test_user_query() \ No newline at end of file diff --git a/website/utils/__init__.py b/website/utils/__init__.py new file mode 100755 index 0000000..2a25a25 --- /dev/null +++ b/website/utils/__init__.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +Utility functions for the website application. +This package contains various utilities for database management, +user management, and server administration. +""" + +from .db_fix import fix_database_schema +from .db_rebuild import rebuild_database +from .db_test import test_database_connection, test_models, print_database_stats, run_all_tests +from .user_manager import list_users, create_user, reset_password, delete_user, create_admin_user +from .server import run_development_server + +__all__ = [ + # Database utilities + 'fix_database_schema', + 'rebuild_database', + 'test_database_connection', + 'test_models', + 'print_database_stats', + 'run_all_tests', + + # User management + 'list_users', + 'create_user', + 'reset_password', + 'delete_user', + 'create_admin_user', + + # Server management + 'run_development_server', +] \ No newline at end of file diff --git a/website/utils/__pycache__/__init__.cpython-311.pyc b/website/utils/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3a362b9b4d807420b29cf86b6bd339d80fbb1c89 GIT binary patch literal 986 zcma)4J&)5c7j0zvZXiT%89-tVtz8Y6gK{7B~?+X(&im)+Ca9j#Ll#R3G;nz9VCm;6x4ZK&3cULp)SR_^8o! z<*^#!QGm`7i@pT-g!NvbasU12_>C1(*sE+-7r7N>VX|4NGdt(m1)myWc}A;Biky1o zS^Rn~OjgnS9X;n+UKW-L_+>%0C~K3|jWfYFP8hXxN)68xEx=sy!aj*>!}TGN(Sl{b zEr6y>i9#5wx50h|(2sH8PG{nh?7R_^&$*&NA8S6Xg=BlxICS?dH+D}D$gto|T`p-f zs$^Umcc;>#IIuI8TH{h(7lcYlyr~&?Tr43Q;!`#*$u*~zH#axb+;B@OYRpBcnM=Sc z-zi61%ch*h9+JK5eKWq`vaEb3$foA;K*Kg59eU_^=z54e^gQ%E_|?&ghk=LG!w}%* z_|!#&urepab=j0aqZ)rMhRe1+jc*qL;mi6{5I688lFgX(Uqia?Cg&!!kLz1SzM*T# z1l>HH%uB^5d8zpX>YQxXXaDuPVJ3vYge@Uw)zwY!8BC*={JDMr4*aamcYt*}2!eGl y48k??FhIehC3<)$D+J6fSs}9gm#h$7{-3PSHY@U-7KO2 literal 0 HcmV?d00001 diff --git a/website/utils/__pycache__/db_fix.cpython-311.pyc b/website/utils/__pycache__/db_fix.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6c05a3df1c6627d9a2744f40ae950dd6e79ac3aa GIT binary patch literal 4502 zcmcInO>7&-6`tj;$R#=C>L-f)Cwl`sp;aT4EZMHD$ckx8{z*m2rIb3MQ7kC#%36E* z)7_OV5ppaP1}X~#lo$>qv@xnnouo<89DC`dMGv{{wtFBJFkqm_Avd;e0K=!g+2yZ( z47)&wv$L}^?|t*$y!Yk}KM#ch2+F_z8k+qx^!`acs>N67JiSOEw1z|^QVL4CZz@T_ zw@2}0JV_7bj?qclp<$9O61wiK!faG=ssqmZ6)D&z=E{fUe*}_!1&>=^^py-qbc0O6 zDo^8EL{i|92N2;0#XUOeh(0z8NK);gTHmTkfJ{tLTNcO==f?Hyl0{dyv%j z2mrpS!XTifCY`5;{ho}D_fR$~rgTY{Gg6Porc?R6?GvYkd`h2%IgdD*G)GJCidv8h8nygE;_na;(S}$zWj65IBFW4a|OXLw?1mM(G zz-WUX|GYfkeBNv6St*m^rx*B?D9YJud`8wZ@=oWJxlC4z z7Ty`D<%XQ3Wh1(q8KV3-RZ8iEG^Hp!qz^^X_!%{q;kC4y($lliXteNVjk!XQCQOnD znJ1l=G;L;1Q5N19FYG#(%Vr_*5I8+oOGkrzAvQTWHhOM~C+UShpO}dA+AkDHBSI#n z>5|G{xj32_<@JTUbUNa!5#eLwBM$lL$ebprk;3Ws;pIxmi?6Oj*X2a)LOkXI2y%8N zcbMSzq-)t4>`ih&2Ru*rL$ijifu(;QASauMcEgBUKJgTTU&@3UNtKY2~ z1MBp9>@#m|z{!q<1EVClH<+21vwVG*2JJw_PC`mA9ErJ}5vh+wD(*pSRc9jx?~9F1 zjVAc1*x9jBo@^5z8yVrxO^m%CALr`;{M4oR=;Tx^e%THvU`Rp9U6Zr*rRrn<<5G2i zhNZlzo8Zf8AyU=I{oZ+r-WO@(GV?!+CC*)pB@XxZ_4U~Fw45s(s=~v^&e(@ugbkeh z)aVCO4FOb%)VVyk!sY;)>SGAR(6y?H>>ua0T?^;sMjY z$MWwfqhl?vIn8%$=RK~-EptD3QEPkg*6K$qA3eNga&KAOTV=%fJ09aUqkY1}mo0qR zz?UE6a4~*wqByZU;mn*e@t}nV4Ln%F?d6Uttb_e8vdVvIOVtE35rK2V?CQ6=`j_!zHe`f*zu9N9!xlSiu*0Aa z-nG7H1dl*<_VQg5pRw>61D}DtTpeE-H}=L&JZ|A}1CKumcD)L5$NG>Fj6ije-!bu! zg@+70R6`y!aooaj1IM2PTi3A>JOtGtK561p7CvR*Q#In4iO*X2tbxxGD}+{qE5UoA zVrV&3V)1HVC2$WHaYe=BCVRqSPZ;cp64z0dsh8Z;ZM@{!vuAae*{-MY;M@;lZ(i!7 z|KdG60I$D!N4nwlZ69;#nD5&kQ_$M10J%=i3PMkZ8_frROC^73Y+75;?6924=Tu#h zr=#RAmF=&6J8`d)E4b}TO>53yH9(t@6-f~6Kt82PS)KgZvb}N^&T8E`MBD?yo5_ii zqB$podyH13Dzk_REcwZ=gu+YC zuY|gd=2}95CFfT{okq2Sxx%f*E33kau%7tr%5Q~V3tuF@zVb)m_rjk(FyB6Jy?x&7 zzF>7^7>kOe1CwOgek>UmGm@>Hq)$ literal 0 HcmV?d00001 diff --git a/website/utils/__pycache__/db_rebuild.cpython-311.pyc b/website/utils/__pycache__/db_rebuild.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1d4f21d2973c8177175381cc380f9bbbad0af1ea GIT binary patch literal 4104 zcmbVPZ)h9I6`$4aN+WqCt>u4b$Bx(MOKpXVopVS6iJkW1SiZYJ4lX_1h{7UkH?f?R zR=2yVZDC)-aZq0{BpfLTZA1N`m)`XPZK0(pw9uyg&}+WznuUl3L80VBzv;*Y!a<>L zR$BeD6D}Ri&Yw5)e(%kjH*ZG242MGq67~1+(jS5d{hM^W6sR>GpQ8}Ei6kUZ3QAUM zDoH`#r}(nIq>rl7=p^mVVUnzjmi->gwuj>p-1-G);1=duM-JWr%|3^(nx5B7hGe=) zrw)&&xt5R|y5j>x_^QcW8Lekhzt?tvkgMV)uQc;G*TdM-{mSV|ePk;%$&+M!eVyc3WX#eB+Ga{AMn z3}d1sUrZGgLrg>GlB#879j5xEMTdD;mo*2^R_SZ$x*%u}gVFFk{sl`m&!`&-q2+qJ zRlTF2paHYJBvD3v6A}6;y6tNR&OBo^{4R+vvKpqHJBWCQTMLV$#00;)6NNzfg!(kf0qL1(XR763+H+76~5Gv-)KE| zg}pBt3qGjDf<_D&MXw>9S@BgP0);L>%!O-#IgjFf#rat^n^$B*R@MYfUMyr3Nf(SI zS%51dwV2XnVR226G&P^kvA~_)x0~3p>^yNZc zBX`re;_zwc$&ygW3-W44Hwb@ShM@|@(S?+j1L_H3RxK!!kW&qzdK(Em(;jk&vEq1!qcjVO+vB*SP&BhrA zlS#Q$R(4o9o60B-&gh~$(Fs+xlf;x!e5o!{lP?vNlvW342tbi1Wixc5xbIyc>9r+F zf?i0cWnI5mP?WWi;{G~b?Iso)H!-MO9IsouwrK*W>+WMa((s(7shS`av=)ok*iH!X zevL#lrvu&rVp`1^@~Yvmd6;czBjpdyvMJc;jHbi0BIDXlBhN8-b-;FsDMj%tN7@>>W;OC^aQp=$ol{pFPL~aM zjp+J{s!2{j2dlW)DM@ntu*_`6(EKi-rYZ{YjX3LcRDHyK#b|wGl#(@ap=q5^=wu}V zNncM*E~#00GOcRzq$K}faz$R$GlpER$;$Lecn{SqG?8C(_*z@^99A0#JQp|ws$W5m zg51`y6&$dG0~K_Vjk!e`hs{X*lK>QuEqu_%2TgqNi63#1t>f2DT|ISs%<6dA?s&O^ z0>K1T9vInv(;7ImdA8zX!wIUy4_yzFUy1L%$$iY-Uf8~D@l!TGW%5&HzQ^pHyVn5) zWQ$MOe8P0|({k5f1yKOdT)Okm{CV~-)E^UnN|?Q8AY1&L&Ci*;@>56^njk>q2}Z$= zt&Sg`zA=CE?T_F7WZvq2)$V@vH-?F)Ej(@GX=uQjf)O+FEens?c+A9OWgOb-_$a&; z-V8q?W@sG=e-zz{Zbo-=@!h4K!BXr%si(i(H+o^b?y$)n235i_Gd5*$M{Mqh$sH+k2P|&P=Eh8J>=BteZE-U; zH)C=$rLMk;N_mdtdefK0ufD1~SHsPoKFL#v@2?;_2!7`k<*Gu%JKV6vjo93X$&I)Q zP5*xO9`)PA?-C%dMI_hh?t1_b$o{|AD&Cbx=zD0;py}U-b8+K!|5=v)>%P}chkf@s z3flL>p;^S_GOF@xOX~&>T{s!s)g#ykOMO84OLRW(1S3;31 z?yrP~%+_2&p)2mMgnCRbSNtf#Z!KICuZp+c`PstH#h-|uzH@is*W$0ld*8DTCG0~9 zYiQmcnzzDl*x@&>gi4715dSFpLG(Ix{e*?~+i1Uu_WuhVH=mtL=z#g`Tw!J?@VI*M lK7|-=v$#X|n{fVKBRW@dxE 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 Szl}@qM&^tN-UFkLSnL01FXk2i literal 0 HcmV?d00001 diff --git a/website/utils/__pycache__/server.cpython-311.pyc b/website/utils/__pycache__/server.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..40209bf414f2612bdb165758ace137f12a66a1ec GIT binary patch literal 2106 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