Files
guides/backend/database.py
2026-05-29 17:58:43 +02:00

252 lines
7.8 KiB
Python

import aiosqlite
from config import DB_PATH, STORAGE_DIR
from paths import final_paths
CREATE_GUIDES = """
CREATE TABLE IF NOT EXISTS guides (
id TEXT PRIMARY KEY,
topic TEXT NOT NULL,
format TEXT NOT NULL,
instructions TEXT NOT NULL DEFAULT '',
status TEXT NOT NULL DEFAULT 'queued',
progress TEXT,
error_msg TEXT,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
)
"""
CREATE_BAUSTEINE = """
CREATE TABLE IF NOT EXISTS bausteine (
id TEXT PRIMARY KEY,
topic TEXT NOT NULL,
title TEXT NOT NULL,
description TEXT NOT NULL DEFAULT '',
purpose TEXT NOT NULL DEFAULT '',
example TEXT NOT NULL DEFAULT '',
sort_order INTEGER NOT NULL DEFAULT 0,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
)
"""
CREATE_SUGGESTIONS = """
CREATE TABLE IF NOT EXISTS baustein_suggestions (
id TEXT PRIMARY KEY,
topic TEXT NOT NULL,
title TEXT NOT NULL,
description TEXT NOT NULL DEFAULT '',
purpose TEXT NOT NULL DEFAULT '',
example TEXT NOT NULL DEFAULT '',
status TEXT NOT NULL DEFAULT 'pending',
created_at TEXT NOT NULL
)
"""
_db: aiosqlite.Connection | None = None
async def get_db() -> aiosqlite.Connection:
global _db
if _db is None:
_db = await aiosqlite.connect(DB_PATH)
_db.row_factory = None
return _db
async def init_db():
db = await get_db()
await db.execute(CREATE_GUIDES)
await db.execute(CREATE_BAUSTEINE)
await db.execute(CREATE_SUGGESTIONS)
cursor = await db.execute("PRAGMA table_info(guides)")
columns = {row[1] for row in await cursor.fetchall()}
if "instructions" not in columns:
await db.execute("ALTER TABLE guides ADD COLUMN instructions TEXT NOT NULL DEFAULT ''")
cursor = await db.execute("PRAGMA table_info(bausteine)")
columns = {row[1] for row in await cursor.fetchall()}
if "sort_order" not in columns:
await db.execute("ALTER TABLE bausteine ADD COLUMN sort_order INTEGER NOT NULL DEFAULT 0")
await db.execute(
"UPDATE guides SET status = 'error', progress = NULL, error_msg = 'Server-Neustart' "
"WHERE status IN ('queued', 'generating')"
)
await db.commit()
await _migrate_uuid_filenames(db)
async def _migrate_uuid_filenames(db: aiosqlite.Connection) -> None:
cursor = await db.execute("SELECT id, topic, format FROM guides WHERE status = 'done'")
rows = await cursor.fetchall()
for guide_id, topic, format_name in rows:
final_html, final_pdf = final_paths(topic, format_name)
old_html = STORAGE_DIR / "html" / f"{guide_id}.html"
old_pdf = STORAGE_DIR / "pdf" / f"{guide_id}.pdf"
if old_html.exists() and not final_html.exists():
old_html.rename(final_html)
if old_pdf.exists() and not final_pdf.exists():
old_pdf.rename(final_pdf)
async def close_db():
global _db
if _db is not None:
await _db.close()
_db = None
def _row_to_dict(row, cursor):
columns = [d[0] for d in cursor.description]
return dict(zip(columns, row))
async def create_guide(guide: dict) -> dict:
db = await get_db()
await db.execute(
"""INSERT INTO guides (id, topic, format, instructions, status, progress, created_at, updated_at)
VALUES (:id, :topic, :format, :instructions, :status, :progress, :created_at, :updated_at)""",
guide,
)
await db.commit()
return guide
async def get_guide(guide_id: str) -> dict | None:
db = await get_db()
cursor = await db.execute("SELECT * FROM guides WHERE id = ?", (guide_id,))
row = await cursor.fetchone()
if row is None:
return None
return _row_to_dict(row, cursor)
async def list_guides() -> list[dict]:
db = await get_db()
cursor = await db.execute("SELECT * FROM guides ORDER BY created_at DESC")
rows = await cursor.fetchall()
return [_row_to_dict(row, cursor) for row in rows]
async def update_guide(guide_id: str, **fields) -> None:
sets = ", ".join(f"{k} = :{k}" for k in fields)
fields["id"] = guide_id
db = await get_db()
await db.execute(f"UPDATE guides SET {sets} WHERE id = :id", fields)
await db.commit()
async def delete_guide(guide_id: str) -> bool:
db = await get_db()
cursor = await db.execute("DELETE FROM guides WHERE id = ?", (guide_id,))
await db.commit()
return cursor.rowcount > 0
# --- Bausteine ---
async def create_baustein(baustein: dict) -> dict:
db = await get_db()
await db.execute(
"""INSERT INTO bausteine (id, topic, title, description, purpose, example, created_at, updated_at)
VALUES (:id, :topic, :title, :description, :purpose, :example, :created_at, :updated_at)""",
baustein,
)
await db.commit()
return baustein
async def list_bausteine(topic: str) -> list[dict]:
db = await get_db()
cursor = await db.execute(
"SELECT * FROM bausteine WHERE topic = ? ORDER BY sort_order ASC, created_at ASC", (topic,)
)
rows = await cursor.fetchall()
return [_row_to_dict(row, cursor) for row in rows]
async def update_baustein_sort_orders(topic: str, order_map: dict) -> None:
db = await get_db()
for baustein_id, order in order_map.items():
await db.execute(
"UPDATE bausteine SET sort_order = ? WHERE id = ? AND topic = ?",
(order, baustein_id, topic),
)
await db.commit()
async def get_baustein(baustein_id: str) -> dict | None:
db = await get_db()
cursor = await db.execute("SELECT * FROM bausteine WHERE id = ?", (baustein_id,))
row = await cursor.fetchone()
if row is None:
return None
return _row_to_dict(row, cursor)
async def update_baustein(baustein_id: str, **fields) -> None:
sets = ", ".join(f"{k} = :{k}" for k in fields)
fields["id"] = baustein_id
db = await get_db()
await db.execute(f"UPDATE bausteine SET {sets} WHERE id = :id", fields)
await db.commit()
async def delete_baustein(baustein_id: str) -> bool:
db = await get_db()
cursor = await db.execute("DELETE FROM bausteine WHERE id = ?", (baustein_id,))
await db.commit()
return cursor.rowcount > 0
# --- Baustein Suggestions ---
async def create_suggestions(suggestions: list[dict]) -> None:
db = await get_db()
await db.executemany(
"""INSERT INTO baustein_suggestions (id, topic, title, description, purpose, example, status, created_at)
VALUES (:id, :topic, :title, :description, :purpose, :example, :status, :created_at)""",
suggestions,
)
await db.commit()
async def list_suggestions(topic: str) -> list[dict]:
db = await get_db()
cursor = await db.execute(
"SELECT * FROM baustein_suggestions WHERE topic = ? ORDER BY created_at ASC", (topic,)
)
rows = await cursor.fetchall()
return [_row_to_dict(row, cursor) for row in rows]
async def get_suggestion(suggestion_id: str) -> dict | None:
db = await get_db()
cursor = await db.execute("SELECT * FROM baustein_suggestions WHERE id = ?", (suggestion_id,))
row = await cursor.fetchone()
if row is None:
return None
return _row_to_dict(row, cursor)
async def update_suggestion(suggestion_id: str, **fields) -> None:
sets = ", ".join(f"{k} = :{k}" for k in fields)
fields["id"] = suggestion_id
db = await get_db()
await db.execute(f"UPDATE baustein_suggestions SET {sets} WHERE id = :id", fields)
await db.commit()
async def delete_suggestion(suggestion_id: str) -> bool:
db = await get_db()
cursor = await db.execute("DELETE FROM baustein_suggestions WHERE id = ?", (suggestion_id,))
await db.commit()
return cursor.rowcount > 0
async def delete_pending_suggestions(topic: str) -> None:
db = await get_db()
await db.execute(
"DELETE FROM baustein_suggestions WHERE topic = ? AND status = 'pending'", (topic,)
)
await db.commit()