# Architecture FlowDeck — Document Complet v4.0 > **Version:** 4.0 · **Date:** 2026-07-20 · **Auteur:** Hermes-Deepin > **Cible:** Clone Notion intégré à Gitea/GitHub — multi-comptes, multi-intégrations > **Version déployée:** v4.0.x (production : https://flowdeck.dracodev.net) --- ## Table des matières 1. [Vue d'ensemble](#1-vue-densemble) 2. [Architecture système](#2-architecture-système) 3. [Modèle de données](#3-modèle-de-données) 4. [Routage & API](#4-routage--api) 5. [Frontend](#5-frontend) 6. [Système d'authentification](#6-système-dauthentification) 7. [Intégration Gitea](#7-intégration-gitea) 8. [Système de propriétés](#8-système-de-propriétés) 9. [Système de vues](#9-système-de-vues) 10. [Sub-items & Dépendances](#10-sub-items--dépendances) 11. [My Tasks & Dashboard unifié](#11-my-tasks--dashboard-unifié) 12. [Éditeur de blocs](#12-éditeur-de-blocs) 13. [Multi-utilisateurs & Collaboratif](#13-multi-utilisateurs--collaboratif) 14. [Déploiement](#14-déploiement) 15. [Références](#15-références) --- ## 1. Vue d'ensemble FlowDeck est un **clone de Notion** intégré à Gitea. Il recrée l'expérience Notion — databases, propriétés typées, vues multiples, sub-items, dépendances, éditeur de blocs — tout en restant connecté aux repositories et issues Gitea. ### Principes architecturaux - **Serveur-side rendering** — Jinja2 + HTMX, pas de SPA lourde - **SQLite-first** — un fichier, zéro maintenance, migration PostgreSQL triviale si scaling futur - **Gitea-native** — chaque database peut être liée à un repo Gitea, les issues deviennent des pages - **Progressive enhancement** — fonctionnel sans JS, enrichi avec Alpine.js + SortableJS - **Propriétés en JSON** — flexibilité maximale du schéma sans migration de table --- ## 2. Architecture système ``` ┌──────────────────────────────────────────────────────────────────┐ │ CLIENT (Browser) │ │ │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ Templates Jinja2 (SSR) │ │ │ │ ├─ base.html — Layout Notion commun (sidebar) │ │ │ │ ├─ _header.html — Topbar + breadcrumbs │ │ │ │ ├─ _workspace_tree_macro.html — Macro arbre sidebar │ │ │ │ ├─ landing.html — Landing page non-auth │ │ │ │ ├─ workspaces.html — Page Workspaces (locale/Gitea) │ │ │ │ ├─ library.html — Bibliothèque multi-onglets │ │ │ │ ├─ local_workspace.html — Workspace local (explorer) │ │ │ │ ├─ page_editor.html — Éditeur de blocs │ │ │ │ ├─ trash.html — Corbeille (local only) │ │ │ │ ├─ settings.html — Settings / My Account / Admin │ │ │ │ ├─ accounts.html — Gestion comptes admin │ │ │ │ ├─ board.html — Kanban (wrapper) │ │ │ │ ├─ board_fragment.html — Cartes Kanban │ │ │ │ ├─ table_view.html — Vue table │ │ │ │ ├─ login.html — Login/Register (local + OAuth) │ │ │ │ ├─ public_page.html — Page publiée (/p/slug) │ │ │ │ └─ (legacy: dashboard, detailed_board, card_detail, etc.) │ │ │ └─────────────────────────────────────────────────────────┘ │ │ │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ JavaScript (servi localement, pas de CDN externe) │ │ │ │ ├─ Alpine.js 3.14 — Interactivité légère │ │ │ │ ├─ HTMX 1.9 — AJAX sans JS (legacy) │ │ │ │ ├─ SortableJS 1.15 — Drag & drop │ │ │ │ └─ Prism.js — Syntax highlighting │ │ │ └─────────────────────────────────────────────────────────┘ │ │ │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ CSS custom — Design system Notion, dark theme, 31+ KB │ │ │ └─────────────────────────────────────────────────────────┘ │ └──────────────────────────┬───────────────────────────────────────┘ │ HTTP (HTML fragments / JSON) ┌──────────────────────────▼───────────────────────────────────────┐ │ FASTAPI (Python 3.11+) │ │ │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ MIDDLEWARE │ │ │ │ ├─ SessionMiddleware — Sessions utilisateur │ │ │ │ ├─ CSRFMiddleware — Protection CSRF │ │ │ │ ├─ CORSMiddleware — CORS (* pour dev local) │ │ │ │ └─ RateLimiter — Rate limiting par IP │ │ │ └─────────────────────────────────────────────────────────┘ │ │ │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ ROUTERS │ │ │ │ ├─ main.py — FastAPI app, lifespan, CORS, 404 │ │ │ │ ├─ dashboard.py — /, /workspaces, /library, /help │ │ │ │ │ — /trash, /settings, /accounts │ │ │ │ │ — /p/{slug} (pages publiques) │ │ │ │ │ — _sidebar_data() — données sidebar │ │ │ │ ├─ board.py — /board/{o}/{r} Board + vues │ │ │ │ │ — /board/api/pages CRUD pages │ │ │ │ │ — /board/api/trash Trash │ │ │ │ ├─ api.py — /api/... CRUD + sync │ │ │ │ ├─ auth.py — /auth/... OAuth2 + local │ │ │ │ ├─ local_workspace.py— /api/local-workspace CRUD + upload │ │ │ │ ├─ my_tasks.py — /my-tasks Dashboard │ │ │ │ ├─ library.py — /api/library/* API bibliothèque│ │ │ │ ├─ pages.py — /pages/... Pages CRUD │ │ │ │ ├─ collections.py — /db/... Collections │ │ │ │ ├─ editor.py — /api/editor/... Block editor │ │ │ │ ├─ public_api.py — /api/v1 Public API v1 │ │ │ │ ├─ api_v2.py — /api/v2 Public API v2 │ │ │ │ │ — Bearer + scopes, CRUD complet │ │ │ │ ├─ web_clipper.py — /api/v2/web-clipper Web Clipper │ │ │ │ ├─ permissions.py — /api/v2 (ACL) Permissions │ │ │ │ ├─ workspace.py — Workspaces API + Gitea projets │ │ │ │ ├─ webhooks.py — /webhooks/... Gitea hooks │ │ │ │ └─ admin.py — /api/admin/* Admin users │ │ │ └─────────────────────────────────────────────────────────┘ │ │ │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ SERVICES │ │ │ │ ├─ GiteaClient (httpx async) — API Gitea │ │ │ │ ├─ FormulaEngine — Moteur de formules │ │ │ │ ├─ RollupEngine — Agrégations cross-DB │ │ │ │ ├─ CollectionSync — Sync Gitea ↔ collections │ │ │ │ ├─ DependencyChecker — Contraintes dépendances │ │ │ │ ├─ PermissionManager — ACL multi-user │ │ │ │ └─ SearchEngine — FTS5 recherche full-text │ │ │ └─────────────────────────────────────────────────────────┘ │ │ │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ DATA LAYER │ │ │ │ ┌─────────────────────────────────────────────────┐ │ │ │ │ │ SQLite (WAL mode, foreign keys ON) │ │ │ │ │ │ │ │ │ │ │ │ ┌─ collections (v1.3) │ │ │ │ │ │ ├─ collection_pages (v1.3) │ │ │ │ │ │ ├─ collection_views (v1.3) │ │ │ │ │ │ ├─ collection_properties (v1.4) │ │ │ │ │ │ ├─ boards (legacy) │ │ │ │ │ │ ├─ cards (legacy) │ │ │ │ │ │ ├─ col_mapping (legacy) │ │ │ │ │ │ ├─ notes │ │ │ │ │ │ ├─ checklists │ │ │ │ │ │ ├─ checklist_items │ │ │ │ │ │ ├─ project_properties (legacy → collection_properties) │ │ │ │ ├─ property_values (legacy → collection_pages) │ │ │ │ ├─ ai_keywords │ │ │ │ │ │ ├─ pages │ │ │ │ │ │ ├─ page_shares (v4.0) │ │ │ │ │ │ ├─ recents (v4.0) │ │ │ │ │ │ ├─ tags (v4.0) │ │ │ │ │ │ ├─ page_tags (v4.0) │ │ │ │ │ │ ├─ gitea_private_pages (v4.0) │ │ │ │ │ │ ├─ users │ │ │ │ │ │ ├─ user_tokens │ │ │ │ │ │ ├─ workspaces (v2.0) │ │ │ │ │ │ ├─ workspace_members (v2.0) │ │ │ │ │ │ ├─ page_history (v2.0) │ │ │ │ │ │ ├─ comments (v2.0) │ │ │ │ │ │ ├─ favorites (v2.0) │ │ │ │ │ │ └─ database_templates (v2.0) │ │ │ │ │ └─────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────┘ │ └──────────────────────────┬───────────────────────────────────────┘ │ HTTPS ┌──────────────────────────▼───────────────────────────────────────┐ │ GITEA API REST v1 │ │ git.dracodev.net │ │ ├─ /api/v1/user/repos — Liste projets │ │ ├─ /api/v1/repos/{o}/{r}/issues — Issues │ │ ├─ /api/v1/repos/{o}/{r}/labels — Labels │ │ ├─ /api/v1/repos/{o}/{r}/milestones — Milestones │ │ ├─ /api/v1/user — Infos utilisateur │ │ └─ /api/v1/orgs/{org}/repos — Repos d'organisation │ └──────────────────────────────────────────────────────────────────┘ ``` --- ## 3. Modèle de données ### 3.1 Tables Core (existantes v1.0) ```sql -- Authentification CREATE TABLE users ( id INTEGER PRIMARY KEY AUTOINCREMENT, login TEXT NOT NULL UNIQUE, full_name TEXT NOT NULL DEFAULT '', email TEXT NOT NULL DEFAULT '', avatar_url TEXT NOT NULL DEFAULT '', is_admin BOOLEAN NOT NULL DEFAULT 0, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE user_tokens ( id INTEGER PRIMARY KEY AUTOINCREMENT, gitea_user_id INTEGER NOT NULL UNIQUE, gitea_token TEXT NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); -- Boards (legacy — sera absorbé par collections en v1.3) CREATE TABLE boards ( id INTEGER PRIMARY KEY AUTOINCREMENT, project_owner TEXT NOT NULL, project_name TEXT NOT NULL, columns_json TEXT NOT NULL DEFAULT '["Backlog","À faire","En cours","Révision","Terminé"]', wip_limits_json TEXT DEFAULT '{}', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, UNIQUE(project_owner, project_name) ); CREATE TABLE cards ( id INTEGER PRIMARY KEY AUTOINCREMENT, board_id INTEGER NOT NULL REFERENCES boards(id), gitea_issue_id INTEGER, column_name TEXT NOT NULL DEFAULT 'Backlog', position INTEGER NOT NULL DEFAULT 0, priority TEXT DEFAULT 'Medium', due_date TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE col_mapping ( id INTEGER PRIMARY KEY AUTOINCREMENT, board_id INTEGER NOT NULL REFERENCES boards(id), column_name TEXT NOT NULL, gitea_label TEXT NOT NULL, close_issue BOOLEAN NOT NULL DEFAULT 0, UNIQUE(board_id, column_name) ); -- Notes & Checklists CREATE TABLE notes ( id INTEGER PRIMARY KEY AUTOINCREMENT, project_owner TEXT NOT NULL, project_name TEXT NOT NULL, title TEXT NOT NULL DEFAULT 'Notes', content TEXT NOT NULL DEFAULT '', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, UNIQUE(project_owner, project_name, title) ); CREATE TABLE checklists ( id INTEGER PRIMARY KEY AUTOINCREMENT, board_id INTEGER NOT NULL, gitea_issue_id INTEGER NOT NULL, title TEXT NOT NULL DEFAULT 'Checklist', position INTEGER NOT NULL DEFAULT 0, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, UNIQUE(board_id, gitea_issue_id, title) ); CREATE TABLE checklist_items ( id INTEGER PRIMARY KEY AUTOINCREMENT, checklist_id INTEGER NOT NULL REFERENCES checklists(id) ON DELETE CASCADE, content TEXT NOT NULL DEFAULT '', checked BOOLEAN NOT NULL DEFAULT 0, position INTEGER NOT NULL DEFAULT 0, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); -- Propriétés custom (legacy) CREATE TABLE project_properties ( id INTEGER PRIMARY KEY AUTOINCREMENT, project_owner TEXT NOT NULL, project_name TEXT NOT NULL, name TEXT NOT NULL, prop_type TEXT NOT NULL DEFAULT 'select', options_json TEXT DEFAULT '[]', position INTEGER NOT NULL DEFAULT 0, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, UNIQUE(project_owner, project_name, name) ); CREATE TABLE property_values ( id INTEGER PRIMARY KEY AUTOINCREMENT, property_id INTEGER NOT NULL REFERENCES project_properties(id) ON DELETE CASCADE, gitea_issue_id INTEGER NOT NULL, value TEXT NOT NULL DEFAULT '', UNIQUE(property_id, gitea_issue_id) ); CREATE TABLE ai_keywords ( id INTEGER PRIMARY KEY AUTOINCREMENT, project_owner TEXT NOT NULL, project_name TEXT NOT NULL, keyword TEXT NOT NULL, color TEXT NOT NULL DEFAULT '#787774', usage_count INTEGER NOT NULL DEFAULT 1, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, UNIQUE(project_owner, project_name, keyword) ); -- Pages CREATE TABLE pages ( id INTEGER PRIMARY KEY AUTOINCREMENT, workspace TEXT NOT NULL, title TEXT NOT NULL DEFAULT 'New page', content TEXT NOT NULL DEFAULT '', content_format TEXT NOT NULL DEFAULT 'markdown', parent_section TEXT DEFAULT 'Private', parent_id INTEGER REFERENCES pages(id), sort_order INTEGER NOT NULL DEFAULT 0, deleted_at TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); ``` ### 3.2 Tables Database Concept (v1.3) ```sql -- Collections = Databases Notion CREATE TABLE collections ( id INTEGER PRIMARY KEY AUTOINCREMENT, workspace_id INTEGER REFERENCES workspaces(id), name TEXT NOT NULL, description TEXT DEFAULT '', icon TEXT DEFAULT '📋', schema_json TEXT NOT NULL DEFAULT '[]', -- [{name,type,options,position}, ...] -- Lien Gitea optionnel gitea_owner TEXT, gitea_repo TEXT, is_locked BOOLEAN NOT NULL DEFAULT 0, is_inline BOOLEAN NOT NULL DEFAULT 0, parent_page_id INTEGER REFERENCES collection_pages(id), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, created_by INTEGER REFERENCES users(id), UNIQUE(workspace_id, name) ); -- Pages dans une collection CREATE TABLE collection_pages ( id INTEGER PRIMARY KEY AUTOINCREMENT, collection_id INTEGER NOT NULL REFERENCES collections(id) ON DELETE CASCADE, title TEXT NOT NULL DEFAULT '', icon TEXT DEFAULT '📄', position INTEGER NOT NULL DEFAULT 0, -- Sub-items parent_id INTEGER REFERENCES collection_pages(id), -- Gitea link gitea_issue_id INTEGER, gitea_issue_number INTEGER, -- Toutes les valeurs de propriétés en JSON property_values_json TEXT NOT NULL DEFAULT '{}', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, created_by INTEGER REFERENCES users(id), updated_by INTEGER REFERENCES users(id) ); CREATE INDEX idx_cp_collection ON collection_pages(collection_id, position); CREATE INDEX idx_cp_parent ON collection_pages(parent_id); CREATE INDEX idx_cp_gitea ON collection_pages(gitea_issue_id); -- Vues d'une collection CREATE TABLE collection_views ( id INTEGER PRIMARY KEY AUTOINCREMENT, collection_id INTEGER NOT NULL REFERENCES collections(id) ON DELETE CASCADE, name TEXT NOT NULL DEFAULT 'Default View', view_type TEXT NOT NULL DEFAULT 'table', -- table | board | calendar | timeline | gallery | list config_json TEXT NOT NULL DEFAULT '{}', -- { -- group_by: "Status", -- filters: [{property,operator,value}, ...], -- filter_conjunction: "and" | "or", -- sorts: [{property,direction}, ...], -- visible_properties: ["Title","Status"], -- card_size: "medium", -- cover_property: "Files", -- date_property: "DueDate", -- date_range_property: "EndDate" -- } position INTEGER NOT NULL DEFAULT 0, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); ``` ### 3.3 Tables Propriétés Avancées (v1.4) ```sql CREATE TABLE collection_properties ( id INTEGER PRIMARY KEY AUTOINCREMENT, collection_id INTEGER NOT NULL REFERENCES collections(id) ON DELETE CASCADE, name TEXT NOT NULL, prop_type TEXT NOT NULL DEFAULT 'text', -- title, text, number, select, multi_select, status, -- date, person, checkbox, url, email, phone, -- relation, rollup, formula, -- files, unique_id, -- created_time, created_by, last_edited_time, last_edited_by -- Options pour select/multi_select/status options_json TEXT DEFAULT '[]', -- [{name:"Todo",color:"gray"}, {name:"Done",color:"green"}] -- Format pour number number_format TEXT DEFAULT 'number', -- number, percent, dollar, euro, pound, yen -- Pour relation related_collection_id INTEGER REFERENCES collections(id), reverse_name TEXT, -- Pour rollup relation_property_id INTEGER REFERENCES collection_properties(id), target_property_id INTEGER REFERENCES collection_properties(id), rollup_function TEXT, -- count, sum, avg, min, max, range, unique -- Pour formula formula_expression TEXT, position INTEGER NOT NULL DEFAULT 0, required BOOLEAN NOT NULL DEFAULT 0, visible_in_views BOOLEAN NOT NULL DEFAULT 1, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, UNIQUE(collection_id, name) ); ``` ### 3.4 Tables Multi-Utilisateurs (v2.0) ```sql CREATE TABLE workspaces ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, owner_id INTEGER NOT NULL REFERENCES users(id), settings_json TEXT NOT NULL DEFAULT '{}', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE workspace_members ( id INTEGER PRIMARY KEY AUTOINCREMENT, workspace_id INTEGER NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE, user_id INTEGER NOT NULL REFERENCES users(id), role TEXT NOT NULL DEFAULT 'editor', -- admin | editor | commenter | viewer joined_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, UNIQUE(workspace_id, user_id) ); CREATE TABLE page_history ( id INTEGER PRIMARY KEY AUTOINCREMENT, page_id INTEGER NOT NULL REFERENCES collection_pages(id) ON DELETE CASCADE, user_id INTEGER REFERENCES users(id), change_type TEXT NOT NULL, -- created | updated | moved | deleted | restored snapshot_json TEXT NOT NULL DEFAULT '{}', -- Snapshot des valeurs avant modification created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE comments ( id INTEGER PRIMARY KEY AUTOINCREMENT, page_id INTEGER NOT NULL REFERENCES collection_pages(id) ON DELETE CASCADE, user_id INTEGER NOT NULL REFERENCES users(id), body TEXT NOT NULL DEFAULT '', parent_id INTEGER REFERENCES comments(id), resolved BOOLEAN NOT NULL DEFAULT 0, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE favorites ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL REFERENCES users(id), page_id INTEGER REFERENCES collection_pages(id), collection_id INTEGER REFERENCES collections(id), -- L'un des deux est NOT NULL position INTEGER NOT NULL DEFAULT 0, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, UNIQUE(user_id, page_id, collection_id) ); CREATE TABLE database_templates ( id INTEGER PRIMARY KEY AUTOINCREMENT, workspace_id INTEGER REFERENCES workspaces(id), name TEXT NOT NULL, description TEXT DEFAULT '', schema_json TEXT NOT NULL DEFAULT '[]', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, created_by INTEGER REFERENCES users(id) ); CREATE TABLE page_templates ( id INTEGER PRIMARY KEY AUTOINCREMENT, collection_id INTEGER NOT NULL REFERENCES collections(id) ON DELETE CASCADE, name TEXT NOT NULL DEFAULT 'Default', property_values_json TEXT NOT NULL DEFAULT '{}', content_json TEXT DEFAULT '[]', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); ``` ### 3.5 Diagramme des relations ``` ┌──────────────┐ ┌──────────────────┐ ┌───────────────────┐ │ workspaces │──1:N──│ workspace_members│──N:1──│ users │ │ │ │ - role │ │ - login │ │ - name │ │ - joined_at │ │ - email │ │ - owner_id │ └──────────────────┘ │ - avatar_url │ └──────┬───────┘ │ - is_admin │ │ 1:N └──────┬────┬────────┘ ▼ │ │ ┌──────────────┐ ┌──────────────────┐ │ │ (author) │ collections │──1:N──│ collection_pages │◄─N:1───────┘ │ │ │ │ │ │ │ - name │ │ - title │ │ │ - schema_json│ │ - parent_id ────┼─── self-ref │ │ - icon │ │ - gitea_issue_id│ │ │ - gitea_* │ │ - property_values│ │ │ - is_locked │ │ _json │ │ └──────┬───────┘ └────────┬─────────┘ │ │ 1:N │ 1:N │ ▼ ▼ │ ┌──────────────────┐ ┌──────────────────┐ │ │collection_views │ │ comments │ │ │ - view_type │ │ - body │ │ │ - config_json │ │ - resolved │ │ └──────────────────┘ │ - parent_id │ │ └────────┬─────────┘ │ │ │ ┌──────────────────┐ │ │ │collection_properties│ │ │ │ - name │ │ │ │ - prop_type │◄─────────┘ │ │ - options_json │ │ │ - related_collection_id ──┐ │ │ - relation_property_id │ self-ref │ │ - target_property_id │ │ │ - formula_expression │ │ └────────────────────────────┘ │ │ ┌──────────────────┐ ┌──────────────────┐ │ │ page_history │ │ favorites │ │ │ - change_type │ │ - position │ │ │ - snapshot_json │ └──────────────────┘ │ │ - user_id ──────┼────────────────────────────────────────┘ └──────────────────┘ ``` --- ## 4. Routage & API ### 4.1 Routes existantes (v1.2) ``` GET / → Dashboard GET /board/{owner}/{repo} → Board Kanban GET /board/{owner}/{repo}/view/{view} → Fragment vue (kanban|table|status|teamload|detailed) GET /board/{owner}/{repo}?view=&status=&filter=&sort= → Avec paramètres GET /board/api/properties/{o}/{r} → Liste propriétés POST /board/api/properties/{o}/{r} → Créer propriété DEL /board/api/properties/{o}/{r} → Supprimer propriété POST /board/api/properties/{o}/{r}/values → Set valeur propriété GET /board/api/ai-keywords/{o}/{r} → Liste keywords POST /board/api/ai-keywords/{o}/{r}/extract → Extraire keywords POST /board/api/sync/{o}/{r} → Sync complète GET /api/health → Health check GET /api/stats → Statistiques GET /api/projects → Projets JSON POST /api/move → Déplacer carte GET /api/issues/{o}/{r}/{id} → Issue detail (JSON) GET /api/issues/{o}/{r}/{id}?format=html → Issue detail (HTML) POST /api/issues/{o}/{r} → Créer issue PATCH /api/issues/{o}/{r}/{id} → Update issue POST /api/checklists/{o}/{r}/{id} → Créer checklist POST /api/checklist-items/{o}/{r}/{id}/{cl_id} → Ajouter item PATCH /api/checklist-items/{id} → Toggle item GET /notes/{o}/{r} → Lire notes POST /notes/{o}/{r} → Sauvegarder notes GET /auth/login → Login OAuth2 Gitea GET /auth/callback → Callback OAuth2 GET /auth/logout → Déconnexion POST /webhooks/gitea → Webhook Gitea GET /pages/{page_id} → Voir page POST /board/api/pages → Créer page GET /board/api/pages/{id} → Get page JSON PUT /board/api/pages/{id} → Update page PUT /board/api/pages/{id}/move → Déplacer page (drag & drop) POST /board/api/pages/{id}/blocks → Save block editor DEL /board/api/pages/{id} → Soft delete GET /board/trash → Page corbeille GET /board/api/trash → Liste trash POST /board/api/trash/{id}/restore → Restaurer DEL /board/api/trash/{id} → Suppression définitive GET /library → Bibliothèque GET /accounts → Gestion comptes ``` ### 4.2 Routes Database (v1.3-v1.5) ``` GET /db → Liste collections du workspace POST /db → Créer collection GET /db/{collection_id} → Vue par défaut de la collection GET /db/{collection_id}/view/{view_id} → Vue spécifique GET /db/{collection_id}/fragment/{view_type} → Fragment HTMX POST /db/{collection_id}/pages → Créer page GET /db/{collection_id}/pages/{page_id} → Détail page PUT /db/{collection_id}/pages/{page_id} → Update page DELETE /db/{collection_id}/pages/{page_id} → Soft delete page GET /db/{collection_id}/properties → Liste propriétés POST /db/{collection_id}/properties → Créer propriété PUT /db/{collection_id}/properties/{prop_id} → Update propriété DELETE /db/{collection_id}/properties/{prop_id} → Supprimer propriété GET /db/{collection_id}/views → Liste vues POST /db/{collection_id}/views → Créer vue PUT /db/{collection_id}/views/{view_id} → Update config vue DELETE /db/{collection_id}/views/{view_id} → Supprimer vue POST /db/{collection_id}/views/duplicate/{id} → Dupliquer vue POST /db/{collection_id}/views/save-as → Sauvegarder état courant GET /db/{collection_id}/templates → Page templates POST /db/{collection_id}/templates → Créer template POST /db/{collection_id}/templates/{tid}/apply → Appliquer template POST /db/{collection_id}/import/csv → Import CSV GET /db/{collection_id}/export/csv → Export CSV ``` ### 4.3 Routes Sub-items & Dépendances (v1.8) ``` GET /db/{collection_id}/pages/{page_id}/sub-items → Liste sub-items POST /db/{collection_id}/pages/{page_id}/sub-items → Créer sub-item GET /db/{collection_id}/pages/{page_id}/dependencies → Bloque/Bloqué par POST /db/{collection_id}/pages/{page_id}/dependencies → Ajouter dépendance DELETE /db/{collection_id}/pages/{page_id}/dependencies/{dep_id} → Supprimer POST /db/{collection_id}/pages/{page_id}/check-deps → Vérifier contraintes ``` ### 4.4 Routes My Tasks (v1.9) ``` GET /my-tasks → Dashboard unifié GET /my-tasks?view=calendar → Vue calendrier GET /my-tasks?view=overdue → Tâches en retard GET /my-tasks?view=upcoming&days=7 → 7 prochains jours GET /my-tasks?filter=collection:3 → Filtrer par collection ``` ### 4.5 Routes Multi-Utilisateurs (v2.0) ``` GET /workspace/{ws_id} → Workspace POST /workspace → Créer workspace PUT /workspace/{ws_id}/settings → Settings POST /workspace/{ws_id}/members → Inviter membre PUT /workspace/{ws_id}/members/{user_id}/role → Changer rôle DELETE /workspace/{ws_id}/members/{user_id} → Retirer membre GET /db/{collection_id}/pages/{page_id}/history → Historique GET /db/{collection_id}/pages/{page_id}/comments→ Commentaires POST /db/{collection_id}/pages/{page_id}/comments→ Ajouter commentaire GET /favorites → Liste favoris POST /favorites → Ajouter favori DELETE /favorites/{id} → Retirer favori ``` ### 4.6 Routes Sidebar & Library (v4.0) ``` GET /library → Bibliothèque dynamique GET /api/library/recents → Pages récentes GET /api/library/favorites → Favoris GET /api/library/shared → Pages partagées GET /api/library/published → Pages publiées GET /api/library/private → Pages privées GET /api/library/workspace → Pages du workspace GET /api/local-workspace/items → Arborescence workspace POST /api/local-workspace/items → Créer page/dossier PUT /api/local-workspace/items/{id} → Renommer DELETE /api/local-workspace/items/{id} → Soft delete POST /api/local-workspace/upload → Upload fichier GET /api/files/{ws_id}/{filename} → Servir fichier uploadé GET /help → Page d'aide complète GET /api/public/pages/{slug} → Page publique ``` ### 4.7 Routes Share & Publish (v4.0) ``` POST /api/pages/{id}/share → Partager une page DELETE /api/pages/{id}/share/{share_id} → Retirer partage GET /api/pages/{id}/shares → Liste partages POST /api/pages/{id}/publish → Publier page DELETE /api/pages/{id}/publish → Dépublier ``` --- ## 5. Système d'authentification (v4.0) FlowDeck supporte **3 méthodes d'authentification** et la **connexion d'intégrations multiples** à un même compte local. ### 5.1 Types de comptes | Type | Auth | Workspaces | Intégrations | |------|------|------------|--------------| | **Local pur** | email + password | Locaux uniquement | Aucune | | **Local + intégrations** | email + password | Locaux + distants | Gitea et/ou GitHub | | **OAuth pur** | Gitea ou GitHub | Distants uniquement | Non-déconnectable | ### 5.2 Comportement du sidebar selon le type de compte ``` LOCAL PUR: ├─ 📁 [nom workspace local] ├─ 📅 Meetings ├─ 🕒 Recents ├─ ⭐ Favorites ├─ 🤖 Agents ├─ 👥 Shared ← visible si pages partagées ├─ 🌐 Published ← visible si pages publiées ├─ (Private caché) └─ 📚 Library / ✅ My Tasks / 🗑️ Trash / ❓ Help LOCAL + GITEA/GITHUB: ├─ 🔗 [owner/repo] ← workspace distant actif ├─ 📅 Meetings ├─ 🕒 Recents ├─ ⭐ Favorites ├─ 🤖 Agents ├─ 👥 Shared ├─ 🌐 Published ├─ 🔒 Private ← visible (fichiers locaux liés au projet distant) └─ 📚 / ✅ / 🗑️ / ❓ OAUTH PUR (GITEA/GITHUB): ├─ 🔗 [owner/repo] ← identité = compte Gitea/GitHub ├─ 🕒 Recents ├─ ⭐ Favorites ├─ 👥 Shared ├─ 🌐 Published ├─ (Private caché) └─ 📚 / ✅ / ❓ (Trash caché — pas de workspace local) ``` ### 5.3 Règles d'affichage - **Section Private** : visible UNIQUEMENT si compte local + workspace distant actif - **Trash** : visible si `auth_method == 'local'` ou si des pages locales existent - **Boutons New File/New Folder** : cachés si `has_active_workspace == False` - **Message "No workspace open"** : affiché dans la section Workspace si aucun workspace actif - **Header sidebar** : toujours le compte local si auth_method=local, même avec intégrations ### 5.4 cookie `flowdeck_workspace` - `""` → aucun workspace actif - `"42"` → workspace local ID 42 - `"gitea:owner:repo"` → workspace distant Gitea - Nettoyage automatique si cookie invalide (workspace supprimé ou autre user) --- ## 6. Frontend ### 5.1 Layout ```ascii ┌──────────────────────────────────────────────────────────────┐ │ TOPBAR (unified-header) │ │ ┌──────┬───────────────────────────────────────┬───────────┐ │ │ │ «» │ Breadcrumbs: 🏠 Workspaces │ Share ⚙ │ │ │ └──────┴───────────────────────────────────────┴───────────┘ │ ├────────────┬─────────────────────────────────────────────────┤ │ SIDEBAR │ CONTENU PRINCIPAL │ │ (240px) │ │ │ │ ┌─ Help Page ────────────────────────────────┐│ │ ┌────────┐ │ │ ┌──────────────┐ ┌──────────────┐ ││ │ │ Header │ │ │ │ 🚀 Getting │ │ 📝 Pages │ (...) ││ │ │ [D] │ │ │ │ Started │ │ & Editor │ ││ │ │ Draco │ │ │ └──────────────┘ └──────────────┘ ││ │ └────────┘ │ └─────────────────────────────────────────────┘│ │ │ │ │ 🏠 💬 📨 🔍│ ┌─ Library ─────────────────────────────────┐│ │ │ │ [Recents|Favorites|Shared|Published|...] ││ │ 📁 Worksp. │ │ ┌──────┬──────────┬──────────┬───────────┐ ││ │ 📄 New Pg │ │ │Name │Created by│Source │Last edited│ ││ │ 📁 New Fl │ │ ├──────┼──────────┼──────────┼───────────┤ ││ │ 📄 page 1 │ │ │doc A │Draco │Local │10 min ago │ ││ │ 📄 page 2 │ │ │doc B │bruno │Gitea │2 hours ago│ ││ │ │ │ └──────┴──────────┴──────────┴───────────┘ ││ │ 📅 Meeting │ └─────────────────────────────────────────────┘│ │ 🕒 Recents │ │ │ ⭐ Favoris │ ┌─ Workspaces ───────────────────────────────┐│ │ 🤖 Agents │ │ ┌──────────┐ ┌──────────┐ ┌───┐ ┌───┐ ││ │ 👥 Shared │ │ │Project A │ │Project B │ │ + │ │ + │ ││ │ 🌐 Publ. │ │ │3 pages │ │12 pages │ │ │ │ │ ││ │ │ │ └──────────┘ └──────────┘ └───┘ └───┘ ││ │ ══════════ │ └─────────────────────────────────────────────┘│ │ 📚 Library │ │ │ ✅ My Task │ ┌─ Settings ─────────────────────────────────┐│ │ 🗑️ Trash │ │ My Account | Admin | Integrations ││ │ ❓ Help │ │ [Upload photo] [Full Name] [Email] [...] ││ │ │ └─────────────────────────────────────────────┘│ │ 💬 [+📄] │ │ └────────────┴─────────────────────────────────────────────────┘ ``` ### 5.2 Vues supportées | Vue | Icône | Template | Description | |-----|-------|----------|-------------| | Table | 📊 | `table_view.html` | Colonnes = propriétés, lignes = pages | | Board (Kanban) | 📋 | `board_fragment.html` | Groupé par propriété Select | | Calendar | 📅 | `calendar_view.html` | Groupé par propriété Date | | Timeline (Gantt) | 📈 | `timeline_view.html` | Barres horizontales sur axe Date | | Gallery | 🖼️ | `gallery_view.html` | Cartes visuelles avec cover | | List | 📝 | `list_view.html` | Compact, 1 colonne + preview | ### 5.3 Composants réutilisables - **Property editor** — éditeur inline par type (select dropdown, date picker, text input...) - **Filter builder** — UI de construction de filtres (propriété + opérateur + valeur) - **Sort builder** — UI de tri multi-critères - **Group selector** — dropdown pour choisir la propriété de groupement - **View tabs** — onglets pour switcher entre vues - **Page modal** — détail d'une page avec propriétés, checklists, commentaires - **Slash menu** — menu contextuel pour l'éditeur de blocs --- ## 6. Système d'authentification ### 6.1 Flux OAuth2 Gitea ``` User ──→ GET /auth/login ──→ Redirect Gitea OAuth │ Gitea authorize page │ User authorize ──→ GET /auth/callback?code=xxx │ POST /api/v1/login/oauth/access_token │ ┌─────▼──────┐ │ access_token│ └─────┬──────┘ │ GET /api/v1/user │ ┌─────▼──────────────────┐ │ Create/Update: │ │ - users (login, email) │ │ - user_tokens (token) │ │ - session cookie │ └─────────────────────────┘ ``` ### 6.2 Multi-utilisateurs (v2.0) ``` ┌──────────────────────────────────────────────────────────┐ │ PERMISSION MODEL │ │ │ │ Workspace │ │ ├─ Owner (créateur) — tout peut faire │ │ ├─ Admin — gérer membres, config │ │ ├─ Editor — CRUD pages, propriétés │ │ ├─ Commenter — commentaires seulement │ │ └─ Viewer — lecture seule │ │ │ │ Collection (hérite du workspace par défaut) │ │ ├─ Peut être overridé par collection │ │ └─ Database lock → lecture seule pour tout le monde │ │ │ │ Page │ │ ├─ Propriétaire = created_by │ │ ├─ Mention @user → notifie l'utilisateur │ │ └─ Assignee (property type Person) │ └──────────────────────────────────────────────────────────┘ ``` --- ## 6.5 Modèle de comptes & intégrations (v4.0) ### 6.5.1 Types de comptes FlowDeck supporte 3 types de comptes, déterminés par `auth_method` dans la table `users` : ``` ┌──────────────┬──────────────────┬─────────────────────────────────────┐ │ auth_method │ Login via │ Identité dans le sidebar │ ├──────────────┼──────────────────┼─────────────────────────────────────┤ │ local │ email + password │ Nom local (ex: "Draco") │ │ gitea │ OAuth2 Gitea │ Nom Gitea + badge 🦎 Gitea │ │ github │ OAuth2 GitHub │ Nom GitHub + badge 🐙 GitHub │ └──────────────┴──────────────────┴─────────────────────────────────────┘ ``` **Règle fondamentale** : Un compte local avec intégration(s) reste un compte LOCAL. Le badge OAuth (🦎/🐙) n'apparaît QUE pour les comptes dont `auth_method` est le provider. ``` Compte LOCAL avec intégrations : ┌──────────────────────────────────┐ │ [D] Draco │ ← Nom local, PAS de badge │ bruno.charest@gmail.com │ └──────────────────────────────────┘ Compte GITEA (auth_method=gitea) : ┌──────────────────────────────────┐ │ [B] bruno [🦎 Gitea] │ ← Badge visible │ bruno@dracodev.net │ └──────────────────────────────────┘ ``` ### 6.5.2 Matrice des intégrations ``` Compte principal Peut ajouter Déconnexion possible ───────────────────────────────────────────────────────── local Gitea ✅ Oui local GitHub ✅ Oui local Gitea + GitHub ✅ Les deux ───────────────────────────────────────────────────────── gitea GitHub ✅ GitHub ❌ Gitea github Gitea ✅ Gitea ❌ GitHub ``` **Settings → Integrations** : Affiche toutes les intégrations avec bouton Disconnect. Le provider principal (auth_method) n'a PAS de bouton Disconnect. ### 6.5.3 Scénarios de workspace ``` SCÉNARIO A : Compte local pur ───────────────────────────── · Login email+password · Workspaces locaux uniquement · Section Private : CACHÉE (tout est déjà local) · Trash : ✅ (restauration locale) SCÉNARIO B : Compte local + intégration(s) ────────────────────────────────────────── · Login email+password · Workspaces : locaux + Gitea + GitHub · Section Private : VISIBLE si workspace distant actif · Private : stockage DB locale, associé user+repo · Trash : ✅ (local uniquement) SCÉNARIO C : Compte OAuth pur (gitea ou github) ──────────────────────────────────────────────── · Login via OAuth · Workspaces : distants du provider principal · Section Private : VISIBLE (toujours) · Trash : ❌ (pas de contenu local) · Intégration secondaire : possible · Déconnexion provider principal : impossible ``` ### 6.5.4 Sidebar — règles d'affichage par section ``` Section Scénario A Scénario B Scénario C ───────────── ────────────── ────────────────────── ────────────── Recents ✅ (local) ✅ (tout) ✅ (tout) Favorites ✅ (local) ✅ (tout) ✅ (tout) Shared ✅ (local) ✅ (tout) ✅ (tout) Published ✅ (local) ✅ (tout) ✅ (tout) Agents ✅ ✅ ✅ Private ❌ CACHÉ ✅ si ws distant actif ✅ Workspace ✅ ✅ ✅ Trash ✅ (local) ✅ (local) ❌ ``` Chaque section a un bouton `→ Library` qui ouvre : ``` /library?tab=recents|favorites|shared|published|private|workspace ``` ### 6.5.5 Système Share & Publish Le bouton [Share] dans la topbar d'une page ouvre une modale à 2 tabs : ``` ┌──────────────────────────────────────────────────────┐ │ [ Share ] [ Publish ] │ ├──────────────────────────────────────────────────────┤ │ Share tab : │ │ · Invite people : [email] [Invite] │ │ · General access : [🔒 Restricted] Copy link │ │ · Liste des personnes/groupes avec accès │ │ → Document listé dans sidebar "Shared" │ ├──────────────────────────────────────────────────────┤ │ Publish tab : │ │ · [Publish to web] → URL publique générée │ │ · URL : https://flowdeck.draco.dev/p/ │ │ · [Unpublish] │ │ → Document listé dans sidebar "Published" │ └──────────────────────────────────────────────────────┘ ``` ### 6.5.6 Stockage des fichiers Private Quand un workspace distant est actif, les fichiers créés dans la section Private sont stockés dans la **base de données locale** (table `pages` avec `parent_section='Private'` et `workspace=gitea:/`). Cela permet de : - Prendre des notes personnelles liées à un projet sans les committer - Garder des brouillons avant de les pousser - Avoir un espace de travail privé même sur un repo partagé ### 6.5.7 Trash — règles ``` Contenu Trash ? Comportement ─────────────────────── ──────── ────────────────────────── Workspace local ✅ Supprimé → restaurable Dossier/fichier local ✅ Supprimé → restaurable Fichier Gitea/GitHub ❌ Commit "delete" → pas trash Page distante ❌ Commit "delete" → pas trash ``` ### 6.5.8 Édition de fichiers — UI unifiée ``` Fichier LOCAL Fichier DISTANT (Gitea/GitHub) ┌─────────────────────────┐ ┌─────────────────────────────┐ │ Même UI d'édition │ │ Même UI d'édition │ │ Même rendu visuel │ │ Même rendu visuel │ │ Sauvegarde auto DB │ │ + Bouton [Commit] │ │ Pas de commit │ │ + Champ message commit │ └─────────────────────────┘ └─────────────────────────────┘ ``` ### 6.5.9 Page Library — `/library?tab=` ``` ┌──────────────────────────────────────────────────────────────┐ │ Library │ │ [Recents] [Favorites] [Shared] [Published] │ │ [Private] [Workspace] │ ├──────────────────────────────────────────────────────────────┤ │ Filtres : [All] [Local] [Gitea] [GitHub] │ │ │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ 📄 Document X Local · modifié il y a 2h │ │ │ │ 📄 README.md Gitea · bruno/flowdeck │ │ │ │ 📄 Notes Local · Workspace Projet A │ │ │ └─────────────────────────────────────────────────────────┘ │ └──────────────────────────────────────────────────────────────┘ ``` ### 6.5.10 Schéma DB — mises à jour pour v4.0 ```sql -- users : ajout auth_method ALTER TABLE users ADD COLUMN auth_method TEXT NOT NULL DEFAULT 'local'; -- 'local' | 'gitea' | 'github' -- users : colonnes OAuth existantes -- gitea_id INTEGER (NULL si pas Gitea) -- github_id INTEGER (NULL si pas GitHub) -- gitea_token TEXT (token intégration, NULL si pas connecté) -- github_token TEXT (token intégration, NULL si pas connecté) -- pages : ajout published ALTER TABLE pages ADD COLUMN is_published BOOLEAN NOT NULL DEFAULT 0; ALTER TABLE pages ADD COLUMN publish_slug TEXT; ALTER TABLE pages ADD COLUMN is_shared BOOLEAN NOT NULL DEFAULT 0; ALTER TABLE pages ADD COLUMN share_mode TEXT DEFAULT 'restricted'; -- 'restricted' | 'link' | 'public' -- page_shares : qui a accès à quel document CREATE TABLE page_shares ( id INTEGER PRIMARY KEY AUTOINCREMENT, page_id INTEGER NOT NULL REFERENCES pages(id) ON DELETE CASCADE, shared_with_user_id INTEGER REFERENCES users(id), shared_with_email TEXT, permission TEXT NOT NULL DEFAULT 'view', -- 'view' | 'comment' | 'edit' created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, created_by INTEGER REFERENCES users(id) ); -- recents : historique de consultation CREATE TABLE recents ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL REFERENCES users(id), page_id INTEGER NOT NULL REFERENCES pages(id), workspace TEXT NOT NULL, source_type TEXT NOT NULL DEFAULT 'local', -- 'local' | 'gitea' | 'github' accessed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, UNIQUE(user_id, page_id) ); ``` ### 6.5.11 Plan de migration ``` Phase 1 — DB & Modèles 1. Ajouter auth_method aux users existants (auto-détecter) 2. Ajouter is_published, is_shared, share_mode aux pages 3. Créer table page_shares 4. Créer table recents Phase 2 — Sidebar 1. Badge OAuth (🦎/🐙) basé sur auth_method 2. Afficher/cacher Private selon type de compte + workspace actif 3. Boutons Library par section 4. Trash filtré local-only Phase 3 — Share & Publish 1. Modale Share à 2 tabs fonctionnelle 2. Share via email/user → table page_shares 3. Share via lien → share_mode='link' 4. Publish → is_published + publish_slug 5. Page publique servable à /p/ Phase 4 — Library 1. Page /library avec tabs 2. Filtrage par source (local/gitea/github) 3. Recents alimentés automatiquement Phase 5 — Intégrations 1. GitHub OAuth déjà fait (providers.py) 2. Matrice de déconnexion respectée 3. Compte local peut connecter/déconnecter Gitea+GitHub ``` --- ## 7. Intégration Gitea ### 7.1 Flux de données ``` Création Collection Sync bidirectionnelle ────────────────── ────────────────────── 1. Collection vide 5. POST /board/api/sync/{o}/{r} (pas de lien Gitea) │ ├─ GET issues Gitea 2. Collection liée ├─ Map issues → collection_pages (gitea_owner + gitea_repo) │ (via _issue_column) ├─ Extract AI keywords 3. Pull initial └─ Upsert cards in DB GET /api/v1/repos/{o}/{r}/issues 6. Webhook Gitea 4. Mapping colonnes POST /webhooks/gitea col_mapping: │ column_name → gitea_label ├─ issue.opened → create page ├─ issue.closed → archive page └─ issue.labeled → move column ``` ### 7.2 Adaptateur de compatibilité ```python class GiteaBoardCompat: """Adaptateur: board Gitea legacy → Collection.""" @staticmethod def from_board(board_row) -> dict: return { "collection_id": f"gitea:{board_row.id}", "name": f"{board_row.project_owner}/{board_row.project_name}", "gitea_owner": board_row.project_owner, "gitea_repo": board_row.project_name, "schema": [ {"name": "Title", "type": "title"}, {"name": "Status", "type": "select", "options": json.loads(board_row.columns_json)}, {"name": "Priority", "type": "select", "options": ["P1","P2","P3","P4"]}, ], "is_gitea_linked": True, } @staticmethod def from_collection_page(page_row) -> dict: """Convertit collection_page → card (pour templates legacy).""" return { "id": str(page_row.get("gitea_issue_number", page_row["id"])), "title": page_row["title"], "status": _derive_status(page_row), ... } ``` --- ## 8. Système de propriétés ### 8.1 Architecture ``` ┌─────────────────────────────────────────────────────────────┐ │ PROPERTY SYSTEM │ │ │ │ collection_properties (schéma) property_values_json │ │ ┌──────────────────────────┐ ┌──────────────────────┐ │ │ │ id: 1 │ │ Dans collection_pages │ │ │ │ name: "Status" │ │ { │ │ │ │ prop_type: "status" │ │ "1": "Done", │ │ │ │ options_json: [ │ │ "2": "P1", │ │ │ │ {name:"Todo", │ │ "3": "2026-07-15", │ │ │ │ color:"gray"}, │ │ "4": ["bruno"], │ │ │ │ {name:"Done", │ │ "5": ["page_42"], │ │ │ │ color:"green"} │ │ } │ │ │ │ ] │ └──────────────────────┘ │ │ └──────────────────────────┘ │ │ │ │ Avantages du JSON: │ │ - Pas de migration de table pour ajouter une propriété │ │ - Requêtes via json_extract() en SQLite │ │ - Facilement requêtable avec index si nécessaire │ └─────────────────────────────────────────────────────────────┘ ``` ### 8.2 Types de propriétés | Type | Stockage | Exemple | |------|----------|---------| | `title` | String | `"Ma tâche"` | | `text` | String | `"Description longue..."` | | `number` | Float | `42` / `12.5` | | `select` | String | `"En cours"` | | `multi_select` | JSON array | `["Frontend","Backend"]` | | `status` | String + color | `"Done"` (green) | | `date` | ISO 8601 string | `"2026-07-15"` | | `person` | JSON array | `[{"id":1,"login":"bruno"}]` | | `checkbox` | Boolean | `true` | | `url` | String | `"https://..."` | | `email` | String | `"a@b.com"` | | `phone` | String | `"+1..."` | | `relation` | JSON array IDs | `[42, 57]` | | `rollup` | Computé | `3` (COUNT) | | `formula` | Computé | `"Résultat"` | | `files` | JSON array URLs | `[{"url":"...","name":"img.png"}]` | | `unique_id` | Integer | `42` | | `created_time` | Auto | `"2026-07-10T..."` | | `created_by` | Auto | `{"id":1,"login":"bruno"}` | | `last_edited_time` | Auto | `"2026-07-10T..."` | | `last_edited_by` | Auto | `{"id":1,"login":"bruno"}` | ### 8.3 Formula Engine ```python # Moteur d'expressions JavaScript-like FUNCTIONS = { "prop": lambda ctx, name: ctx["values"].get(name), "now": lambda ctx: datetime.now().isoformat(), "today": lambda ctx: date.today().isoformat(), "if": lambda ctx, cond, a, b: a if cond else b, "concat": lambda ctx, *args: "".join(str(a) for a in args), "round": lambda ctx, n, d=0: round(n, d), "contains": lambda ctx, s, sub: sub in str(s), "length": lambda ctx, s: len(str(s)), "toNumber": lambda ctx, s: float(s) if s else 0, "formatDate": lambda ctx, d, fmt: format_date(d, fmt), "dateAdd": lambda ctx, d, n, unit: add_to_date(d, n, unit), "dateSubtract": lambda ctx, d, n, unit: subtract_from_date(d, n, unit), "replace": lambda ctx, s, old, new: str(s).replace(old, new), "replaceAll": lambda ctx, s, old, new: str(s).replace(old, new), # all by default "join": lambda ctx, sep, *args: sep.join(str(a) for a in args), "empty": lambda ctx, v: v is None or v == "" or v == [], "and": lambda ctx, *args: all(args), "or": lambda ctx, *args: any(args), "not": lambda ctx, v: not v, } ``` ### 8.4 Rollup Engine ```python ROLLUP_FUNCTIONS = { "count": lambda values: len(values), "count_values": lambda values: len([v for v in values if v]), "empty": lambda values: len([v for v in values if not v]), "not_empty": lambda values: len([v for v in values if v]), "sum": lambda values: sum(float(v) for v in values if v), "average": lambda values: sum(vals:=...) / len(vals) if vals else 0, "median": lambda values: statistics.median(vals), "min": lambda values: min(vals), "max": lambda values: max(vals), "range": lambda values: max(vals) - min(vals), "unique": lambda values: list(set(values)), } ``` --- ## 9. Système de vues ### 9.1 Config JSON d'une vue ```json { "group_by": "Status", "filters": [ {"property": "Status", "operator": "is_not", "value": "Archivé"}, {"property": "DueDate", "operator": "is_after", "value": "2026-01-01"} ], "filter_conjunction": "and", "sorts": [ {"property": "Priority", "direction": "asc"}, {"property": "DueDate", "direction": "desc"} ], "visible_properties": ["Title", "Status", "Assignee", "DueDate"], "card_size": "medium", "cover_property": "Files", "date_property": "DueDate", "date_range_property": "EndDate" } ``` ### 9.2 Chaîne de rendu ``` Requête: GET /db/3/view/7 │ ▼ 1. Charger collection_views.id=7 → view_type="board", config_json={...} │ ▼ 2. Charger collection_pages WHERE collection_id=3 → [page1, page2, page3, ...] │ ▼ 3. Appliquer config_json.filters → pages filtrées │ ▼ 4. Appliquer config_json.sorts → pages triées │ ▼ 5. Grouper par config_json.group_by → { "Todo": [p1, p2], "Doing": [p3], "Done": [p4] } │ ▼ 6. Ne garder que config_json.visible_properties → Alléger le payload │ ▼ 7. Render template correspondant à view_type → board_fragment.html │ ▼ 8. Retourner HTML fragment (HTMX) ``` --- ## 10. Sub-items & Dépendances ### 10.1 Sub-items ``` ┌──────────────────────────────────────┐ │ collection_pages (même collection) │ │ │ │ id │ title │ parent_id │ │ ───┼────────────────┼───────────────│ │ 1 │ Refonte UI │ NULL │ ← Parent │ 2 │ Header │ 1 │ ← Sub-item de 1 │ 3 │ Dark mode │ 1 │ ← Sub-item de 1 │ 4 │ Tests header │ 2 │ ← Sub-sub-item │ │ │ Activation: │ │ 1. enable_sub_items(collection_id) │ │ 2. Crée 2 propriétés relation auto: │ │ - "Parent item" → même collection│ │ - "Sub-item" (inverse) │ │ 3. Board affiche sub-items indentés │ └──────────────────────────────────────┘ ``` ### 10.2 Dépendances ``` ┌───────────────────────────────────────────┐ │ Tâche A ──blocks──► Tâche B │ │ Tâche B ──blocked_by──► Tâche A │ │ │ │ Contraintes: │ │ - B ne peut pas être "Done" si A ≠ "Done"│ │ - Timeline affiche les flèches │ │ │ │ check_dependency_constraint(): │ │ if new_status == "Done": │ │ blocked = get_blocked_pages(page_id) │ │ if any(not done for blocked): │ │ raise DependencyError(...) │ └───────────────────────────────────────────┘ ``` --- ## 11. My Tasks & Dashboard unifié ``` ┌──────────────────────────────────────────────────────┐ │ MY TASKS │ │ │ │ ┌──────────────────────────────────────────────┐ │ │ │ 📋 Projet Alpha [3 tâches]│ │ │ │ ├─ Design maquette 🟡 En cours 15 juil │ │ │ │ ├─ API endpoint 🔴 Bloqué 20 juil │ │ │ │ └─ Tests UI ⚪ Backlog — │ │ │ │ │ │ │ │ 📋 Daily Tasks [2] │ │ │ │ ├─ Réunion standup 🟢 Done 10 juil │ │ │ │ └─ Revue de code 🟡 En cours 12 juil │ │ │ └──────────────────────────────────────────────┘ │ │ │ │ Algorithme: │ │ 1. Scanner toutes les collections du workspace │ │ 2. Filtrer pages où: │ │ - Une propriété "Person" = current_user │ │ - Status ≠ "Done" / "Archived" │ │ 3. Grouper par collection │ │ 4. Trier par Due Date ASC │ └──────────────────────────────────────────────────────┘ ``` --- ## 12. Éditeur de blocs ### 12.1 Types de blocs ``` ┌──────────────────────────────────────────────────┐ │ BLOCK TYPES │ │ │ │ Texte: │ │ ├─ text (paragraph) │ │ ├─ heading_1, heading_2, heading_3, heading_4 │ │ ├─ bulleted_list, numbered_list │ │ ├─ to_do (checkbox) │ │ ├─ toggle (repliable) │ │ └─ quote │ │ │ │ Média: │ │ ├─ image (resizable, alignable) │ │ ├─ video (embed) │ │ ├─ file (attachment) │ │ ├─ bookmark (link preview) │ │ └─ code (syntax highlighting) │ │ │ │ Layout: │ │ ├─ divider │ │ ├─ callout (icône + fond coloré) │ │ └─ column (2, 3, 4, 5 colonnes) │ │ │ │ Data: │ │ ├─ inline_database (collection embarquée) │ │ └─ linked_database (vue d'une collection) │ └──────────────────────────────────────────────────┘ ``` ### 12.2 Format de stockage ```json { "blocks": [ { "id": "b1", "type": "heading_2", "content": "Architecture", "children": [] }, { "id": "b2", "type": "text", "content": "FlowDeck utilise FastAPI...", "children": [] }, { "id": "b3", "type": "toggle", "content": "Détails techniques", "children": [ { "id": "b3a", "type": "bulleted_list", "content": "SQLite WAL mode", "children": [] }, { "id": "b3b", "type": "bulleted_list", "content": "HTMX + Alpine.js", "children": [] } ] }, { "id": "b4", "type": "code", "content": "print('Hello')", "language": "python", "children": [] }, { "id": "b5", "type": "callout", "content": "Note importante", "icon": "💡", "color": "blue", "children": [] }, { "id": "b6", "type": "divider", "content": "", "children": [] } ] } ``` --- ## 13. Multi-utilisateurs & Collaboratif ### 13.1 Modèle de workspace ``` ┌─────────────────────────────────────────────────────────┐ │ WORKSPACE "Foxy Dev Team" │ │ │ │ Members: │ │ ├─ bruno (owner) — tout │ │ ├─ marie (admin) — gérer membres, config │ │ ├─ paul (editor) — CRUD pages │ │ └─ julie (viewer) — lecture seule │ │ │ │ Collections: │ │ ├─ Tasks ─┬─ permissions: workspace │ │ │ └─ visibilité: tous les membres │ │ ├─ Roadmap─ permissions: admin+editor │ │ │ └─ visibilité: bruno, marie, paul │ │ └─ HR ──── permissions: owner only │ │ └─ visibilité: bruno uniquement │ │ │ │ Collaboration features: │ │ ├─ Présence en temps réel (qui est en ligne) │ │ ├─ Verrouillage optimiste (dernier qui sauve gagne) │ │ ├─ Historique des modifications par page │ │ ├─ Commentaires avec @mentions │ │ └─ Notifications (changement de statut, assignation) │ └─────────────────────────────────────────────────────────┘ ``` ### 13.2 Gestion des conflits ``` Stratégie: "Last write wins" avec historique 1. Alice ouvre la page → charge version v=42 2. Bob ouvre la page → charge version v=42 3. Alice sauvegarde → v=43 (succès) 4. Bob sauvegarde → v=43 (succès, écrase Alice) → L'historique conserve le snapshot v=42→v=43 d'Alice Alternative future: Operational Transformation (OT) ou CRDT pour du vrai temps réel (complexité ++) ``` --- ## 14. Déploiement ### 14.1 Docker Compose (production) ```yaml services: flowdeck: build: . container_name: flowdeck ports: - "8080:8080" environment: - GITEA_URL=https://git.dracodev.net - GITEA_TOKEN=${GITEA_TOKEN} - GITEA_OAUTH_CLIENT_ID=${GITEA_OAUTH_CLIENT_ID} - GITEA_OAUTH_CLIENT_SECRET=${GITEA_OAUTH_CLIENT_SECRET} - APP_SECRET_KEY=${APP_SECRET_KEY} - DATABASE_URL=sqlite:////data/flowdeck.db - WEBHOOK_BASE_URL=${WEBHOOK_BASE_URL:-http://localhost:8080} volumes: - flowdeck_data:/data restart: unless-stopped # Optionnel: PostgreSQL pour le futur # postgres: # image: postgres:16-alpine # environment: # POSTGRES_USER: flowdeck # POSTGRES_PASSWORD: ${DB_PASSWORD} # POSTGRES_DB: flowdeck # volumes: # - pg_data:/var/lib/postgresql/data volumes: flowdeck_data: # pg_data: ``` ### 14.2 Migration SQLite → PostgreSQL ```bash # Étape 1: Exporter le schéma SQLite vers PostgreSQL pgloader /data/flowdeck.db postgresql://flowdeck:***@localhost/flowdeck # Étape 2: Changer la config # DATABASE_URL=postgresql://flowdeck:***@localhost/flowdeck # Étape 3: Redémarrer docker compose restart flowdeck ``` --- ## 15. Références - [Notion API — Property Object](https://developers.notion.com/reference/property-object) - [Notion Help — Databases](https://www.notion.com/help/intro-to-databases) - [Notion Help — Tasks & Dependencies](https://www.notion.com/help/tasks-and-dependencies) - [Notion Help — Relations & Rollups](https://www.notion.com/help/relations-and-rollups) - [FlowDeck — NOTION_DATABASE_TASKS_GUIDE.md](docs/NOTION_DATABASE_TASKS_GUIDE.md) - [FlowDeck — ROADMAP.md](ROADMAP.md) --- ## 16. Versions à venir ### v4.1.0 — Content Blocks enrichis (priorité standard) - **Callout boxes** — Blocs d'alerte (info, warning, tip, danger) - **Table of Contents** — Auto-généré depuis les headings - **LaTeX / KaTeX** — Formules mathématiques - **Toggle lists** — Listes pliables/dépliables - **Multi-columns** — Mise en page 2-3 colonnes ### v4.2.0 — Export (priorité standard) - Export **Markdown** avec images - Export **PDF** (via WeasyPrint ou headless Chrome) - Export **HTML** standalone (page auto-suffisante) ### v4.3.0 — Collaboration (priorité basse) - **Commentaires inline** sur les pages - **@mentions** pour notifier des utilisateurs - **Notifications email** (changements, mentions) - **Permissions par workspace** (viewer, editor, admin) ### v5.0.0 — Plateforme avancée (futur) - **AI Assistants** — Génération de contenu, résumés, suggestions - **Embeds** — Vidéos, PDF, Figma, Google Docs - **Automatisations** — Règles déclenchées sur événements (Notion-style) - **Base de données avancée** — Relations inter-collections, rollups - **Kanban flexible** — Colonnes custom, WIP limits - **API publique REST v2** — `/api/v2` (v6.3.0) : Bearer + scopes `read/write/admin`, CRUD complet, pagination, RFC 7807, idempotence, audit, OpenAPI (`/docs`, `docs/openapi-v2.json`) ; `/api/v1` lecture seule (compat) - **Volume Docker persistant** — `/data` monté pour survie des données - **PostgreSQL** — Migration optionnelle pour scaling