fix: avatars persistant — route fichier + couleur en DB + sidebar affichage
- 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:
@@ -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")
|
||||
|
||||
@@ -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}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user