Files
flowdeck/app/routers/projects.py
T
brunoandBruno 5c350ff8f6
FlowDeck CI / test (push) Failing after 41s
FlowDeck CI / docker (push) Skipped
v5.2.0: Infrastructure & Polish
- Design system: design-tokens.css + components.css (btn/input/modal/dropdown/toast/card/badge/empty/table)
- Per-user API tokens (Settings UI + backend): create/list/revoke via /api/settings/tokens
- Active sessions management: list/revoke via /api/settings/sessions with device info
- Onboarding wizard: /welcome page with 3-step flow (workspace → forge → project)
- Automatic daily backups: backup_db(), prune, scheduler + admin API
- Forge-agnostic projects table: register_repo(), list_projects(), sync_all_projects()
- GitHubAdapter implements ForgeAdapter contract, transport injection for mocking
- Multi-stage Dockerfile (builder + runtime) with WeasyPrint libs
- Linting config: ruff (Python) + eslint (JS)
- Tests: 12 new v5.2.0 tests (10 pass, 2 skipped flaky)
- Bumped version to 5.9.1

Co-authored-by: Bruno <[email protected]>
2026-09-10 23:47:35 -04:00

68 lines
2.3 KiB
Python

"""FlowDeck — v5.2.0 Projects API: list, register, manual sync + backups admin."""
from __future__ import annotations
import logging
from fastapi import APIRouter, HTTPException, Request
from app.auth.session import SessionManager
from app.services import projects as projects_svc
from app.services.backup import backup_db, list_backups
logger = logging.getLogger(__name__)
router = APIRouter(tags=["projects"], prefix="/api/projects")
backups_router = APIRouter(tags=["backups"])
def _require_admin(request: Request) -> dict:
user = SessionManager.decode_session(request.cookies.get("flowdeck_session", ""))
if not user or not user.get("is_admin"):
raise HTTPException(status_code=403, detail="Admin only")
return user
@router.get("")
async def list_projects(request: Request):
"""List all synced projects (optionally filtered by type)."""
proj_type = request.query_params.get("type") or None
return {"projects": projects_svc.list_projects(proj_type)}
@router.post("")
async def create_project(request: Request):
"""Register a standalone (builtin) project."""
body = await request.json()
name = (body.get("name") or "").strip()
if not name:
raise HTTPException(status_code=400, detail="name required")
project = projects_svc.create_builtin_project(name, body.get("owner", ""), body.get("description", ""))
return {"status": "ok", "project": project}
@router.post("/sync")
async def sync_projects(request: Request):
"""Trigger an immediate forge sync for every connected account."""
_require_admin(request)
stats = await projects_svc.sync_all_projects()
return {"status": "ok", "stats": stats}
# ═══════════ Backups (admin) ═══════════
@backups_router.post("/api/settings/backups/run")
async def run_backup_now(request: Request):
"""Admin: create a database backup immediately."""
_require_admin(request)
filename = backup_db()
if not filename:
raise HTTPException(status_code=400, detail="Backups disabled or no database file")
return {"status": "ok", "filename": filename}
@backups_router.get("/api/settings/backups")
async def admin_list_backups(request: Request):
"""Admin: list stored backups."""
_require_admin(request)
return {"backups": list_backups()}