From 51767bea92f435ef3dbefe9cb2477367aedc1e35 Mon Sep 17 00:00:00 2001 From: sascha Date: Sat, 27 Dec 2025 10:30:32 +0000 Subject: [PATCH] Dateien nach "app" hochladen --- app/Dockerfile | 10 +++ app/app.py | 192 +++++++++++++++++++++++++++++++++++++++++++ app/requirements.txt | 2 + 3 files changed, 204 insertions(+) create mode 100644 app/Dockerfile create mode 100644 app/app.py create mode 100644 app/requirements.txt diff --git a/app/Dockerfile b/app/Dockerfile new file mode 100644 index 0000000..cf3423e --- /dev/null +++ b/app/Dockerfile @@ -0,0 +1,10 @@ +FROM python:3.9-slim + +WORKDIR /app + +COPY requirements.txt requirements.txt +RUN pip install --no-cache-dir -r requirements.txt + +COPY . . + +CMD ["python", "app.py"] diff --git a/app/app.py b/app/app.py new file mode 100644 index 0000000..efd84c6 --- /dev/null +++ b/app/app.py @@ -0,0 +1,192 @@ +import os +import random +import time +import psycopg2 +from flask import Flask, render_template, request, jsonify +from difflib import SequenceMatcher + +app = Flask(__name__) + +# DB Verbindung mit Retry-Logik +def get_db_connection(): + max_retries = 5 + for i in range(max_retries): + try: + conn = psycopg2.connect( + host=os.environ.get('DB_HOST', 'postgres'), + database=os.environ.get('DB_NAME', 'englisch6a'), + user=os.environ.get('DB_USER', 'englisch6a'), + password=os.environ.get('DB_PASS', 'englisch') + ) + return conn + except psycopg2.OperationalError: + if i < max_retries - 1: + time.sleep(2) + continue + else: + raise + +def init_db_if_needed(): + try: + conn = get_db_connection() + cur = conn.cursor() + + # Prüfen ob Tabelle existiert + cur.execute("SELECT EXISTS (SELECT FROM information_schema.tables WHERE table_name = 'vocabulary');") + exists = cur.fetchone()[0] + + if not exists: + print("Tabelle 'vocabulary' nicht gefunden. Initialisiere Datenbank aus init.sql...") + if os.path.exists('init.sql'): + with open('init.sql', 'r', encoding='utf-8') as f: + sql_commands = f.read() + cur.execute(sql_commands) + conn.commit() + print("Datenbank erfolgreich initialisiert!") + else: + print("Fehler: init.sql nicht gefunden!") + else: + # Optional: Prüfen ob leer + cur.execute("SELECT COUNT(*) FROM vocabulary") + count = cur.fetchone()[0] + if count == 0: + print("Tabelle leer. Fülle Daten...") + if os.path.exists('init.sql'): + with open('init.sql', 'r', encoding='utf-8') as f: + sql_commands = f.read() + cur.execute(sql_commands) + conn.commit() + + cur.close() + conn.close() + except Exception as e: + print(f"Datenbank-Check Fehler: {e}") + +@app.route('/') +def index(): + return render_template('index.html') + +@app.route('/api/pages') +def get_pages(): + conn = get_db_connection() + cur = conn.cursor() + cur.execute('SELECT DISTINCT page FROM vocabulary ORDER BY page') + pages = [row[0] for row in cur.fetchall()] + cur.close() + conn.close() + return jsonify(pages) + +@app.route('/api/start', methods=['POST']) +def start_quiz(): + data = request.json + pages = data.get('pages', []) + mode = data.get('mode', 'random') + + conn = get_db_connection() + cur = conn.cursor() + + if not pages: + cur.execute('SELECT id, english, german FROM vocabulary') + else: + cur.execute('SELECT id, english, german FROM vocabulary WHERE page = ANY(%s)', (pages,)) + + rows = cur.fetchall() + cur.close() + conn.close() + + vocab_list = [] + for row in rows: + vid, eng, ger = row + + direction = mode + if mode == 'random': + direction = random.choice(['de-en', 'en-de']) + + question = "" + answer = "" + + if direction == 'de-en': + question = ger + answer = eng + else: + question = eng + answer = ger + + vocab_list.append({ + 'id': vid, + 'question': question, + 'answer': answer, + 'direction': direction + }) + + random.shuffle(vocab_list) + return jsonify(vocab_list) + +def expand_variants(text): + segments = [s.strip() for s in text.split(';')] + possibilities = [] + + for seg in segments: + if '/-' in seg: + parts = seg.split('/-') + base = parts[0].strip() + suffixes = parts[1:] + possibilities.append(base) + for suf in suffixes: + clean_suf = suf.strip() + if clean_suf: + possibilities.append(base + clean_suf) + elif '/' in seg: + choices = seg.split('/') + possibilities.extend([c.strip() for c in choices]) + else: + possibilities.append(seg) + + return possibilities + +def clean_text_for_comparison(text): + text = text.lower().strip() + if text.startswith('to '): + text = text[3:].strip() + return text + +@app.route('/api/check', methods=['POST']) +def check_answer(): + data = request.json + user_input = data.get('input', '') + db_answer = data.get('correct', '') + + clean_user = clean_text_for_comparison(user_input) + valid_answers_raw = expand_variants(db_answer) + valid_answers_clean = [clean_text_for_comparison(v) for v in valid_answers_raw] + + if clean_user in valid_answers_clean: + return jsonify({'status': 'correct', 'msg': 'Richtig!'}) + + best_ratio = 0 + best_match = "" + for correct_option in valid_answers_clean: + ratio = SequenceMatcher(None, clean_user, correct_option).ratio() + if ratio > best_ratio: + best_ratio = ratio + best_match = correct_option + + threshold = 0.75 + if len(best_match) <= 4: + threshold = 0.8 + + if best_ratio >= threshold: + return jsonify({ + 'status': 'typo', + 'msg': f'Fast richtig! Meintest du: "{best_match}"? (Es war "{user_input}")' + }) + + return jsonify({ + 'status': 'wrong', + 'msg': f'Leider falsch. Versuche es erneut.' + }) + +if __name__ == '__main__': + # Beim Start DB prüfen und ggf. befüllen + init_db_if_needed() + app.run(host='0.0.0.0', port=5000) diff --git a/app/requirements.txt b/app/requirements.txt new file mode 100644 index 0000000..630d4b8 --- /dev/null +++ b/app/requirements.txt @@ -0,0 +1,2 @@ +Flask +psycopg2-binary