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 & RESET (Wie gehabt) --- def get_db_connection(): max_retries = 10 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: time.sleep(2) continue raise Exception("DB Connection failed") def reset_db_on_startup(): try: conn = get_db_connection() cur = conn.cursor() if os.path.exists('init.sql'): with open('init.sql', 'r', encoding='utf-8') as f: cur.execute(f.read()) conn.commit() print("DB Reset erfolgreich.") cur.close() conn.close() except Exception as e: print(f"DB Reset Fehler: {e}") reset_db_on_startup() # --- HILFSFUNKTIONEN --- def clean_text(text): return text.lower().strip() def check_part(user_in, correct_str): variants = [x.strip().lower() for x in correct_str.split('/')] return user_in in variants # --- API ROUTEN --- @app.route('/') def index(): return render_template('index.html') @app.route('/api/pages') def get_pages(): # Wir filtern jetzt nach Typ! mode_type = request.args.get('type', 'vocab') # 'vocab' oder 'irregular' conn = get_db_connection() cur = conn.cursor() if mode_type == 'irregular': cur.execute("SELECT DISTINCT page FROM irregular_verbs ORDER BY page") else: cur.execute("SELECT DISTINCT page FROM vocabulary ORDER BY page") rows = cur.fetchall() cur.close() conn.close() pages = sorted([r[0] for r in rows]) return jsonify(pages) @app.route('/api/question', methods=['POST']) def get_question(): data = request.json main_mode = data.get('mainMode') # 'vocab' oder 'irregular' sub_mode = data.get('subMode') # 'de-en', 'start-german', etc. pages = data.get('pages', []) conn = get_db_connection() cur = conn.cursor() if main_mode == 'irregular': # Verben Logik query = "SELECT infinitive, simple_past, past_participle, german, page FROM irregular_verbs WHERE page = ANY(%s) ORDER BY RANDOM() LIMIT 1" cur.execute(query, (pages,)) row = cur.fetchone() cur.close() conn.close() if not row: return jsonify({'error': 'Keine Verben gefunden.'}) # Entscheiden basierend auf sub_mode if sub_mode == 'start-german': # Frage: Deutsch -> Antwort: Alle 3 Formen return jsonify({ 'type': 'irregular_full', 'question': row[3], # Deutsch 'answer_infinitive': row[0], 'answer_simple': row[1], 'answer_participle': row[2], 'page': row[4] }) else: # Frage: Infinitiv -> Antwort: Past & Participle (Standard) return jsonify({ 'type': 'irregular_standard', 'question': row[0], # Infinitive 'german_hint': row[3], 'answer_simple': row[1], 'answer_participle': row[2], 'page': row[4] }) else: # Vokabel Logik query = "SELECT english, german, page FROM vocabulary WHERE page = ANY(%s) ORDER BY RANDOM() LIMIT 1" cur.execute(query, (pages,)) row = cur.fetchone() cur.close() conn.close() if not row: return jsonify({'error': 'Keine Vokabeln gefunden.'}) q_text, a_text = "", "" if sub_mode == 'de-en': q_text, a_text = row[1], row[0] elif sub_mode == 'en-de': q_text, a_text = row[0], row[1] else: # random if random.random() > 0.5: q_text, a_text = row[1], row[0] else: q_text, a_text = row[0], row[1] return jsonify({ 'type': 'vocab', 'question': q_text, 'answer': a_text, 'page': row[2] }) @app.route('/api/check', methods=['POST']) def check_answer(): data = request.json q_type = data.get('type') # 1. Verben: Alles abfragen (Start: Deutsch) if q_type == 'irregular_full': u_inf = clean_text(data.get('infinitive', '')) u_simp = clean_text(data.get('simple', '')) u_part = clean_text(data.get('participle', '')) ok_inf = check_part(u_inf, data.get('correct_infinitive', '')) ok_simp = check_part(u_simp, data.get('correct_simple', '')) ok_part = check_part(u_part, data.get('correct_participle', '')) if ok_inf and ok_simp and ok_part: return jsonify({'status': 'correct', 'msg': 'Perfekt! Alles richtig.'}) else: sol = f"{data['correct_infinitive']} -> {data['correct_simple']} -> {data['correct_participle']}" return jsonify({'status': 'wrong', 'msg': f'Leider falsch. Lösung: {sol}'}) # 2. Verben: Standard (Start: Infinitiv) elif q_type == 'irregular_standard': u_simp = clean_text(data.get('simple', '')) u_part = clean_text(data.get('participle', '')) ok_simp = check_part(u_simp, data.get('correct_simple', '')) ok_part = check_part(u_part, data.get('correct_participle', '')) if ok_simp and ok_part: return jsonify({'status': 'correct', 'msg': 'Richtig!'}) else: sol = f"{data['correct_simple']} -> {data['correct_participle']}" return jsonify({'status': 'wrong', 'msg': f'Lösung: {sol}'}) # 3. Vokabeln else: u_input = clean_text(data.get('input', '')) correct_raw = data.get('correct', '') # Split variants (e.g. "car; auto") valid_answers = [clean_text(x) for x in correct_raw.split(';')] # Add version without brackets "(to) go" -> "go" for v in list(valid_answers): if '(' in v: valid_answers.append(v.replace('(','').replace(')','').strip()) if u_input in valid_answers: return jsonify({'status': 'correct', 'msg': 'Richtig!'}) # Fuzzy Check best_ratio = 0 for v in valid_answers: ratio = SequenceMatcher(None, u_input, v).ratio() if ratio > best_ratio: best_ratio = ratio if best_ratio >= 0.8: return jsonify({'status': 'typo', 'msg': f'Fast richtig! Lösung: {correct_raw}'}) return jsonify({'status': 'wrong', 'msg': f'Falsch. Lösung: {correct_raw}'}) if __name__ == '__main__': app.run(host='0.0.0.0', port=5000)