157 lines
5.4 KiB
Python
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}
|