- Router api_v2.py (~100 endpoints) : tokens, users, workspaces/members, collections, pages, proprietes, vues/dashboards, commentaires/mentions, notifications, favoris/tags/recents, partage/publish, historique, sprints, templates, export/import, forges, recherche FTS, admin, webhooks CRUD - Helpers api_v2_helpers.py : Bearer unifie (sha256/expires_at/extension_devices), scopes hierarchiques read<write<admin, pagination + X-Total-Count, ISO-8601, RFC 7807, idempotence, audit, rate-limit par token - Migration 20 : api_tokens.scopes/expires_at, webhook_deliveries, api_audit_log, idempotency_keys - main.py : handler d'erreurs unifie StarletteHTTPException, /docs + /redoc - config : PUBLIC_API_INSECURE_OK (dev only), API_V2_RATE_LIMIT_PER_TOKEN - OpenAPI docs/openapi-v2.json (402 chemins), tests/test_public_api_v2.py (24) - Docs : CHANGELOG (v6.2.0/6.2.1 clipper + v6.3.0), ROADMAP, API_GUIDE_V6, V6_Web_Clipper, README, ARCHITECTURE, /help - Suite complete 668 verte, ruff OK
81 KiB
Architecture FlowDeck — Document Complet v4.0
Version: 4.0 · Date: 2026-07-20 · Auteur: Hermes-Deepin Cible: Clone Notion intégré à Gitea/GitHub — multi-comptes, multi-intégrations Version déployée: v4.0.x (production : https://flowdeck.dracodev.net)
Table des matières
- Vue d'ensemble
- Architecture système
- Modèle de données
- Routage & API
- Frontend
- Système d'authentification
- Intégration Gitea
- Système de propriétés
- Système de vues
- Sub-items & Dépendances
- My Tasks & Dashboard unifié
- Éditeur de blocs
- Multi-utilisateurs & Collaboratif
- Déploiement
- Références
1. Vue d'ensemble
FlowDeck est un clone de Notion intégré à Gitea. Il recrée l'expérience Notion — databases, propriétés typées, vues multiples, sub-items, dépendances, éditeur de blocs — tout en restant connecté aux repositories et issues Gitea.
Principes architecturaux
- Serveur-side rendering — Jinja2 + HTMX, pas de SPA lourde
- SQLite-first — un fichier, zéro maintenance, migration PostgreSQL triviale si scaling futur
- Gitea-native — chaque database peut être liée à un repo Gitea, les issues deviennent des pages
- Progressive enhancement — fonctionnel sans JS, enrichi avec Alpine.js + SortableJS
- Propriétés en JSON — flexibilité maximale du schéma sans migration de table
2. Architecture système
┌──────────────────────────────────────────────────────────────────┐
│ CLIENT (Browser) │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Templates Jinja2 (SSR) │ │
│ │ ├─ base.html — Layout Notion commun (sidebar) │ │
│ │ ├─ _header.html — Topbar + breadcrumbs │ │
│ │ ├─ _workspace_tree_macro.html — Macro arbre sidebar │ │
│ │ ├─ landing.html — Landing page non-auth │ │
│ │ ├─ workspaces.html — Page Workspaces (locale/Gitea) │ │
│ │ ├─ library.html — Bibliothèque multi-onglets │ │
│ │ ├─ local_workspace.html — Workspace local (explorer) │ │
│ │ ├─ page_editor.html — Éditeur de blocs │ │
│ │ ├─ trash.html — Corbeille (local only) │ │
│ │ ├─ settings.html — Settings / My Account / Admin │ │
│ │ ├─ accounts.html — Gestion comptes admin │ │
│ │ ├─ board.html — Kanban (wrapper) │ │
│ │ ├─ board_fragment.html — Cartes Kanban │ │
│ │ ├─ table_view.html — Vue table │ │
│ │ ├─ login.html — Login/Register (local + OAuth) │ │
│ │ ├─ public_page.html — Page publiée (/p/slug) │ │
│ │ └─ (legacy: dashboard, detailed_board, card_detail, etc.) │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ JavaScript (servi localement, pas de CDN externe) │ │
│ │ ├─ Alpine.js 3.14 — Interactivité légère │ │
│ │ ├─ HTMX 1.9 — AJAX sans JS (legacy) │ │
│ │ ├─ SortableJS 1.15 — Drag & drop │ │
│ │ └─ Prism.js — Syntax highlighting │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ CSS custom — Design system Notion, dark theme, 31+ KB │ │
│ └─────────────────────────────────────────────────────────┘ │
└──────────────────────────┬───────────────────────────────────────┘
│ HTTP (HTML fragments / JSON)
┌──────────────────────────▼───────────────────────────────────────┐
│ FASTAPI (Python 3.11+) │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ MIDDLEWARE │ │
│ │ ├─ SessionMiddleware — Sessions utilisateur │ │
│ │ ├─ CSRFMiddleware — Protection CSRF │ │
│ │ ├─ CORSMiddleware — CORS (* pour dev local) │ │
│ │ └─ RateLimiter — Rate limiting par IP │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ ROUTERS │ │
│ │ ├─ main.py — FastAPI app, lifespan, CORS, 404 │ │
│ │ ├─ dashboard.py — /, /workspaces, /library, /help │ │
│ │ │ — /trash, /settings, /accounts │ │
│ │ │ — /p/{slug} (pages publiques) │ │
│ │ │ — _sidebar_data() — données sidebar │ │
│ │ ├─ board.py — /board/{o}/{r} Board + vues │ │
│ │ │ — /board/api/pages CRUD pages │ │
│ │ │ — /board/api/trash Trash │ │
│ │ ├─ api.py — /api/... CRUD + sync │ │
│ │ ├─ auth.py — /auth/... OAuth2 + local │ │
│ │ ├─ local_workspace.py— /api/local-workspace CRUD + upload │ │
│ │ ├─ my_tasks.py — /my-tasks Dashboard │ │
│ │ ├─ library.py — /api/library/* API bibliothèque│ │
│ │ ├─ pages.py — /pages/... Pages CRUD │ │
│ │ ├─ collections.py — /db/... Collections │ │
│ │ ├─ editor.py — /api/editor/... Block editor │ │
│ │ ├─ public_api.py — /api/v1 Public API v1 │ │
│ │ ├─ api_v2.py — /api/v2 Public API v2 │ │
│ │ │ — Bearer + scopes, CRUD complet │ │
│ │ ├─ web_clipper.py — /api/v2/web-clipper Web Clipper │ │
│ │ ├─ permissions.py — /api/v2 (ACL) Permissions │ │
│ │ ├─ workspace.py — Workspaces API + Gitea projets │ │
│ │ ├─ webhooks.py — /webhooks/... Gitea hooks │ │
│ │ └─ admin.py — /api/admin/* Admin users │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ SERVICES │ │
│ │ ├─ GiteaClient (httpx async) — API Gitea │ │
│ │ ├─ FormulaEngine — Moteur de formules │ │
│ │ ├─ RollupEngine — Agrégations cross-DB │ │
│ │ ├─ CollectionSync — Sync Gitea ↔ collections │ │
│ │ ├─ DependencyChecker — Contraintes dépendances │ │
│ │ ├─ PermissionManager — ACL multi-user │ │
│ │ └─ SearchEngine — FTS5 recherche full-text │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ DATA LAYER │ │
│ │ ┌─────────────────────────────────────────────────┐ │ │
│ │ │ SQLite (WAL mode, foreign keys ON) │ │ │
│ │ │ │ │ │
│ │ │ ┌─ collections (v1.3) │ │ │
│ │ │ ├─ collection_pages (v1.3) │ │ │
│ │ │ ├─ collection_views (v1.3) │ │ │
│ │ │ ├─ collection_properties (v1.4) │ │ │
│ │ │ ├─ boards (legacy) │ │ │
│ │ │ ├─ cards (legacy) │ │ │
│ │ │ ├─ col_mapping (legacy) │ │ │
│ │ │ ├─ notes │ │ │
│ │ │ ├─ checklists │ │ │
│ │ │ ├─ checklist_items │ │ │
│ │ │ ├─ project_properties (legacy → collection_properties) │
│ │ │ ├─ property_values (legacy → collection_pages) │
│ │ │ ├─ ai_keywords │ │ │
│ │ │ ├─ pages │ │ │
│ │ │ ├─ page_shares (v4.0) │ │ │
│ │ │ ├─ recents (v4.0) │ │ │
│ │ │ ├─ tags (v4.0) │ │ │
│ │ │ ├─ page_tags (v4.0) │ │ │
│ │ │ ├─ gitea_private_pages (v4.0) │ │ │
│ │ │ ├─ users │ │ │
│ │ │ ├─ user_tokens │ │ │
│ │ │ ├─ workspaces (v2.0) │ │ │
│ │ │ ├─ workspace_members (v2.0) │ │ │
│ │ │ ├─ page_history (v2.0) │ │ │
│ │ │ ├─ comments (v2.0) │ │ │
│ │ │ ├─ favorites (v2.0) │ │ │
│ │ │ └─ database_templates (v2.0) │ │ │
│ │ └─────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────┘ │
└──────────────────────────┬───────────────────────────────────────┘
│ HTTPS
┌──────────────────────────▼───────────────────────────────────────┐
│ GITEA API REST v1 │
│ git.dracodev.net │
│ ├─ /api/v1/user/repos — Liste projets │
│ ├─ /api/v1/repos/{o}/{r}/issues — Issues │
│ ├─ /api/v1/repos/{o}/{r}/labels — Labels │
│ ├─ /api/v1/repos/{o}/{r}/milestones — Milestones │
│ ├─ /api/v1/user — Infos utilisateur │
│ └─ /api/v1/orgs/{org}/repos — Repos d'organisation │
└──────────────────────────────────────────────────────────────────┘
3. Modèle de données
3.1 Tables Core (existantes v1.0)
-- 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)
-- 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)
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)
CREATE TABLE workspaces (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
owner_id INTEGER NOT NULL REFERENCES users(id),
settings_json TEXT NOT NULL DEFAULT '{}',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE workspace_members (
id INTEGER PRIMARY KEY AUTOINCREMENT,
workspace_id INTEGER NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
user_id INTEGER NOT NULL REFERENCES users(id),
role TEXT NOT NULL DEFAULT 'editor',
-- admin | editor | commenter | viewer
joined_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE(workspace_id, user_id)
);
CREATE TABLE page_history (
id INTEGER PRIMARY KEY AUTOINCREMENT,
page_id INTEGER NOT NULL REFERENCES collection_pages(id) ON DELETE CASCADE,
user_id INTEGER REFERENCES users(id),
change_type TEXT NOT NULL,
-- created | updated | moved | deleted | restored
snapshot_json TEXT NOT NULL DEFAULT '{}',
-- Snapshot des valeurs avant modification
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE comments (
id INTEGER PRIMARY KEY AUTOINCREMENT,
page_id INTEGER NOT NULL REFERENCES collection_pages(id) ON DELETE CASCADE,
user_id INTEGER NOT NULL REFERENCES users(id),
body TEXT NOT NULL DEFAULT '',
parent_id INTEGER REFERENCES comments(id),
resolved BOOLEAN NOT NULL DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE favorites (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL REFERENCES users(id),
page_id INTEGER REFERENCES collection_pages(id),
collection_id INTEGER REFERENCES collections(id),
-- L'un des deux est NOT NULL
position INTEGER NOT NULL DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE(user_id, page_id, collection_id)
);
CREATE TABLE database_templates (
id INTEGER PRIMARY KEY AUTOINCREMENT,
workspace_id INTEGER REFERENCES workspaces(id),
name TEXT NOT NULL,
description TEXT DEFAULT '',
schema_json TEXT NOT NULL DEFAULT '[]',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
created_by INTEGER REFERENCES users(id)
);
CREATE TABLE page_templates (
id INTEGER PRIMARY KEY AUTOINCREMENT,
collection_id INTEGER NOT NULL REFERENCES collections(id) ON DELETE CASCADE,
name TEXT NOT NULL DEFAULT 'Default',
property_values_json TEXT NOT NULL DEFAULT '{}',
content_json TEXT DEFAULT '[]',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
3.5 Diagramme des relations
┌──────────────┐ ┌──────────────────┐ ┌───────────────────┐
│ workspaces │──1:N──│ workspace_members│──N:1──│ users │
│ │ │ - role │ │ - login │
│ - name │ │ - joined_at │ │ - email │
│ - owner_id │ └──────────────────┘ │ - avatar_url │
└──────┬───────┘ │ - is_admin │
│ 1:N └──────┬────┬────────┘
▼ │ │
┌──────────────┐ ┌──────────────────┐ │ │ (author)
│ collections │──1:N──│ collection_pages │◄─N:1───────┘ │
│ │ │ │ │
│ - name │ │ - title │ │
│ - schema_json│ │ - parent_id ────┼─── self-ref │
│ - icon │ │ - gitea_issue_id│ │
│ - gitea_* │ │ - property_values│ │
│ - is_locked │ │ _json │ │
└──────┬───────┘ └────────┬─────────┘ │
│ 1:N │ 1:N │
▼ ▼ │
┌──────────────────┐ ┌──────────────────┐ │
│collection_views │ │ comments │ │
│ - view_type │ │ - body │ │
│ - config_json │ │ - resolved │ │
└──────────────────┘ │ - parent_id │ │
└────────┬─────────┘ │
│ │
┌──────────────────┐ │ │
│collection_properties│ │ │
│ - name │ │ │
│ - prop_type │◄─────────┘ │
│ - options_json │ │
│ - related_collection_id ──┐ │
│ - relation_property_id │ self-ref │
│ - target_property_id │ │
│ - formula_expression │ │
└────────────────────────────┘ │
│
┌──────────────────┐ ┌──────────────────┐ │
│ page_history │ │ favorites │ │
│ - change_type │ │ - position │ │
│ - snapshot_json │ └──────────────────┘ │
│ - user_id ──────┼────────────────────────────────────────┘
└──────────────────┘
4. Routage & API
4.1 Routes existantes (v1.2)
GET / → Dashboard
GET /board/{owner}/{repo} → Board Kanban
GET /board/{owner}/{repo}/view/{view} → Fragment vue (kanban|table|status|teamload|detailed)
GET /board/{owner}/{repo}?view=&status=&filter=&sort= → Avec paramètres
GET /board/api/properties/{o}/{r} → Liste propriétés
POST /board/api/properties/{o}/{r} → Créer propriété
DEL /board/api/properties/{o}/{r} → Supprimer propriété
POST /board/api/properties/{o}/{r}/values → Set valeur propriété
GET /board/api/ai-keywords/{o}/{r} → Liste keywords
POST /board/api/ai-keywords/{o}/{r}/extract → Extraire keywords
POST /board/api/sync/{o}/{r} → Sync complète
GET /api/health → Health check
GET /api/stats → Statistiques
GET /api/projects → Projets JSON
POST /api/move → Déplacer carte
GET /api/issues/{o}/{r}/{id} → Issue detail (JSON)
GET /api/issues/{o}/{r}/{id}?format=html → Issue detail (HTML)
POST /api/issues/{o}/{r} → Créer issue
PATCH /api/issues/{o}/{r}/{id} → Update issue
POST /api/checklists/{o}/{r}/{id} → Créer checklist
POST /api/checklist-items/{o}/{r}/{id}/{cl_id} → Ajouter item
PATCH /api/checklist-items/{id} → Toggle item
GET /notes/{o}/{r} → Lire notes
POST /notes/{o}/{r} → Sauvegarder notes
GET /auth/login → Login OAuth2 Gitea
GET /auth/callback → Callback OAuth2
GET /auth/logout → Déconnexion
POST /webhooks/gitea → Webhook Gitea
GET /pages/{page_id} → Voir page
POST /board/api/pages → Créer page
GET /board/api/pages/{id} → Get page JSON
PUT /board/api/pages/{id} → Update page
PUT /board/api/pages/{id}/move → Déplacer page (drag & drop)
POST /board/api/pages/{id}/blocks → Save block editor
DEL /board/api/pages/{id} → Soft delete
GET /board/trash → Page corbeille
GET /board/api/trash → Liste trash
POST /board/api/trash/{id}/restore → Restaurer
DEL /board/api/trash/{id} → Suppression définitive
GET /library → Bibliothèque
GET /accounts → Gestion comptes
4.2 Routes Database (v1.3-v1.5)
GET /db → Liste collections du workspace
POST /db → Créer collection
GET /db/{collection_id} → Vue par défaut de la collection
GET /db/{collection_id}/view/{view_id} → Vue spécifique
GET /db/{collection_id}/fragment/{view_type} → Fragment HTMX
POST /db/{collection_id}/pages → Créer page
GET /db/{collection_id}/pages/{page_id} → Détail page
PUT /db/{collection_id}/pages/{page_id} → Update page
DELETE /db/{collection_id}/pages/{page_id} → Soft delete page
GET /db/{collection_id}/properties → Liste propriétés
POST /db/{collection_id}/properties → Créer propriété
PUT /db/{collection_id}/properties/{prop_id} → Update propriété
DELETE /db/{collection_id}/properties/{prop_id} → Supprimer propriété
GET /db/{collection_id}/views → Liste vues
POST /db/{collection_id}/views → Créer vue
PUT /db/{collection_id}/views/{view_id} → Update config vue
DELETE /db/{collection_id}/views/{view_id} → Supprimer vue
POST /db/{collection_id}/views/duplicate/{id} → Dupliquer vue
POST /db/{collection_id}/views/save-as → Sauvegarder état courant
GET /db/{collection_id}/templates → Page templates
POST /db/{collection_id}/templates → Créer template
POST /db/{collection_id}/templates/{tid}/apply → Appliquer template
POST /db/{collection_id}/import/csv → Import CSV
GET /db/{collection_id}/export/csv → Export CSV
4.3 Routes Sub-items & Dépendances (v1.8)
GET /db/{collection_id}/pages/{page_id}/sub-items → Liste sub-items
POST /db/{collection_id}/pages/{page_id}/sub-items → Créer sub-item
GET /db/{collection_id}/pages/{page_id}/dependencies → Bloque/Bloqué par
POST /db/{collection_id}/pages/{page_id}/dependencies → Ajouter dépendance
DELETE /db/{collection_id}/pages/{page_id}/dependencies/{dep_id} → Supprimer
POST /db/{collection_id}/pages/{page_id}/check-deps → Vérifier contraintes
4.4 Routes My Tasks (v1.9)
GET /my-tasks → Dashboard unifié
GET /my-tasks?view=calendar → Vue calendrier
GET /my-tasks?view=overdue → Tâches en retard
GET /my-tasks?view=upcoming&days=7 → 7 prochains jours
GET /my-tasks?filter=collection:3 → Filtrer par collection
4.5 Routes Multi-Utilisateurs (v2.0)
GET /workspace/{ws_id} → Workspace
POST /workspace → Créer workspace
PUT /workspace/{ws_id}/settings → Settings
POST /workspace/{ws_id}/members → Inviter membre
PUT /workspace/{ws_id}/members/{user_id}/role → Changer rôle
DELETE /workspace/{ws_id}/members/{user_id} → Retirer membre
GET /db/{collection_id}/pages/{page_id}/history → Historique
GET /db/{collection_id}/pages/{page_id}/comments→ Commentaires
POST /db/{collection_id}/pages/{page_id}/comments→ Ajouter commentaire
GET /favorites → Liste favoris
POST /favorites → Ajouter favori
DELETE /favorites/{id} → Retirer favori
4.6 Routes Sidebar & Library (v4.0)
GET /library → Bibliothèque dynamique
GET /api/library/recents → Pages récentes
GET /api/library/favorites → Favoris
GET /api/library/shared → Pages partagées
GET /api/library/published → Pages publiées
GET /api/library/private → Pages privées
GET /api/library/workspace → Pages du workspace
GET /api/local-workspace/items → Arborescence workspace
POST /api/local-workspace/items → Créer page/dossier
PUT /api/local-workspace/items/{id} → Renommer
DELETE /api/local-workspace/items/{id} → Soft delete
POST /api/local-workspace/upload → Upload fichier
GET /api/files/{ws_id}/{filename} → Servir fichier uploadé
GET /help → Page d'aide complète
GET /api/public/pages/{slug} → Page publique
4.7 Routes Share & Publish (v4.0)
POST /api/pages/{id}/share → Partager une page
DELETE /api/pages/{id}/share/{share_id} → Retirer partage
GET /api/pages/{id}/shares → Liste partages
POST /api/pages/{id}/publish → Publier page
DELETE /api/pages/{id}/publish → Dépublier
5. Système d'authentification (v4.0)
FlowDeck supporte 3 méthodes d'authentification et la connexion d'intégrations multiples à un même compte local.
5.1 Types de comptes
| Type | Auth | Workspaces | Intégrations |
|---|---|---|---|
| Local pur | email + password | Locaux uniquement | Aucune |
| Local + intégrations | email + password | Locaux + distants | Gitea et/ou GitHub |
| OAuth pur | Gitea ou GitHub | Distants uniquement | Non-déconnectable |
5.2 Comportement du sidebar selon le type de compte
LOCAL PUR:
├─ 📁 [nom workspace local]
├─ 📅 Meetings
├─ 🕒 Recents
├─ ⭐ Favorites
├─ 🤖 Agents
├─ 👥 Shared ← visible si pages partagées
├─ 🌐 Published ← visible si pages publiées
├─ (Private caché)
└─ 📚 Library / ✅ My Tasks / 🗑️ Trash / ❓ Help
LOCAL + GITEA/GITHUB:
├─ 🔗 [owner/repo] ← workspace distant actif
├─ 📅 Meetings
├─ 🕒 Recents
├─ ⭐ Favorites
├─ 🤖 Agents
├─ 👥 Shared
├─ 🌐 Published
├─ 🔒 Private ← visible (fichiers locaux liés au projet distant)
└─ 📚 / ✅ / 🗑️ / ❓
OAUTH PUR (GITEA/GITHUB):
├─ 🔗 [owner/repo] ← identité = compte Gitea/GitHub
├─ 🕒 Recents
├─ ⭐ Favorites
├─ 👥 Shared
├─ 🌐 Published
├─ (Private caché)
└─ 📚 / ✅ / ❓ (Trash caché — pas de workspace local)
5.3 Règles d'affichage
- Section Private : visible UNIQUEMENT si compte local + workspace distant actif
- Trash : visible si
auth_method == 'local'ou si des pages locales existent - Boutons New File/New Folder : cachés si
has_active_workspace == False - Message "No workspace open" : affiché dans la section Workspace si aucun workspace actif
- Header sidebar : toujours le compte local si auth_method=local, même avec intégrations
5.4 cookie flowdeck_workspace
""→ aucun workspace actif"42"→ workspace local ID 42"gitea:owner:repo"→ workspace distant Gitea- Nettoyage automatique si cookie invalide (workspace supprimé ou autre user)
6. Frontend
5.1 Layout
┌──────────────────────────────────────────────────────────────┐
│ TOPBAR (unified-header) │
│ ┌──────┬───────────────────────────────────────┬───────────┐ │
│ │ «» │ Breadcrumbs: 🏠 Workspaces │ Share ⚙ │ │
│ └──────┴───────────────────────────────────────┴───────────┘ │
├────────────┬─────────────────────────────────────────────────┤
│ SIDEBAR │ CONTENU PRINCIPAL │
│ (240px) │ │
│ │ ┌─ Help Page ────────────────────────────────┐│
│ ┌────────┐ │ │ ┌──────────────┐ ┌──────────────┐ ││
│ │ Header │ │ │ │ 🚀 Getting │ │ 📝 Pages │ (...) ││
│ │ [D] │ │ │ │ Started │ │ & Editor │ ││
│ │ Draco │ │ │ └──────────────┘ └──────────────┘ ││
│ └────────┘ │ └─────────────────────────────────────────────┘│
│ │ │
│ 🏠 💬 📨 🔍│ ┌─ Library ─────────────────────────────────┐│
│ │ │ [Recents|Favorites|Shared|Published|...] ││
│ 📁 Worksp. │ │ ┌──────┬──────────┬──────────┬───────────┐ ││
│ 📄 New Pg │ │ │Name │Created by│Source │Last edited│ ││
│ 📁 New Fl │ │ ├──────┼──────────┼──────────┼───────────┤ ││
│ 📄 page 1 │ │ │doc A │Draco │Local │10 min ago │ ││
│ 📄 page 2 │ │ │doc B │bruno │Gitea │2 hours ago│ ││
│ │ │ └──────┴──────────┴──────────┴───────────┘ ││
│ 📅 Meeting │ └─────────────────────────────────────────────┘│
│ 🕒 Recents │ │
│ ⭐ Favoris │ ┌─ Workspaces ───────────────────────────────┐│
│ 🤖 Agents │ │ ┌──────────┐ ┌──────────┐ ┌───┐ ┌───┐ ││
│ 👥 Shared │ │ │Project A │ │Project B │ │ + │ │ + │ ││
│ 🌐 Publ. │ │ │3 pages │ │12 pages │ │ │ │ │ ││
│ │ │ └──────────┘ └──────────┘ └───┘ └───┘ ││
│ ══════════ │ └─────────────────────────────────────────────┘│
│ 📚 Library │ │
│ ✅ My Task │ ┌─ Settings ─────────────────────────────────┐│
│ 🗑️ Trash │ │ My Account | Admin | Integrations ││
│ ❓ Help │ │ [Upload photo] [Full Name] [Email] [...] ││
│ │ └─────────────────────────────────────────────┘│
│ 💬 [+📄] │ │
└────────────┴─────────────────────────────────────────────────┘
5.2 Vues supportées
| Vue | Icône | Template | Description |
|---|---|---|---|
| Table | 📊 | table_view.html |
Colonnes = propriétés, lignes = pages |
| Board (Kanban) | 📋 | board_fragment.html |
Groupé par propriété Select |
| Calendar | 📅 | calendar_view.html |
Groupé par propriété Date |
| Timeline (Gantt) | 📈 | timeline_view.html |
Barres horizontales sur axe Date |
| Gallery | 🖼️ | gallery_view.html |
Cartes visuelles avec cover |
| List | 📝 | list_view.html |
Compact, 1 colonne + preview |
5.3 Composants réutilisables
- Property editor — éditeur inline par type (select dropdown, date picker, text input...)
- Filter builder — UI de construction de filtres (propriété + opérateur + valeur)
- Sort builder — UI de tri multi-critères
- Group selector — dropdown pour choisir la propriété de groupement
- View tabs — onglets pour switcher entre vues
- Page modal — détail d'une page avec propriétés, checklists, commentaires
- Slash menu — menu contextuel pour l'éditeur de blocs
6. Système d'authentification
6.1 Flux OAuth2 Gitea
User ──→ GET /auth/login ──→ Redirect Gitea OAuth
│
Gitea authorize page
│
User authorize ──→ GET /auth/callback?code=xxx
│
POST /api/v1/login/oauth/access_token
│
┌─────▼──────┐
│ access_token│
└─────┬──────┘
│
GET /api/v1/user
│
┌─────▼──────────────────┐
│ Create/Update: │
│ - users (login, email) │
│ - user_tokens (token) │
│ - session cookie │
└─────────────────────────┘
6.2 Multi-utilisateurs (v2.0)
┌──────────────────────────────────────────────────────────┐
│ PERMISSION MODEL │
│ │
│ Workspace │
│ ├─ Owner (créateur) — tout peut faire │
│ ├─ Admin — gérer membres, config │
│ ├─ Editor — CRUD pages, propriétés │
│ ├─ Commenter — commentaires seulement │
│ └─ Viewer — lecture seule │
│ │
│ Collection (hérite du workspace par défaut) │
│ ├─ Peut être overridé par collection │
│ └─ Database lock → lecture seule pour tout le monde │
│ │
│ Page │
│ ├─ Propriétaire = created_by │
│ ├─ Mention @user → notifie l'utilisateur │
│ └─ Assignee (property type Person) │
└──────────────────────────────────────────────────────────┘
6.5 Modèle de comptes & intégrations (v4.0)
6.5.1 Types de comptes
FlowDeck supporte 3 types de comptes, déterminés par auth_method dans la table users :
┌──────────────┬──────────────────┬─────────────────────────────────────┐
│ auth_method │ Login via │ Identité dans le sidebar │
├──────────────┼──────────────────┼─────────────────────────────────────┤
│ local │ email + password │ Nom local (ex: "Draco") │
│ gitea │ OAuth2 Gitea │ Nom Gitea + badge 🦎 Gitea │
│ github │ OAuth2 GitHub │ Nom GitHub + badge 🐙 GitHub │
└──────────────┴──────────────────┴─────────────────────────────────────┘
Règle fondamentale : Un compte local avec intégration(s) reste un compte LOCAL.
Le badge OAuth (🦎/🐙) n'apparaît QUE pour les comptes dont auth_method est le provider.
Compte LOCAL avec intégrations :
┌──────────────────────────────────┐
│ [D] Draco │ ← Nom local, PAS de badge
│ [email protected] │
└──────────────────────────────────┘
Compte GITEA (auth_method=gitea) :
┌──────────────────────────────────┐
│ [B] bruno [🦎 Gitea] │ ← Badge visible
│ [email protected] │
└──────────────────────────────────┘
6.5.2 Matrice des intégrations
Compte principal Peut ajouter Déconnexion possible
─────────────────────────────────────────────────────────
local Gitea ✅ Oui
local GitHub ✅ Oui
local Gitea + GitHub ✅ Les deux
─────────────────────────────────────────────────────────
gitea GitHub ✅ GitHub ❌ Gitea
github Gitea ✅ Gitea ❌ GitHub
Settings → Integrations : Affiche toutes les intégrations avec bouton Disconnect. Le provider principal (auth_method) n'a PAS de bouton Disconnect.
6.5.3 Scénarios de workspace
SCÉNARIO A : Compte local pur
─────────────────────────────
· Login email+password
· Workspaces locaux uniquement
· Section Private : CACHÉE (tout est déjà local)
· Trash : ✅ (restauration locale)
SCÉNARIO B : Compte local + intégration(s)
──────────────────────────────────────────
· Login email+password
· Workspaces : locaux + Gitea + GitHub
· Section Private : VISIBLE si workspace distant actif
· Private : stockage DB locale, associé user+repo
· Trash : ✅ (local uniquement)
SCÉNARIO C : Compte OAuth pur (gitea ou github)
────────────────────────────────────────────────
· Login via OAuth
· Workspaces : distants du provider principal
· Section Private : VISIBLE (toujours)
· Trash : ❌ (pas de contenu local)
· Intégration secondaire : possible
· Déconnexion provider principal : impossible
6.5.4 Sidebar — règles d'affichage par section
Section Scénario A Scénario B Scénario C
───────────── ────────────── ────────────────────── ──────────────
Recents ✅ (local) ✅ (tout) ✅ (tout)
Favorites ✅ (local) ✅ (tout) ✅ (tout)
Shared ✅ (local) ✅ (tout) ✅ (tout)
Published ✅ (local) ✅ (tout) ✅ (tout)
Agents ✅ ✅ ✅
Private ❌ CACHÉ ✅ si ws distant actif ✅
Workspace ✅ ✅ ✅
Trash ✅ (local) ✅ (local) ❌
Chaque section a un bouton → Library qui ouvre :
/library?tab=recents|favorites|shared|published|private|workspace
6.5.5 Système Share & Publish
Le bouton [Share] dans la topbar d'une page ouvre une modale à 2 tabs :
┌──────────────────────────────────────────────────────┐
│ [ Share ] [ Publish ] │
├──────────────────────────────────────────────────────┤
│ Share tab : │
│ · Invite people : [email] [Invite] │
│ · General access : [🔒 Restricted] Copy link │
│ · Liste des personnes/groupes avec accès │
│ → Document listé dans sidebar "Shared" │
├──────────────────────────────────────────────────────┤
│ Publish tab : │
│ · [Publish to web] → URL publique générée │
│ · URL : https://flowdeck.draco.dev/p/<slug> │
│ · [Unpublish] │
│ → Document listé dans sidebar "Published" │
└──────────────────────────────────────────────────────┘
6.5.6 Stockage des fichiers Private
Quand un workspace distant est actif, les fichiers créés dans la section
Private sont stockés dans la base de données locale (table pages avec
parent_section='Private' et workspace=gitea:<owner>/<repo>).
Cela permet de :
- Prendre des notes personnelles liées à un projet sans les committer
- Garder des brouillons avant de les pousser
- Avoir un espace de travail privé même sur un repo partagé
6.5.7 Trash — règles
Contenu Trash ? Comportement
─────────────────────── ──────── ──────────────────────────
Workspace local ✅ Supprimé → restaurable
Dossier/fichier local ✅ Supprimé → restaurable
Fichier Gitea/GitHub ❌ Commit "delete" → pas trash
Page distante ❌ Commit "delete" → pas trash
6.5.8 Édition de fichiers — UI unifiée
Fichier LOCAL Fichier DISTANT (Gitea/GitHub)
┌─────────────────────────┐ ┌─────────────────────────────┐
│ Même UI d'édition │ │ Même UI d'édition │
│ Même rendu visuel │ │ Même rendu visuel │
│ Sauvegarde auto DB │ │ + Bouton [Commit] │
│ Pas de commit │ │ + Champ message commit │
└─────────────────────────┘ └─────────────────────────────┘
6.5.9 Page Library — /library?tab=<tab>
┌──────────────────────────────────────────────────────────────┐
│ Library │
│ [Recents] [Favorites] [Shared] [Published] │
│ [Private] [Workspace] │
├──────────────────────────────────────────────────────────────┤
│ Filtres : [All] [Local] [Gitea] [GitHub] │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 📄 Document X Local · modifié il y a 2h │ │
│ │ 📄 README.md Gitea · bruno/flowdeck │ │
│ │ 📄 Notes Local · Workspace Projet A │ │
│ └─────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────┘
6.5.10 Schéma DB — mises à jour pour v4.0
-- users : ajout auth_method
ALTER TABLE users ADD COLUMN auth_method TEXT NOT NULL DEFAULT 'local';
-- 'local' | 'gitea' | 'github'
-- users : colonnes OAuth existantes
-- gitea_id INTEGER (NULL si pas Gitea)
-- github_id INTEGER (NULL si pas GitHub)
-- gitea_token TEXT (token intégration, NULL si pas connecté)
-- github_token TEXT (token intégration, NULL si pas connecté)
-- pages : ajout published
ALTER TABLE pages ADD COLUMN is_published BOOLEAN NOT NULL DEFAULT 0;
ALTER TABLE pages ADD COLUMN publish_slug TEXT;
ALTER TABLE pages ADD COLUMN is_shared BOOLEAN NOT NULL DEFAULT 0;
ALTER TABLE pages ADD COLUMN share_mode TEXT DEFAULT 'restricted';
-- 'restricted' | 'link' | 'public'
-- page_shares : qui a accès à quel document
CREATE TABLE page_shares (
id INTEGER PRIMARY KEY AUTOINCREMENT,
page_id INTEGER NOT NULL REFERENCES pages(id) ON DELETE CASCADE,
shared_with_user_id INTEGER REFERENCES users(id),
shared_with_email TEXT,
permission TEXT NOT NULL DEFAULT 'view',
-- 'view' | 'comment' | 'edit'
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
created_by INTEGER REFERENCES users(id)
);
-- recents : historique de consultation
CREATE TABLE recents (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL REFERENCES users(id),
page_id INTEGER NOT NULL REFERENCES pages(id),
workspace TEXT NOT NULL,
source_type TEXT NOT NULL DEFAULT 'local',
-- 'local' | 'gitea' | 'github'
accessed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE(user_id, page_id)
);
6.5.11 Plan de migration
Phase 1 — DB & Modèles
1. Ajouter auth_method aux users existants (auto-détecter)
2. Ajouter is_published, is_shared, share_mode aux pages
3. Créer table page_shares
4. Créer table recents
Phase 2 — Sidebar
1. Badge OAuth (🦎/🐙) basé sur auth_method
2. Afficher/cacher Private selon type de compte + workspace actif
3. Boutons Library par section
4. Trash filtré local-only
Phase 3 — Share & Publish
1. Modale Share à 2 tabs fonctionnelle
2. Share via email/user → table page_shares
3. Share via lien → share_mode='link'
4. Publish → is_published + publish_slug
5. Page publique servable à /p/<slug>
Phase 4 — Library
1. Page /library avec tabs
2. Filtrage par source (local/gitea/github)
3. Recents alimentés automatiquement
Phase 5 — Intégrations
1. GitHub OAuth déjà fait (providers.py)
2. Matrice de déconnexion respectée
3. Compte local peut connecter/déconnecter Gitea+GitHub
7. Intégration Gitea
7.1 Flux de données
Création Collection Sync bidirectionnelle
────────────────── ──────────────────────
1. Collection vide 5. POST /board/api/sync/{o}/{r}
(pas de lien Gitea) │
├─ GET issues Gitea
2. Collection liée ├─ Map issues → collection_pages
(gitea_owner + gitea_repo) │ (via _issue_column)
├─ Extract AI keywords
3. Pull initial └─ Upsert cards in DB
GET /api/v1/repos/{o}/{r}/issues
6. Webhook Gitea
4. Mapping colonnes POST /webhooks/gitea
col_mapping: │
column_name → gitea_label ├─ issue.opened → create page
├─ issue.closed → archive page
└─ issue.labeled → move column
7.2 Adaptateur de compatibilité
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 | "[email protected]" |
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
# 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
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
{
"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
{
"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)
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
# É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
- Notion Help — Databases
- Notion Help — Tasks & Dependencies
- Notion Help — Relations & Rollups
- FlowDeck — NOTION_DATABASE_TASKS_GUIDE.md
- FlowDeck — ROADMAP.md
16. Versions à venir
v4.1.0 — Content Blocks enrichis (priorité standard)
- Callout boxes — Blocs d'alerte (info, warning, tip, danger)
- Table of Contents — Auto-généré depuis les headings
- LaTeX / KaTeX — Formules mathématiques
- Toggle lists — Listes pliables/dépliables
- Multi-columns — Mise en page 2-3 colonnes
v4.2.0 — Export (priorité standard)
- Export Markdown avec images
- Export PDF (via WeasyPrint ou headless Chrome)
- Export HTML standalone (page auto-suffisante)
v4.3.0 — Collaboration (priorité basse)
- Commentaires inline sur les pages
- @mentions pour notifier des utilisateurs
- Notifications email (changements, mentions)
- Permissions par workspace (viewer, editor, admin)
v5.0.0 — Plateforme avancée (futur)
- AI Assistants — Génération de contenu, résumés, suggestions
- Embeds — Vidéos, PDF, Figma, Google Docs
- Automatisations — Règles déclenchées sur événements (Notion-style)
- Base de données avancée — Relations inter-collections, rollups
- Kanban flexible — Colonnes custom, WIP limits
- API publique REST v2 —
/api/v2(v6.3.0) : Bearer + scopesread/write/admin, CRUD complet, pagination, RFC 7807, idempotence, audit, OpenAPI (/docs,docs/openapi-v2.json) ;/api/v1lecture seule (compat) - Volume Docker persistant —
/datamonté pour survie des données - PostgreSQL — Migration optionnelle pour scaling