novela/containers/novela/routers/settings.py
2026-04-15 21:39:20 +02:00

157 lines
5.4 KiB
Python

import re
from fastapi import APIRouter, Request, UploadFile, File
from fastapi.responses import HTMLResponse
from shared_templates import templates
from db import get_db_conn
from epub import detect_image_format
from routers.common import write_image_file
router = APIRouter()
@router.get("/settings", response_class=HTMLResponse)
async def settings_page(request: Request):
return templates.TemplateResponse(request, "settings.html", {"active": "settings"})
@router.get("/api/break-patterns")
async def get_break_patterns():
with get_db_conn() as conn:
with conn.cursor() as cur:
cur.execute(
"SELECT id, pattern_type, pattern, enabled, is_default FROM break_patterns ORDER BY id"
)
return [
{
"id": r[0],
"pattern_type": r[1],
"pattern": r[2],
"enabled": r[3],
"is_default": r[4],
}
for r in cur.fetchall()
]
@router.post("/api/break-patterns")
async def add_break_pattern(request: Request):
body = await request.json()
ptype = (body.get("pattern_type") or "").strip()
pattern = (body.get("pattern") or "").strip()
if ptype not in ("regex", "css_class") or not pattern:
return {"error": "Invalid input"}
if ptype == "regex":
try:
re.compile(pattern)
except re.error as e:
return {"error": f"Invalid regex: {e}"}
with get_db_conn() as conn:
with conn:
with conn.cursor() as cur:
cur.execute(
"""
INSERT INTO break_patterns (pattern_type, pattern)
VALUES (%s, %s)
ON CONFLICT (pattern_type, pattern) DO NOTHING
RETURNING id
""",
(ptype, pattern),
)
row = cur.fetchone()
if not row:
return {"error": "Pattern already exists"}
return {"ok": True, "id": row[0]}
@router.patch("/api/break-patterns/{pid}")
async def update_break_pattern(pid: int, request: Request):
body = await request.json()
with get_db_conn() as conn:
with conn:
with conn.cursor() as cur:
if "enabled" in body:
cur.execute("UPDATE break_patterns SET enabled = %s WHERE id = %s", (bool(body["enabled"]), pid))
if "pattern" in body:
new_pat = (body.get("pattern") or "").strip()
cur.execute("SELECT pattern_type FROM break_patterns WHERE id = %s", (pid,))
row = cur.fetchone()
if row and row[0] == "regex":
try:
re.compile(new_pat)
except re.error as e:
return {"error": f"Invalid regex: {e}"}
cur.execute("UPDATE break_patterns SET pattern = %s WHERE id = %s", (new_pat, pid))
return {"ok": True}
@router.delete("/api/break-patterns/{pid}")
async def delete_break_pattern(pid: int):
with get_db_conn() as conn:
with conn:
with conn.cursor() as cur:
cur.execute("DELETE FROM break_patterns WHERE id = %s", (pid,))
return {"ok": True}
@router.get("/api/app-settings")
async def get_app_settings():
with get_db_conn() as conn:
with conn.cursor() as cur:
cur.execute("SELECT develop_mode, break_image_sha256, break_image_ext FROM app_settings WHERE id = 1")
row = cur.fetchone()
if not row:
return {"develop_mode": False, "break_image_url": None}
sha, ext = row[1], row[2]
break_image_url = f"/library/db-images/{sha[:2]}/{sha}{ext}" if sha and ext else None
return {"develop_mode": bool(row[0]), "break_image_url": break_image_url}
@router.patch("/api/app-settings")
async def update_app_settings(request: Request):
body = await request.json()
with get_db_conn() as conn:
with conn:
with conn.cursor() as cur:
if "develop_mode" in body:
cur.execute(
"UPDATE app_settings SET develop_mode = %s WHERE id = 1",
(bool(body["develop_mode"]),),
)
return {"ok": True}
@router.post("/api/app-settings/break-image")
async def upload_break_image(file: UploadFile = File(...)):
data = await file.read()
if not data:
return {"error": "Empty file"}
_, media_type = detect_image_format(data, file.filename or "break")
sha, ext, _ = write_image_file(data, media_type)
# Also write to static/break.png so EPUB embeds the same image
try:
with open("static/break.png", "wb") as f:
f.write(data)
except Exception:
pass
with get_db_conn() as conn:
with conn:
with conn.cursor() as cur:
cur.execute(
"UPDATE app_settings SET break_image_sha256 = %s, break_image_ext = %s WHERE id = 1",
(sha, ext),
)
url = f"/library/db-images/{sha[:2]}/{sha}{ext}"
return {"ok": True, "url": url}
@router.delete("/api/reading-history")
async def reset_reading_history():
with get_db_conn() as conn:
with conn:
with conn.cursor() as cur:
cur.execute("DELETE FROM reading_sessions")
return {"ok": True}