diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..26d3352
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,3 @@
+# Default ignored files
+/shelf/
+/workspace.xml
diff --git a/.idea/Lotto2PY.iml b/.idea/Lotto2PY.iml
new file mode 100644
index 0000000..8388dbc
--- /dev/null
+++ b/.idea/Lotto2PY.iml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml
new file mode 100644
index 0000000..105ce2d
--- /dev/null
+++ b/.idea/inspectionProfiles/profiles_settings.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..812ab5a
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..ecc9268
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..35eb1dd
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/6aus49APP/app.py b/6aus49APP/app.py
new file mode 100644
index 0000000..f5cb354
--- /dev/null
+++ b/6aus49APP/app.py
@@ -0,0 +1,291 @@
+# /opt/lotto/app.py
+import os
+from datetime import date, datetime
+from typing import List, Optional
+
+from fastapi import FastAPI, Query, Request, HTTPException
+from fastapi.responses import HTMLResponse
+from fastapi.staticfiles import StaticFiles
+from fastapi.templating import Jinja2Templates
+from pydantic import BaseModel
+from sqlalchemy import create_engine, text
+from sqlalchemy.engine import Engine, Row
+from sqlalchemy.pool import NullPool
+from dotenv import load_dotenv
+
+# --------------------------------------------------------
+# 0) Konfiguration laden
+# --------------------------------------------------------
+load_dotenv()
+
+DATABASE_URL = os.getenv("DATABASE_URL")
+if not DATABASE_URL:
+ raise RuntimeError("DATABASE_URL nicht gesetzt (.env prüfen)")
+
+PAGE_SIZE = int(os.getenv("PAGE_SIZE", "10"))
+
+# --------------------------------------------------------
+# 1) App und DB initialisieren
+# --------------------------------------------------------
+engine: Engine = create_engine(DATABASE_URL, poolclass=NullPool, future=True)
+
+BASE_DIR = os.path.dirname(os.path.abspath(__file__))
+STATIC_DIR = os.path.join(BASE_DIR, "static")
+TEMPLATE_DIR = os.path.join(BASE_DIR, "templates")
+
+app = FastAPI(title="Lotto Ziehungen API")
+app.mount("/static", StaticFiles(directory=STATIC_DIR), name="static")
+templates = Jinja2Templates(directory=TEMPLATE_DIR)
+
+# --------------------------------------------------------
+# 2) Modelle (Pydantic)
+# --------------------------------------------------------
+class Draw(BaseModel):
+ datum: Optional[date]
+ z1: Optional[int]
+ z2: Optional[int]
+ z3: Optional[int]
+ z4: Optional[int]
+ z5: Optional[int]
+ z6: Optional[int]
+ sz: Optional[int]
+ sz1: Optional[int]
+ sz2: Optional[int]
+ super6: Optional[str]
+ spiel77: Optional[str]
+
+class DrawList(BaseModel):
+ total: int
+ items: List[Draw]
+
+# --------------------------------------------------------
+# 3) Hilfsfunktionen
+# --------------------------------------------------------
+def normalize_date_sql(column: str) -> str:
+ return (
+ f"COALESCE("
+ f" (CASE WHEN {column} REGEXP '^[0-9]{{4}}-[0-9]{{2}}-[0-9]{{2}}$' THEN DATE({column}) END),"
+ f" STR_TO_DATE({column}, '%d.%m.%Y'),"
+ f" STR_TO_DATE(SUBSTRING_INDEX({column}, '/', -1), '%d.%m.%y')"
+ f")"
+ )
+
+def row_to_draw(row: Row) -> Draw:
+ return Draw(**row)
+
+def _to_date(s: Optional[str]) -> Optional[date]:
+ if not s:
+ return None
+ t = s.strip()
+ if not t:
+ return None
+ try:
+ return date.fromisoformat(t)
+ except ValueError:
+ pass
+ for fmt in ("%d.%m.%Y", "%d.%m.%y"):
+ try:
+ return datetime.strptime(t, fmt).date()
+ except ValueError:
+ continue
+ return None
+
+# --------------------------------------------------------
+# 4) Routen
+# --------------------------------------------------------
+@app.get("/", response_class=HTMLResponse)
+async def index(request: Request):
+ return templates.TemplateResponse(
+ "index.html", {"request": request, "page_size": PAGE_SIZE}
+ )
+
+# -------------------- API: Ziehungen (JSON) ---------------------
+@app.get("/api/draws", response_model=DrawList)
+async def list_draws(
+ game: str = Query("6aus49", pattern="^(6aus49|euro)$"),
+ date_from: Optional[date] = Query(None),
+ date_to: Optional[date] = Query(None),
+ limit: int = Query(PAGE_SIZE, ge=1, le=500),
+ offset: int = Query(0, ge=0),
+ order: str = Query("desc", pattern="^(asc|desc)$"),
+):
+ tbl = "`6aus49`" if game == "6aus49" else "`euro`"
+ dx = normalize_date_sql(f"{tbl}.datum")
+
+ if game == "6aus49":
+ base_select = f"""
+ SELECT {dx} AS datum,
+ z1, z2, z3, z4, z5, z6, sz,
+ CAST(super6 AS CHAR) AS super6,
+ CAST(spiel77 AS CHAR) AS spiel77,
+ NULL AS sz1, NULL AS sz2
+ FROM {tbl}
+ """
+ else:
+ base_select = f"""
+ SELECT {dx} AS datum,
+ z1, z2, z3, z4, z5,
+ NULL AS z6, NULL AS sz,
+ NULL AS super6, NULL AS spiel77,
+ sz1, sz2
+ FROM {tbl}
+ """
+
+ where_parts = []
+ params = {}
+ if date_from:
+ where_parts.append("datum >= :date_from")
+ params["date_from"] = date_from
+ if date_to:
+ where_parts.append("datum <= :date_to")
+ params["date_to"] = date_to
+ where_sql = "WHERE " + " AND ".join(where_parts) if where_parts else ""
+ order_sql = "ORDER BY datum DESC" if order == "desc" else "ORDER BY datum ASC"
+
+ count_sql = f"SELECT COUNT(*) AS cnt FROM ({base_select}) AS t {where_sql}"
+ data_sql = f"SELECT * FROM ({base_select}) AS t {where_sql} {order_sql} LIMIT :limit OFFSET :offset"
+
+ with engine.begin() as conn:
+ total = conn.execute(text(count_sql), params).scalar_one()
+ rows = conn.execute(text(data_sql), {**params, "limit": limit, "offset": offset}).mappings().all()
+ items = [row_to_draw(r) for r in rows]
+
+ return DrawList(total=total, items=items)
+
+# -------------------- API: Detail (JSON) ------------------------
+@app.get("/api/draw/{game}/{draw_date}", response_model=Draw)
+async def get_draw(game: str, draw_date: date):
+ tbl = "`6aus49`" if game == "6aus49" else "`euro`"
+ dx = normalize_date_sql(f"{tbl}.datum")
+
+ if game == "6aus49":
+ sql = text(f"""
+ SELECT {dx} AS datum,
+ z1, z2, z3, z4, z5, z6, sz,
+ CAST(super6 AS CHAR) AS super6,
+ CAST(spiel77 AS CHAR) AS spiel77,
+ NULL AS sz1, NULL AS sz2
+ FROM {tbl}
+ WHERE {dx} = :d
+ """)
+ else:
+ sql = text(f"""
+ SELECT {dx} AS datum,
+ z1, z2, z3, z4, z5,
+ NULL AS z6, NULL AS sz,
+ NULL AS super6, NULL AS spiel77,
+ sz1, sz2
+ FROM {tbl}
+ WHERE {dx} = :d
+ """)
+
+ with engine.begin() as conn:
+ row = conn.execute(sql, {"d": draw_date}).mappings().first()
+ if not row:
+ raise HTTPException(status_code=404, detail="Ziehung nicht gefunden")
+ return row_to_draw(row)
+
+# -------------------- UI / HTMX (HTML) -------------------------
+@app.get("/ui/draws", response_class=HTMLResponse)
+async def ui_draws(
+ request: Request,
+ game: str = Query("6aus49", pattern="^(6aus49|euro)$"),
+ date_from: Optional[str] = Query(None),
+ date_to: Optional[str] = Query(None),
+ limit: int = Query(PAGE_SIZE, ge=1, le=500),
+ offset: int = Query(0, ge=0),
+ order: str = Query("desc", pattern="^(asc|desc)$"),
+):
+ d_from = _to_date(date_from)
+ d_to = _to_date(date_to)
+
+ result = await list_draws(game, d_from, d_to, limit, offset, order) # type: ignore
+
+ rows_html = []
+ if game == "6aus49":
+ header = (
+ "
Datum
Zahlen
Superzahl
Super 6
Spiel 77
"
+ )
+ for r in result.items:
+ numbers = " ".join(
+ f"{getattr(r, f'z{i}')}"
+ for i in range(1, 7)
+ if getattr(r, f"z{i}") is not None
+ )
+ sz_html = "" if r.sz is None else r.sz
+ rows_html.append(
+ f"
{r.datum or ''}
{numbers}
"
+ f"
{sz_html}
{r.super6 or ''}
{r.spiel77 or ''}
"
+ )
+ else:
+ header = "
Datum
Zahlen
Super 1
Super 2
"
+ for r in result.items:
+ numbers = " ".join(
+ f"{getattr(r, f'z{i}')}"
+ for i in range(1, 6)
+ if getattr(r, f"z{i}") is not None
+ )
+ sz1_html = "" if r.sz1 is None else r.sz1
+ sz2_html = "" if r.sz2 is None else r.sz2
+ rows_html.append(
+ f"