# Architecture FlowDeck — Document Complet v2.0 > **Version:** 2.0 · **Date:** 2026-07-10 · **Auteur:** Hermes-Deepin > **Cible:** Clone Notion intégré à Gitea avec support multi-utilisateurs --- ## 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) │ │ │ │ ├─ dashboard.html — Projets + sidebar │ │ │ │ ├─ board.html — Kanban (wrapper) │ │ │ │ ├─ board_fragment.html— Cartes Kanban │ │ │ │ ├─ table_view.html — Vue table │ │ │ │ ├─ calendar_view.html — Vue calendrier (v1.6) │ │ │ │ ├─ timeline_view.html — Vue Gantt (v1.6) │ │ │ │ ├─ gallery_view.html — Vue galerie (v1.6) │ │ │ │ ├─ list_view.html — Vue liste (v1.6) │ │ │ │ ├─ status_overview.html — Donut SVG │ │ │ │ ├─ team_load.html — Barres stacked │ │ │ │ ├─ detailed_board.html— Board détaillé │ │ │ │ ├─ card_detail.html — Modale détail carte │ │ │ │ ├─ card.html — Carte individuelle │ │ │ │ ├─ page_editor.html — Éditeur de blocs │ │ │ │ ├─ my_tasks.html — Dashboard unifié (v1.9) │ │ │ │ ├─ library.html — Bibliothèque de pages │ │ │ │ ├─ trash.html — Corbeille │ │ │ │ ├─ accounts.html — Gestion comptes │ │ │ │ ├─ notes.html — Notes Markdown │ │ │ │ └─ base.html — Layout commun │ │ │ └─────────────────────────────────────────────────────────┘ │ │ │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ JavaScript (CDN, pas de build step) │ │ │ │ ├─ HTMX 1.9 — AJAX sans JS │ │ │ │ ├─ Alpine.js 3.14 — Interactivité légère │ │ │ │ ├─ SortableJS 1.15 — Drag & drop │ │ │ │ └─ highlight.js — Syntax highlighting (v2.0) │ │ │ └─────────────────────────────────────────────────────────┘ │ │ │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ 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 │ │ │ │ ├─ dashboard.py — / Dashboard │ │ │ │ ├─ board.py — /board/{o}/{r} Board + vues │ │ │ │ │ — /board/api/... APIs board │ │ │ │ ├─ api.py — /api/... CRUD + sync │ │ │ │ ├─ notes.py — /notes/{o}/{r} Notes │ │ │ │ ├─ auth.py — /auth/... OAuth2 │ │ │ │ ├─ webhooks.py — /webhooks/... Gitea hooks │ │ │ │ ├─ my_tasks.py — /my-tasks (v1.9) │ │ │ │ ├─ pages.py — /pages/... Pages CRUD │ │ │ │ ├─ collections.py — /db/... Collections │ │ │ │ └─ editor.py — /api/editor/... Block editor │ │ │ └─────────────────────────────────────────────────────────┘ │ │ │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ 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 │ │ │ │ │ │ ├─ 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 ``` --- ## 5. Frontend ### 5.1 Layout ``` ┌──────────────────────────────────────────────────────────────┐ │ TOPBAR (44px, sticky) │ │ ┌──────────┬──────────────────────────────────┬────────────┐ │ │ │ Logo │ Breadcrumbs: WS > Collection │ Share... │ │ │ │ FlowDeck │ (ou WS > Collection > Page) │ Settings │ │ │ └──────────┴──────────────────────────────────┴────────────┘ │ ├────────────┬─────────────────────────────────────────────────┤ │ SIDEBAR │ CONTENU PRINCIPAL │ │ (240px) │ │ │ │ ┌─────────────────────────────────────────────┐│ │ Workspace │ │ DATABASE VIEW ││ │ header │ │ ┌──────┬──────┬──────┬──────┬──────────┐ ││ │ │ │ │Title │Statut│Priori│Assign│Due Date │ ││ │ 🔍 Search │ │ ├──────┼──────┼──────┼──────┼──────────┤ ││ │ │ │ │Tâche1│Todo │ P1 │Bruno │15 juil │ ││ │ ▼ Recents │ │ │Tâche2│Doing │ P2 │ — │20 juil │ ││ │ · Proj A │ │ │Tâche3│Done │ P3 │Marie │10 juil │ ││ │ · Proj B │ │ └──────┴──────┴──────┴──────┴──────────┘ ││ │ │ │ [+ New] [Filter] [Sort] [Search] [...] ││ │ ▼ Private │ └─────────────────────────────────────────────┘│ │ · Notes │ │ │ │ ┌─ Barre d'outils ────────────────────────────┐│ │ ▼ Shared │ │ [Table▼] [Kanban] [Calendar] [Gallery] ... ││ │ · ... │ │ [Group▼] [Filter▼] [Sort▼] [Properties▼] ││ │ │ └─────────────────────────────────────────────┘│ │ ────────── │ │ │ [+New page]│ ┌─ Contenu de la vue ─────────────────────────┐│ │ (sticky │ │ (table / board / calendar / gallery / ...) ││ │ footer) │ └─────────────────────────────────────────────┘│ └────────────┴─────────────────────────────────────────────────┘ ``` ### 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) │ └──────────────────────────────────────────────────────────┘ ``` --- ## 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) - [Notion Data Sources (2025)](https://www.notionapps.com/blog/notion-data-sources-update-2025/) - [react-notion-x](https://github.com/NotionX/react-notion-x) - [FlowDeck — NOTION_DATABASE_TASKS_GUIDE.md](docs/NOTION_DATABASE_TASKS_GUIDE.md) - [FlowDeck — ROADMAP.md](ROADMAP.md)