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:
parent
5682d2a844
commit
e4639365a4
6 changed files with 1199 additions and 1089 deletions
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
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
Loading…
Add table
Add a link
Reference in a new issue