feat(v4.0): Library tabs + Sidebar OAuth badge + Sharing routes
FlowDeck CI / test (push) Failing after 4s
FlowDeck CI / docker (push) Has been skipped

app/routers/library.py: /api/library/recents|favorites|shared|published|private|workspace
app/routers/sharing.py: /api/pages/{id}/share|publish + page_shares
app/templates/base.html: OAuth badge 🦎/🐙, '→ Library' buttons
app/templates/library.html: page avec tabs + filtres source
app/routers/dashboard.py: auth_method + github_linked dans context
tests/test_app.py: tests library + sharing + recents
This commit is contained in:
2026-07-14 12:16:28 -04:00
parent 802d681212
commit bd6fd62734
6 changed files with 368 additions and 174 deletions
+25 -1
View File
@@ -303,13 +303,37 @@ def _sidebar_data(request: Request, owner: str = "", repo: str = "") -> dict:
"children": [],
})
# Auth method & OAuth badge data
auth_method = "local"
gitea_linked = False
github_linked = False
if user and user.get("id"):
try:
with get_conn() as conn:
am_row = conn.execute("SELECT auth_method FROM users WHERE id=?", (user["id"],)).fetchone()
if am_row and am_row["auth_method"]:
auth_method = am_row["auth_method"]
tokens = conn.execute(
"SELECT provider FROM user_oauth_tokens WHERE user_id=?", (user["id"],)
).fetchall()
for t in tokens:
if t["provider"] == "gitea":
gitea_linked = True
elif t["provider"] == "github":
github_linked = True
except Exception:
pass
return {"workspace_name": ws_name, "workspace_initial": ws_name[0].upper() if ws_name else "B",
"active_ws_name": active_ws_name,
"workspace_pages": _load_workspace_pages(ws_cookie),
"current_page": repo or "Dashboard", "last_edited": "now",
"recent_pages": recent, "private_pages": private_items,
"favorite_pages": favorites, "shared_pages": shared_pages,
"user": user, "workspace_key": ws_key}
"user": user, "workspace_key": ws_key,
"auth_method": auth_method,
"gitea_linked": gitea_linked,
"github_linked": github_linked}
def _extract_ai_keywords(owner: str, repo: str, labels: list[dict], body: str = ""):
+4 -65
View File
@@ -157,75 +157,14 @@ async def trash_page(request: Request, owner: str = Query(default=""), repo: str
@router.get("/library", response_class=HTMLResponse)
async def library_page(request: Request, owner: str = Query(default=""), repo: str = Query(default="")):
"""Library page avec onglets Recents, Favorites, Shared, Private — scopé au workspace.
La sidebar garde exactement la même arborescence que dans le workspace.
Seul le contenu principal change pour afficher les pages triées par catégorie.
"""Library page — tabbed view (recents, favorites, shared, published, private, workspace).
Sidebar data is kept intact. Tab content is loaded client-side via /api/library/* endpoints.
"""
from jinja2 import Environment, FileSystemLoader
from app.routers.board import _sidebar_data as board_sidebar
env = Environment(loader=FileSystemLoader("app/templates"))
sidebar = board_sidebar(request, owner, repo) # Sidebar intacte, inchangée
ws_key = f"{owner}/{repo}" if owner and repo else ""
with get_conn() as conn:
base_query = (
"SELECT id, title, workspace, parent_section, updated_at FROM pages "
"WHERE parent_section != 'Trash'"
)
params = []
if ws_key:
base_query += " AND workspace=?"
params.append(ws_key)
base_query += " ORDER BY updated_at DESC"
rows = conn.execute(base_query, params).fetchall()
def to_page(r):
r = dict(r)
return {
"id": f"page/{r['id']}",
"name": r["title"] or "Untitled",
"icon": "📄",
"url": f"/pages/{r['id']}",
"created_by": "You",
"source": r.get("workspace") or "🔒 Private",
"last_edited": r.get("updated_at", "now"),
"last_visited": r.get("updated_at", "now"),
"section": r.get("parent_section", "Private"),
}
all_pages = [to_page(r) for r in rows]
# Load favorites from DB for Library
user = SessionManager.decode_session(request.cookies.get("flowdeck_session", ""))
uid = user["id"] if user and user.get("id") else 1
with get_conn() as conn:
fav_rows = conn.execute(
"SELECT p.id, p.title, p.workspace, p.updated_at FROM favorites f "
"JOIN pages p ON p.id = f.page_id "
"WHERE f.user_id=? ORDER BY f.position", (uid,)
).fetchall()
lib_favs = []
for r in fav_rows:
r = dict(r)
lib_favs.append({
"id": f"page/{r['id']}",
"name": r["title"] or "Untitled",
"icon": "📄",
"url": f"/pages/{r['id']}",
"created_by": "You",
"source": r.get("workspace") or "🔒 Private",
"last_edited": r.get("updated_at", "now"),
"last_visited": r.get("updated_at", "now"),
})
# Catégoriser par parent_section — sidebar data intacte
sidebar["lib_recent"] = all_pages
sidebar["lib_favorites"] = lib_favs
sidebar["lib_shared"] = []
sidebar["lib_private"] = [p for p in all_pages if p.get("section") == "Private"]
sidebar["lib_has_favorites"] = len(lib_favs) > 0
sidebar["lib_has_shared"] = False
sidebar = board_sidebar(request, owner, repo)
template = env.get_template("library.html")
return template.render(**sidebar)