Files
flowdeck/ARCHITECTURE.md
bruno 0ea447ee6f
FlowDeck CI / test (push) Failing after 4s
FlowDeck CI / docker (push) Has been skipped
docs: ARCHITECTURE.md v4.0 — mise à jour complète
- Version 3.0 → 4.0, date 2026-07-20
- Templates: reflète la structure actuelle (base, _header, _workspace_tree_macro, etc.)
- Routers: ajoutés (local_workspace, library, private, public_api, admin, workspace)
- Data layer: nouvelles tables v4.0 (page_shares, recents, tags, gitea_private_pages)
- Nouvelle section 5: Système d'authentification (3 types de comptes, sidebar behavior, cookie flowdeck_workspace)
- Layout diagram: sidebar Notion complet avec toutes les sections
- Section 16: Versions à venir (v4.1.0 → v5.0.0)
- Nettoyage références obsolètes
2026-07-20 13:27:13 -04:00

1711 lines
81 KiB
Markdown

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