diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 48ddc16..20f2168 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -1,101 +1,1308 @@ -# Architecture FlowDeck v1.0.0 +# Architecture FlowDeck — Document Complet v2.0 -## Schéma +> **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) ``` ┌──────────────────────────────────────────────────────────┐ -│ Browser │ -│ HTMX + Alpine.js + SortableJS + CSS Notion (31 KB) │ -└──────────────┬──────────────────────────────────────────┘ - │ HTTP (Jinja2 HTML / JSON API) -┌──────────────▼──────────────────────────────────────────┐ -│ FastAPI (Python) — lifespan │ -│ ┌──────────┬──────────┬──────────┬──────────┐ │ -│ │ dashboard│ board │ notes │ api │ │ -│ │ │ 5 vues │ │ CRUD │ │ -│ │ │ filtres │ │ issues │ │ -│ │ │ tri │ │ checklists│ │ -│ │ │ props │ │ │ │ -│ └────┬─────┴────┬─────┴──────────┴────┬─────┘ │ -│ │ │ │ │ -│ ┌────▼──────────▼─────────────────────▼─────┐ │ -│ │ GiteaClient (httpx async) │ │ -│ └──────────────────┬────────────────────────┘ │ -│ │ │ -│ ┌──────────────────▼────────────────────────┐ │ -│ │ SQLite (WAL mode) │ │ -│ │ boards, cards, notes, col_mapping │ │ -│ │ checklists, checklist_items │ │ -│ │ project_properties, property_values │ │ -│ │ ai_keywords │ │ -│ │ users, user_tokens │ │ -│ └───────────────────────────────────────────┘ │ -└──────────────────────┬───────────────────────────────────┘ - │ HTTPS -┌──────────────────────▼───────────────────────────────────┐ -│ Gitea API REST v1 │ -│ git.dracodev.net │ +│ 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) │ └──────────────────────────────────────────────────────────┘ ``` -## Flux de données +--- -1. **Dashboard** → `/api/v1/user/repos` Gitea → liste projets avec sidebar -2. **Board** → `/api/v1/repos/{o}/{r}/issues` → cartes mappées sur colonnes -3. **5 vues** → `/board/{o}/{r}/view/{kanban|table|status|teamload|detailed}` — fragments HTMX -4. **Filtres** → `status=todo,progress&filter=assignee:X&sort=status:asc` -5. **Drag & drop** → SortableJS → `POST /api/move` → BDD + Gitea labels -6. **Propriétés custom** → `project_properties` + `property_values` tables -7. **AI Keywords** → auto-extraction depuis labels + body → `ai_keywords` table -8. **Sync** → `POST /board/api/sync/{o}/{r}` → fetch + update + extract +## 7. Intégration Gitea -## Tables DB (12) - -| Table | Rôle | -|-------|------| -| `boards` | Config Kanban par projet | -| `cards` | Position issues dans colonnes | -| `col_mapping` | Colonne → label Gitea | -| `notes` | Notes markdown | -| `checklists` | Checklists par issue | -| `checklist_items` | Items de checklist | -| `users` | Utilisateurs locaux | -| `user_tokens` | Tokens Gitea OAuth | -| `project_properties` | Propriétés custom par projet | -| `property_values` | Valeurs par issue | -| `ai_keywords` | Mots-clés auto-extraits | - -## Routes (v1.0.0) +### 7.1 Flux de données ``` -GET / → Dashboard -GET /board/{owner}/{repo} → Board Kanban -GET /board/{owner}/{repo}/view/{view} → Fragment vue -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 +Création Collection Sync bidirectionnelle +────────────────── ────────────────────── -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 modal) -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/... → Ajouter item -PATCH /api/checklist-items/{id} → Toggle item +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 ``` -## Décisions techniques +### 7.2 Adaptateur de compatibilité -- **SQLite**: KISS, un fichier, pas de serveur -- **HTMX + Alpine.js**: Léger, pas de build step, fragments HTML -- **CSS custom**: Design system Notion, pas de framework CSS -- **SortableJS**: Drag & drop natif, pas de React dnd -- **Jinja2 server-side**: Templates côté serveur, HTMX pour interactivité +```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) diff --git a/ROADMAP.md b/ROADMAP.md index 3ddbdbd..2503435 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -3,34 +3,219 @@ > **Vision**: Copie conforme de l'UI/UX Notion, intégrée à Gitea. > **19 images de référence** dans `images/` — analysées le 2026-07-08. -## v0.2.0 ✅ — Base +## Completed + +### v0.2.0 ✅ — Base Dashboard + Board Kanban + Gitea sync + Notes + Docker -## v0.3.0 ✅ — Auth +### v0.3.0 ✅ — Auth OAuth2 Gitea, token par utilisateur, CSRF, rate limiting, webhooks -## v0.4.0 ✅ — UI Notion +### v0.4.0 ✅ — UI Notion Layout 3 zones (sidebar 240px + topbar 44px), dark mode, CSS 31KB, Alpine.js -## v0.5.0 ✅ — Kanban +### v0.5.0 ✅ — Kanban Colonnes, groupes, cartes enrichies, drag&drop, modale détail, checklists, commentaires -## v0.6.0 ✅ — Table View +### v0.6.0 ✅ — Table View 6 colonnes triables, groupes rétractables -## v0.7.0 ✅ — Filtres & Tri +### v0.7.0 ✅ — Filtres & Tri Filtres cumulables AND, status dropdown, sort multi-critères -## v0.8.0 ✅ — Vues Spéciales +### v0.8.0 ✅ — Vues Spéciales Status Overview (donut SVG), Team Load (barres), Detailed Board -## v0.9.0 ✅ — Backend +### v0.9.0 ✅ — Backend Propriétés custom, AI keywords, sync API, 12 tables DB -## v1.0.0 ✅ — Production +### v1.0.0 ✅ — Production - [x] FastAPI lifespan (deprecation fix) - [x] 28 tests — 5 vues, filtres, tris, APIs, CSRF, CORS, DB - [x] CI/CD Gitea Actions (`.gitea/workflows/ci.yml`) - [x] CHANGELOG.md, ARCHITECTURE.md à jour - [x] Version 1.0.0 partout (main.py, api.py, health) - [x] Tag git v1.0.0 + +### v1.1.0 ✅ — Pages & Sidebar +- [x] Pages Markdown avec arbre imbriqué (parent_id, sort_order) +- [x] Block editor mode (content_format='blocks', JSON storage) +- [x] Soft delete → Trash → Restore → Permanent delete +- [x] Drag & drop réorganisation pages +- [x] Sidebar Notion-style avec sections Recents/Private/Shared +- [x] "+ New page" footer button +- [x] Workspace header avec avatar + chevron toggle + +### v1.2.0 ✅ — Éditeur Notion +- [x] Slash menu (/) 15 commandes fonctionnelles +- [x] Navigation clavier (flèches, Enter, Escape) +- [x] Placeholders Notion-style (Heading 1/2/3/4, Toggle, Quote, Code) +- [x] Suppression outline blanc sur les blocs +- [x] Heading 4 support +- [x] Breadcrumbs contextuels (workspace > projet > page) + +--- + +## Upcoming + +### v1.3.0 — Database Concept (Bloc 1 du gap analysis) + +- [ ] **Table `collections`** — base de données abstraite, indépendante de Gitea + - `id`, `name`, `description`, `icon`, `schema_json` + - Optionnellement liée à Gitea (`gitea_owner`, `gitea_repo`) + - Supporte les templates de database +- [ ] **Table `collection_pages`** — remplace `cards`, pages génériques + - `collection_id`, `title`, `position`, `parent_id`, `property_values_json` + - `gitea_issue_id` optionnel pour les pages liées à Gitea + - Auto-propriétés : `created_at`, `updated_at`, `created_by`, `updated_by` +- [ ] **Table `collection_views`** — vues configurables par collection + - `name`, `view_type` (table/board/calendar/timeline/gallery/list) + - `config_json` (filtres, tris, group_by, visible_properties, card_size...) + - `position` pour l'ordre des onglets +- [ ] **Rétrocompatibilité Gitea** — adaptateur `GiteaBoardCompat` + - Les boards Gitea existants deviennent des collections automatiquement + - Migration transparente des `cards` → `collection_pages` +- [ ] **Database lock** — mode lecture seule (`collections.is_locked`) +- [ ] **Database description** — texte riche au-dessus de la database + +### v1.4.0 — Propriétés Avancées (Bloc 2) + +- [ ] **Table `collection_properties`** — remplace `project_properties` + - Liée à `collections` (pas à `project_owner/name`) + - Supporte tous les types Notion +- [ ] **Type `number`** — avec formatage (nombre, %, €, $, £, ¥) +- [ ] **Type `checkbox`** — booléen natif +- [ ] **Type `url`** — lien cliquable +- [ ] **Type `email`** — email cliquable +- [ ] **Type `phone`** — téléphone cliquable +- [ ] **Type `status`** — select avec couleurs forcées (9 couleurs Notion) +- [ ] **Type `files`** — uploads/images attachés aux pages +- [ ] **Type `unique_id`** — ID auto-incrémenté par collection +- [ ] **Type `created_time` / `created_by`** — auto-propriétés +- [ ] **Type `last_edited_time` / `last_edited_by`** — auto-propriétés +- [ ] **Migration** `project_properties` → `collection_properties` + +### v1.5.0 — Relations & Rollups (Bloc 2 suite) + +- [ ] **Type `relation`** — lien bidirectionnel entre collections + - `related_collection_id`, `reverse_name` auto-généré + - Stockage : JSON array de page IDs dans `property_values_json` +- [ ] **Type `rollup`** — agrégation via relation + - Functions : COUNT, COUNT_VALUES, SUM, AVG, MEDIAN, MIN, MAX, RANGE, UNIQUE + - Dépend de `relation_property_id` + `target_property_id` +- [ ] **Type `formula`** — propriétés calculées + - Moteur d'expressions JavaScript-like + - Fonctions : `prop()`, `now()`, `today()`, `if()`, `concat()`, `round()`, etc. +- [ ] **Formula engine** — `services/formula_engine.py` +- [ ] **Rollup engine** — `services/rollup_engine.py` + +### v1.6.0 — Vues Manquantes (Bloc 3) + +- [ ] **Calendar view** — regroupement par propriété Date (jour/semaine/mois) + - Template `calendar_view.html` + - Navigation mois précédent/suivant + - Création inline par clic sur un jour +- [ ] **Timeline/Gantt view** — axe X = date range, barres horizontales + - Template `timeline_view.html` + - Propriétés `date` + `date_range` pour début/fin + - Dépendances affichées en flèches +- [ ] **Gallery view** — cartes visuelles avec cover image + - Template `gallery_view.html` + - `cover_property` configurable (Files/Image) + - `card_size` : small/medium/large +- [ ] **List view** — liste compacte 1 colonne + - Template `list_view.html` + - Preview du contenu sous le titre + +### v1.7.0 — Vues Améliorées (Bloc 3 suite) + +- [ ] **Group_by configurable** — Kanban groupé par n'importe quelle propriété Select/Status + - Plus seulement les labels Gitea + - UI de sélection du group_by dans la barre d'outils +- [ ] **Visible properties par vue** — toggle colonnes dans chaque vue +- [ ] **Card size** — small/medium/large pour board et gallery +- [ ] **Cover image property** — choisir la propriété de cover +- [ ] **Groupes de filtres** — (A OR B) AND C, UI de construction de filtres +- [ ] **Save view as** — sauvegarder état courant comme nouvelle vue + +### v1.8.0 — Sub-items & Dépendances (Bloc 4) + +- [ ] **Sub-items sur cartes Kanban** — `parent_id` dans `collection_pages` + - Auto-référence : une page peut être enfant d'une autre dans la même collection +- [ ] **Affichage imbriqué** — cartes indentées sous le parent dans le board + - CSS `.sub-item` avec indentation visuelle + - Compteur de sous-tâches sur la carte parent +- [ ] **Bouton "+ Sub-item"** — création inline de sous-tâche +- [ ] **État parent = agrégation enfants** — "Done" seulement si tous enfants Done +- [ ] **Dependencies (Blocking ↔ Blocked by)** + - 2e relation auto-référencée + - Propriétés `Blocks` et `Blocked by` créées automatiquement +- [ ] **Contrainte de dépendance** — empêcher "Done" si bloque des tâches non terminées +- [ ] **Flèches de dépendance** — visuel dans Timeline et Kanban + +### v1.9.0 — My Tasks & Dashboard Unifié (Bloc 5) + +- [ ] **Vue `/my-tasks`** — agrège toutes les pages assignées à l'utilisateur + - Scan cross-collection (toutes les databases du workspace) + - Filtre automatique : `Assignee = current_user` ET `Status ≠ Done` +- [ ] **Groupement par collection** — sections par database d'origine +- [ ] **Filtres globaux** — Status, Due Date, Collection +- [ ] **Calendrier intégré** — My Tasks en vue calendrier +- [ ] **Vue Today** — ce qui est dû aujourd'hui +- [ ] **Vue Overdue** — tâches en retard +- [ ] **Vue Upcoming** — 7 prochains jours + +### v2.0.0 — Éditeur Complet & Multi-Utilisateurs (Blocs 6-7) + +- [ ] **Drag blocks to reorder** — réorganisation des blocs dans l'éditeur +- [ ] **Colonnes (2, 3, 4, 5)** — layout multi-colonnes dans l'éditeur +- [ ] **Callout blocks** — bloc avec icône et fond coloré +- [ ] **Toggle blocks** — bloc repliable +- [ ] **Code blocks avec syntax highlighting** — highlight.js +- [ ] **Image resize/alignment** — redimensionnement et alignement +- [ ] **Embeds** — vidéo, fichier, bookmark web +- [ ] **Database templates** — structures de database réutilisables +- [ ] **Page templates** — pages modèles dans une database +- [ ] **Inline databases** — databases embarquées dans une page +- [ ] **Linked databases** — même database affichée dans plusieurs pages +- [ ] **Multi-user workspaces** — collaboration temps réel + - Workspaces partagés avec permissions (read/write/admin) + - User presence (qui est en ligne, qui édite quelle page) + - Historique des modifications par page + - Commentaires sur les pages (pas juste les issues Gitea) +- [ ] **Favorites** — système de favoris fonctionnel +- [ ] **CSV import/export** pour databases +- [ ] **Public sharing** — lien de partage public pour une vue + +### v2.1.0 — Intégrations Avancées (futur) + +- [ ] **API publique** — REST API documentée pour usage externe +- [ ] **Webhooks sortants** — notifier des services externes +- [ ] **n8n integration** — nœud FlowDeck pour workflows +- [ ] **Slack/Discord integration** — notifications dans les channels +- [ ] **Google Calendar sync** — synchronisation bidirectionnelle +- [ ] **Mobile PWA** — Progressive Web App pour mobile +- [ ] **PostgreSQL migration** — si passage multi-tenant + +--- + +## Résumé des phases + +``` +v1.0.0 ✅ v1.1.0 ✅ v1.2.0 ✅ v1.3.0 ⬜ v1.4.0 ⬜ v1.5.0 ⬜ v1.6.0 ⬜ v1.7.0 ⬜ +Production Pages Editor DB Concept Properties Relations Views View+ + ────────────────────────────────────────────── + 52 fonctionnalités identifiées dans le gap analysis + │ + ├─ Bloc 1: Database Concept (v1.3) — 6 items + ├─ Bloc 2: Propriétés avancées (v1.4-1.5) — 14 items + ├─ Bloc 3: Vues manquantes (v1.6-1.7) — 10 items + ├─ Bloc 4: Sub-items & Deps (v1.8) — 7 items + ├─ Bloc 5: My Tasks (v1.9) — 7 items + ├─ Bloc 6: Éditeur complet (v2.0) — 8 items + └─ Bloc 7: Fonctions transversales — +multi-user + +v1.8.0 ⬜ v1.9.0 ⬜ v2.0.0 ⬜ v2.1.0 ⬜ +Sub-items My Tasks Editor+ Future + Multi-user +```