fix: avatars persistant — route fichier + couleur en DB + sidebar affichage
FlowDeck CI / test (push) Failing after 4s
FlowDeck CI / docker (push) Has been skipped

- Route manquante GET /api/settings/avatar/{filename} → sert le fichier uploadé
  (résout le 404 sur l'image uploadée)
- Colonne avatar_color ajoutée à users (migration ALTER TABLE try/except)
- set_avatar_color sauvegarde la couleur dans la DB (pas seulement local)
- _sidebar_data() lit avatar_url + avatar_color depuis la DB
- Sidebar avatar: fond coloré quand pas d'image, image quand uploadée
  avec background-image:url() + initiale en fallback
This commit is contained in:
2026-07-12 16:38:21 -04:00
parent 4668eb77ed
commit 14ad34c886
3 changed files with 35 additions and 4 deletions
+6
View File
@@ -27,6 +27,7 @@ def init_db():
full_name TEXT NOT NULL DEFAULT '',
email TEXT NOT NULL DEFAULT '',
avatar_url TEXT NOT NULL DEFAULT '',
avatar_color TEXT NOT NULL DEFAULT '#3A3A3A',
password_hash TEXT,
is_admin INTEGER NOT NULL DEFAULT 0,
is_active INTEGER NOT NULL DEFAULT 1,
@@ -316,6 +317,11 @@ def init_db():
conn.execute("ALTER TABLE pages ADD COLUMN parent_id INTEGER REFERENCES pages(id)")
except sqlite3.OperationalError:
pass
# Migration: add avatar_color (v2.3.0+)
try:
conn.execute("ALTER TABLE users ADD COLUMN avatar_color TEXT NOT NULL DEFAULT '#3A3A3A'")
except sqlite3.OperationalError:
pass
# Migration: add sort_order for drag & drop tree reordering (v1.1.0+)
try:
conn.execute("ALTER TABLE pages ADD COLUMN sort_order INTEGER NOT NULL DEFAULT 0")
+28 -3
View File
@@ -36,6 +36,19 @@ def _sidebar_data(request: Request, repos: list[dict]) -> dict:
ws = user.get("login", "Bruno") if user else "Bruno"
initial = ws[0].upper() if ws else "B"
# Get avatar info from DB
avatar_url = ""
avatar_color = "#3A3A3A"
if user:
try:
with get_conn() as conn:
row = conn.execute("SELECT avatar_url, avatar_color FROM users WHERE id = ?", (user["id"],)).fetchone()
if row:
avatar_url = row["avatar_url"] or ""
avatar_color = row["avatar_color"] or "#3A3A3A"
except Exception:
pass
recent_pages = []
for repo in repos[:10]:
full_name = repo.get("full_name", "")
@@ -76,6 +89,8 @@ def _sidebar_data(request: Request, repos: list[dict]) -> dict:
"favorite_pages": [],
"shared_pages": [],
"user": user,
"avatar_url": avatar_url,
"avatar_color": avatar_color,
"workspace_key": "",
}
@@ -1125,6 +1140,17 @@ async def upload_avatar(request: Request):
return {"avatar_url": avatar_url}
@router.get("/api/settings/avatar/{filename:path}")
async def serve_avatar_file(filename: str):
"""Serve an uploaded avatar image file."""
from fastapi.responses import FileResponse
from pathlib import Path
filepath = Path("/data/avatars") / filename
if not filepath.is_file():
return JSONResponse({"error": "Not found"}, status_code=404)
return FileResponse(filepath)
@router.get("/api/avatar/{user_id:int}")
async def get_avatar(user_id: int):
"""Redirect to the user's avatar."""
@@ -1137,15 +1163,14 @@ async def get_avatar(user_id: int):
@router.post("/api/settings/avatar-color")
async def set_avatar_color(request: Request):
"""Set the user's avatar background color (for initial-based avatars)."""
import json
"""Set the user's avatar background color."""
body = await request.json()
color = body.get("color", "#3A3A3A")
user = SessionManager.decode_session(request.cookies.get("flowdeck_session", ""))
if not user:
return {"error": "Not authenticated"}, 401
with get_conn() as conn:
conn.execute("UPDATE users SET avatar_url = '' WHERE id = ?", (user["id"],))
conn.execute("UPDATE users SET avatar_url = '', avatar_color = ? WHERE id = ?", (color, user["id"]))
conn.commit()
return {"status": "ok", "color": color}
+1 -1
View File
@@ -65,7 +65,7 @@
<div class="sidebar-workspace-header" @click="workspaceMenuOpen = !workspaceMenuOpen" @mouseenter="wsHeaderHover=true" @mouseleave="wsHeaderHover=false"
:class="{ hover: wsHeaderHover || workspaceMenuOpen }">
<div class="workspace-info">
<div class="workspace-avatar">{{ workspaceInitial }}</div>
<div class="workspace-avatar" style="{% if avatar_url %}background-image:url('{{ avatar_url }}');background-size:cover;{% else %}background:{{ avatar_color }};{% endif %}">{% if not avatar_url %}{{ user.full_name[:1].upper() if user and user.get('full_name') else workspaceInitial }}{% endif %}</div>
<span class="workspace-name">{{ user.full_name or user.login or workspaceName }}</span>
</div>
<span class="workspace-chevron" :class="{ open: workspaceMenuOpen }">▾</span>