refactor(frontend): foundation phase 0a — CSS extraction + NSFW persistence + last_error banner

Primeira parte do refactor frontend planejado. Sem mudar funcionalidade
visivel pro usuario, prepara o terreno pra fase 0b (parciais Jinja +
HTMX + Alpine) na proxima iteracao.

Backend foundation
- Persist over_18 do Reddit como coluna `nsfw` em posts (migration
  ALTER TABLE). Antes vinha em RedditPost mas era jogado fora; sem
  isso o NSFW gate da Fase 1 nao tem dado pra operar.
- POST /api/collector/clear-error pra dispensar o banner que mostra
  o `last_error` no header da UI.

CSS extraction (1063 → 0 inline)
- Move o bloco <style> inteiro do index.html (linhas 7-1069) para
  src/web/static/css/app.css. Index ja serve via <link>.
- Adiciona CSS custom properties no :root (cores principais) como
  base para tema; uso opcional, mantem cores hardcoded onde ja
  estavam pra evitar visual diff.
- Novos placeholders .error-banner, .nsfw-thumb, .discreet pra
  destravar Fase 1 sem nova mudanca de CSS.

UI observability
- Banner vermelho discreto no topo da pagina quando
  collector_status.last_error esta preenchido. updateStatus()
  alimenta; botao "Limpar" chama /api/collector/clear-error.
- Novo helper global showApiError(err, fallback) que LE o `detail`
  real do response (Response ou Error). Substitui as 22 chamadas
  de `showAlert('Erro de conexao', 'error')` espalhadas pelos
  handlers. Agora um 500 do server aparece com a mensagem real,
  nao com o generico que escondia o bug do compose :ro.

Resultado: index.html caiu de 3620 para 2575 linhas, sem refactor de
JS/HTML ainda. Cobertura e CI continuam verdes (151 testes).
This commit is contained in:
authentik Default Admin 2026-05-17 19:53:36 +01:00
parent 5682d2a844
commit e4639365a4
6 changed files with 1199 additions and 1089 deletions

View file

@ -7,7 +7,7 @@ packages = ["src"]
[project]
name = "reddit-media-collector"
version = "1.0.3"
version = "1.1.0"
description = "Self-hosted media collector for Reddit with Immich integration"
readme = "README.md"
requires-python = ">=3.11"

View file

@ -38,6 +38,7 @@ class PostRecord:
permalink: str | None = None # Reddit permalink for Immich
source_type: str | None = None # 'subreddit' or 'user'
flair: str | None = None # Post flair for tagging
nsfw: bool = False # Mirrors Reddit's over_18; powers UI NSFW gate
class Database:
@ -66,7 +67,8 @@ class Database:
file_hash TEXT,
permalink TEXT,
source_type TEXT,
flair TEXT
flair TEXT,
nsfw INTEGER DEFAULT 0
)
""")
# Favorites table
@ -84,6 +86,8 @@ class Database:
conn.execute("ALTER TABLE posts ADD COLUMN source_type TEXT")
with suppress(sqlite3.OperationalError):
conn.execute("ALTER TABLE posts ADD COLUMN flair TEXT")
with suppress(sqlite3.OperationalError):
conn.execute("ALTER TABLE posts ADD COLUMN nsfw INTEGER DEFAULT 0")
conn.execute("CREATE INDEX IF NOT EXISTS idx_subreddit ON posts(subreddit)")
conn.execute("CREATE INDEX IF NOT EXISTS idx_file_hash ON posts(file_hash)")
conn.execute("CREATE INDEX IF NOT EXISTS idx_downloaded ON posts(downloaded_at)")
@ -139,8 +143,8 @@ class Database:
INSERT OR REPLACE INTO posts
(id, subreddit, author, title, url, media_url, media_type,
score, created_utc, downloaded_at, local_path, file_hash,
permalink, source_type, flair)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
permalink, source_type, flair, nsfw)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""",
(
post.id,
@ -158,6 +162,7 @@ class Database:
post.permalink,
post.source_type,
post.flair,
1 if post.nsfw else 0,
),
)
conn.commit()
@ -207,6 +212,7 @@ class Database:
permalink=row["permalink"],
source_type=row["source_type"],
flair=row["flair"],
nsfw=bool(row["nsfw"] or 0),
)
def get_all_downloaded(self) -> list[PostRecord]:
@ -232,6 +238,7 @@ class Database:
permalink=row["permalink"],
source_type=row["source_type"],
flair=row["flair"],
nsfw=bool(row["nsfw"] or 0),
)
)
return posts
@ -562,6 +569,7 @@ class Database:
permalink=row["permalink"],
source_type=row["source_type"],
flair=row["flair"],
nsfw=bool(row["nsfw"] or 0),
)
)
return posts
@ -624,6 +632,7 @@ class Database:
permalink=row["permalink"],
source_type=row["source_type"],
flair=row["flair"],
nsfw=bool(row["nsfw"] or 0),
)
)
return posts

View file

@ -199,6 +199,7 @@ def process_post(
permalink=post.permalink,
source_type=source_type,
flair=post.flair,
nsfw=post.over_18,
)
db.add_post(record)
db.mark_downloaded(item_id, local_path, file_hash)

View file

@ -293,6 +293,14 @@ async def get_collector_status():
return collector_status
@router.post("/api/collector/clear-error")
async def clear_collector_error():
"""Clear the last_error banner after the user dismisses it from the UI."""
with collector_lock:
collector_status["last_error"] = None
return {"message": "cleared"}
@router.post("/api/collector/run")
async def trigger_collector(background_tasks: BackgroundTasks):
"""Trigger the collector to run."""

1117
src/web/static/css/app.css Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff