Files
flowdeck/ARCHITECTURE.md
T
bruno 66ae1289a9
FlowDeck CI / test (push) Failing after 6s
FlowDeck CI / docker (push) Has been skipped
docs: ROADMAP v1.3-v2.1 + ARCHITECTURE v2.0 complète
ROADMAP.md:
- 52 fonctionnalités manquantes organisées en 7 phases (v1.3 → v2.1)
- v1.3: Database Concept (collections, pages, views)
- v1.4-v1.5: Propriétés avancées (number, checkbox, status, relation, rollup, formula)
- v1.6-v1.7: Vues manquantes (calendar, timeline, gallery, list, group_by, filters)
- v1.8: Sub-items & Dépendances
- v1.9: My Tasks & Dashboard unifié
- v2.0: Éditeur complet + Multi-utilisateurs
- v2.1: Intégrations avancées (API, webhooks, n8n, mobile PWA)

ARCHITECTURE.md (63 Ko, 15 sections):
- Architecture système complète (schéma ASCII détaillé)
- Modèle de données: 22 tables SQL (existantes + nouvelles)
- Diagramme des relations entre tables
- Routes API complètes: 80+ endpoints documentés
- Frontend: layout, 6 vues, composants réutilisables
- Authentification OAuth2 + modèle de permissions multi-user
- Intégration Gitea: flux, sync, adaptateur de compatibilité
- Système de propriétés: 18 types, formula engine, rollup engine
- Système de vues: config JSON, chaîne de rendu
- Sub-items: auto-référence, affichage imbriqué
- Dépendances: contraintes, flèches timeline
- My Tasks: algorithme d'agrégation cross-collection
- Éditeur de blocs: 15+ block types, format JSON
- Multi-utilisateurs: workspaces, ACL, historique, commentaires
- Déploiement: Docker Compose + migration PostgreSQL
2026-07-09 21:38:28 -04:00

1309 lines
61 KiB
Markdown

# Architecture FlowDeck — Document Complet v2.0
> **Version:** 2.0 · **Date:** 2026-07-10 · **Auteur:** Hermes-Deepin
> **Cible:** Clone Notion intégré à Gitea avec support multi-utilisateurs
---
## Table des matières
1. [Vue d'ensemble](#1-vue-densemble)
2. [Architecture système](#2-architecture-système)
3. [Modèle de données](#3-modèle-de-données)
4. [Routage & API](#4-routage--api)
5. [Frontend](#5-frontend)
6. [Système d'authentification](#6-système-dauthentification)
7. [Intégration Gitea](#7-intégration-gitea)
8. [Système de propriétés](#8-système-de-propriétés)
9. [Système de vues](#9-système-de-vues)
10. [Sub-items & Dépendances](#10-sub-items--dépendances)
11. [My Tasks & Dashboard unifié](#11-my-tasks--dashboard-unifié)
12. [Éditeur de blocs](#12-éditeur-de-blocs)
13. [Multi-utilisateurs & Collaboratif](#13-multi-utilisateurs--collaboratif)
14. [Déploiement](#14-déploiement)
15. [Références](#15-références)
---
## 1. Vue d'ensemble
FlowDeck est un **clone de Notion** intégré à Gitea. Il recrée l'expérience Notion — databases, propriétés typées, vues multiples, sub-items, dépendances, éditeur de blocs — tout en restant connecté aux repositories et issues Gitea.
### Principes architecturaux
- **Serveur-side rendering** — Jinja2 + HTMX, pas de SPA lourde
- **SQLite-first** — un fichier, zéro maintenance, migration PostgreSQL triviale si scaling futur
- **Gitea-native** — chaque database peut être liée à un repo Gitea, les issues deviennent des pages
- **Progressive enhancement** — fonctionnel sans JS, enrichi avec Alpine.js + SortableJS
- **Propriétés en JSON** — flexibilité maximale du schéma sans migration de table
---
## 2. Architecture système
```
┌──────────────────────────────────────────────────────────────────┐
│ CLIENT (Browser) │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Templates Jinja2 (SSR) │ │
│ │ ├─ dashboard.html — Projets + sidebar │ │
│ │ ├─ board.html — Kanban (wrapper) │ │
│ │ ├─ board_fragment.html— Cartes Kanban │ │
│ │ ├─ table_view.html — Vue table │ │
│ │ ├─ calendar_view.html — Vue calendrier (v1.6) │ │
│ │ ├─ timeline_view.html — Vue Gantt (v1.6) │ │
│ │ ├─ gallery_view.html — Vue galerie (v1.6) │ │
│ │ ├─ list_view.html — Vue liste (v1.6) │ │
│ │ ├─ status_overview.html — Donut SVG │ │
│ │ ├─ team_load.html — Barres stacked │ │
│ │ ├─ detailed_board.html— Board détaillé │ │
│ │ ├─ card_detail.html — Modale détail carte │ │
│ │ ├─ card.html — Carte individuelle │ │
│ │ ├─ page_editor.html — Éditeur de blocs │ │
│ │ ├─ my_tasks.html — Dashboard unifié (v1.9) │ │
│ │ ├─ library.html — Bibliothèque de pages │ │
│ │ ├─ trash.html — Corbeille │ │
│ │ ├─ accounts.html — Gestion comptes │ │
│ │ ├─ notes.html — Notes Markdown │ │
│ │ └─ base.html — Layout commun │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ JavaScript (CDN, pas de build step) │ │
│ │ ├─ HTMX 1.9 — AJAX sans JS │ │
│ │ ├─ Alpine.js 3.14 — Interactivité légère │ │
│ │ ├─ SortableJS 1.15 — Drag & drop │ │
│ │ └─ highlight.js — Syntax highlighting (v2.0) │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ CSS custom — Design system Notion, dark theme, 31+ KB │ │
│ └─────────────────────────────────────────────────────────┘ │
└──────────────────────────┬───────────────────────────────────────┘
│ HTTP (HTML fragments / JSON)
┌──────────────────────────▼───────────────────────────────────────┐
│ FASTAPI (Python 3.11+) │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ MIDDLEWARE │ │
│ │ ├─ SessionMiddleware — Sessions utilisateur │ │
│ │ ├─ CSRFMiddleware — Protection CSRF │ │
│ │ ├─ CORSMiddleware — CORS (* pour dev local) │ │
│ │ └─ RateLimiter — Rate limiting par IP │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ ROUTERS │ │
│ │ ├─ dashboard.py — / Dashboard │ │
│ │ ├─ board.py — /board/{o}/{r} Board + vues │ │
│ │ │ — /board/api/... APIs board │ │
│ │ ├─ api.py — /api/... CRUD + sync │ │
│ │ ├─ notes.py — /notes/{o}/{r} Notes │ │
│ │ ├─ auth.py — /auth/... OAuth2 │ │
│ │ ├─ webhooks.py — /webhooks/... Gitea hooks │ │
│ │ ├─ my_tasks.py — /my-tasks (v1.9) │ │
│ │ ├─ pages.py — /pages/... Pages CRUD │ │
│ │ ├─ collections.py — /db/... Collections │ │
│ │ └─ editor.py — /api/editor/... Block editor │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ SERVICES │ │
│ │ ├─ GiteaClient (httpx async) — API Gitea │ │
│ │ ├─ FormulaEngine — Moteur de formules │ │
│ │ ├─ RollupEngine — Agrégations cross-DB │ │
│ │ ├─ CollectionSync — Sync Gitea ↔ collections │ │
│ │ ├─ DependencyChecker — Contraintes dépendances │ │
│ │ ├─ PermissionManager — ACL multi-user │ │
│ │ └─ SearchEngine — FTS5 recherche full-text │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ DATA LAYER │ │
│ │ ┌─────────────────────────────────────────────────┐ │ │
│ │ │ SQLite (WAL mode, foreign keys ON) │ │ │
│ │ │ │ │ │
│ │ │ ┌─ collections (v1.3) │ │ │
│ │ │ ├─ collection_pages (v1.3) │ │ │
│ │ │ ├─ collection_views (v1.3) │ │ │
│ │ │ ├─ collection_properties (v1.4) │ │ │
│ │ │ ├─ boards (legacy) │ │ │
│ │ │ ├─ cards (legacy) │ │ │
│ │ │ ├─ col_mapping (legacy) │ │ │
│ │ │ ├─ notes │ │ │
│ │ │ ├─ checklists │ │ │
│ │ │ ├─ checklist_items │ │ │
│ │ │ ├─ project_properties (legacy → collection_properties) │
│ │ │ ├─ property_values (legacy → collection_pages) │
│ │ │ ├─ ai_keywords │ │ │
│ │ │ ├─ pages │ │ │
│ │ │ ├─ users │ │ │
│ │ │ ├─ user_tokens │ │ │
│ │ │ ├─ workspaces (v2.0) │ │ │
│ │ │ ├─ workspace_members (v2.0) │ │ │
│ │ │ ├─ page_history (v2.0) │ │ │
│ │ │ ├─ comments (v2.0) │ │ │
│ │ │ ├─ favorites (v2.0) │ │ │
│ │ │ └─ database_templates (v2.0) │ │ │
│ │ └─────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────┘ │
└──────────────────────────┬───────────────────────────────────────┘
│ HTTPS
┌──────────────────────────▼───────────────────────────────────────┐
│ GITEA API REST v1 │
│ git.dracodev.net │
│ ├─ /api/v1/user/repos — Liste projets │
│ ├─ /api/v1/repos/{o}/{r}/issues — Issues │
│ ├─ /api/v1/repos/{o}/{r}/labels — Labels │
│ ├─ /api/v1/repos/{o}/{r}/milestones — Milestones │
│ ├─ /api/v1/user — Infos utilisateur │
│ └─ /api/v1/orgs/{org}/repos — Repos d'organisation │
└──────────────────────────────────────────────────────────────────┘
```
---
## 3. Modèle de données
### 3.1 Tables Core (existantes v1.0)
```sql
-- Authentification
CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
login TEXT NOT NULL UNIQUE,
full_name TEXT NOT NULL DEFAULT '',
email TEXT NOT NULL DEFAULT '',
avatar_url TEXT NOT NULL DEFAULT '',
is_admin BOOLEAN NOT NULL DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE user_tokens (
id INTEGER PRIMARY KEY AUTOINCREMENT,
gitea_user_id INTEGER NOT NULL UNIQUE,
gitea_token TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Boards (legacy — sera absorbé par collections en v1.3)
CREATE TABLE boards (
id INTEGER PRIMARY KEY AUTOINCREMENT,
project_owner TEXT NOT NULL,
project_name TEXT NOT NULL,
columns_json TEXT NOT NULL DEFAULT '["Backlog","À faire","En cours","Révision","Terminé"]',
wip_limits_json TEXT DEFAULT '{}',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE(project_owner, project_name)
);
CREATE TABLE cards (
id INTEGER PRIMARY KEY AUTOINCREMENT,
board_id INTEGER NOT NULL REFERENCES boards(id),
gitea_issue_id INTEGER,
column_name TEXT NOT NULL DEFAULT 'Backlog',
position INTEGER NOT NULL DEFAULT 0,
priority TEXT DEFAULT 'Medium',
due_date TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE col_mapping (
id INTEGER PRIMARY KEY AUTOINCREMENT,
board_id INTEGER NOT NULL REFERENCES boards(id),
column_name TEXT NOT NULL,
gitea_label TEXT NOT NULL,
close_issue BOOLEAN NOT NULL DEFAULT 0,
UNIQUE(board_id, column_name)
);
-- Notes & Checklists
CREATE TABLE notes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
project_owner TEXT NOT NULL,
project_name TEXT NOT NULL,
title TEXT NOT NULL DEFAULT 'Notes',
content TEXT NOT NULL DEFAULT '',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE(project_owner, project_name, title)
);
CREATE TABLE checklists (
id INTEGER PRIMARY KEY AUTOINCREMENT,
board_id INTEGER NOT NULL,
gitea_issue_id INTEGER NOT NULL,
title TEXT NOT NULL DEFAULT 'Checklist',
position INTEGER NOT NULL DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE(board_id, gitea_issue_id, title)
);
CREATE TABLE checklist_items (
id INTEGER PRIMARY KEY AUTOINCREMENT,
checklist_id INTEGER NOT NULL REFERENCES checklists(id) ON DELETE CASCADE,
content TEXT NOT NULL DEFAULT '',
checked BOOLEAN NOT NULL DEFAULT 0,
position INTEGER NOT NULL DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Propriétés custom (legacy)
CREATE TABLE project_properties (
id INTEGER PRIMARY KEY AUTOINCREMENT,
project_owner TEXT NOT NULL,
project_name TEXT NOT NULL,
name TEXT NOT NULL,
prop_type TEXT NOT NULL DEFAULT 'select',
options_json TEXT DEFAULT '[]',
position INTEGER NOT NULL DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE(project_owner, project_name, name)
);
CREATE TABLE property_values (
id INTEGER PRIMARY KEY AUTOINCREMENT,
property_id INTEGER NOT NULL REFERENCES project_properties(id) ON DELETE CASCADE,
gitea_issue_id INTEGER NOT NULL,
value TEXT NOT NULL DEFAULT '',
UNIQUE(property_id, gitea_issue_id)
);
CREATE TABLE ai_keywords (
id INTEGER PRIMARY KEY AUTOINCREMENT,
project_owner TEXT NOT NULL,
project_name TEXT NOT NULL,
keyword TEXT NOT NULL,
color TEXT NOT NULL DEFAULT '#787774',
usage_count INTEGER NOT NULL DEFAULT 1,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE(project_owner, project_name, keyword)
);
-- Pages
CREATE TABLE pages (
id INTEGER PRIMARY KEY AUTOINCREMENT,
workspace TEXT NOT NULL,
title TEXT NOT NULL DEFAULT 'New page',
content TEXT NOT NULL DEFAULT '',
content_format TEXT NOT NULL DEFAULT 'markdown',
parent_section TEXT DEFAULT 'Private',
parent_id INTEGER REFERENCES pages(id),
sort_order INTEGER NOT NULL DEFAULT 0,
deleted_at TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
```
### 3.2 Tables Database Concept (v1.3)
```sql
-- Collections = Databases Notion
CREATE TABLE collections (
id INTEGER PRIMARY KEY AUTOINCREMENT,
workspace_id INTEGER REFERENCES workspaces(id),
name TEXT NOT NULL,
description TEXT DEFAULT '',
icon TEXT DEFAULT '📋',
schema_json TEXT NOT NULL DEFAULT '[]',
-- [{name,type,options,position}, ...]
-- Lien Gitea optionnel
gitea_owner TEXT,
gitea_repo TEXT,
is_locked BOOLEAN NOT NULL DEFAULT 0,
is_inline BOOLEAN NOT NULL DEFAULT 0,
parent_page_id INTEGER REFERENCES collection_pages(id),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
created_by INTEGER REFERENCES users(id),
UNIQUE(workspace_id, name)
);
-- Pages dans une collection
CREATE TABLE collection_pages (
id INTEGER PRIMARY KEY AUTOINCREMENT,
collection_id INTEGER NOT NULL REFERENCES collections(id) ON DELETE CASCADE,
title TEXT NOT NULL DEFAULT '',
icon TEXT DEFAULT '📄',
position INTEGER NOT NULL DEFAULT 0,
-- Sub-items
parent_id INTEGER REFERENCES collection_pages(id),
-- Gitea link
gitea_issue_id INTEGER,
gitea_issue_number INTEGER,
-- Toutes les valeurs de propriétés en JSON
property_values_json TEXT NOT NULL DEFAULT '{}',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
created_by INTEGER REFERENCES users(id),
updated_by INTEGER REFERENCES users(id)
);
CREATE INDEX idx_cp_collection ON collection_pages(collection_id, position);
CREATE INDEX idx_cp_parent ON collection_pages(parent_id);
CREATE INDEX idx_cp_gitea ON collection_pages(gitea_issue_id);
-- Vues d'une collection
CREATE TABLE collection_views (
id INTEGER PRIMARY KEY AUTOINCREMENT,
collection_id INTEGER NOT NULL REFERENCES collections(id) ON DELETE CASCADE,
name TEXT NOT NULL DEFAULT 'Default View',
view_type TEXT NOT NULL DEFAULT 'table',
-- table | board | calendar | timeline | gallery | list
config_json TEXT NOT NULL DEFAULT '{}',
-- {
-- group_by: "Status",
-- filters: [{property,operator,value}, ...],
-- filter_conjunction: "and" | "or",
-- sorts: [{property,direction}, ...],
-- visible_properties: ["Title","Status"],
-- card_size: "medium",
-- cover_property: "Files",
-- date_property: "DueDate",
-- date_range_property: "EndDate"
-- }
position INTEGER NOT NULL DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
```
### 3.3 Tables Propriétés Avancées (v1.4)
```sql
CREATE TABLE collection_properties (
id INTEGER PRIMARY KEY AUTOINCREMENT,
collection_id INTEGER NOT NULL REFERENCES collections(id) ON DELETE CASCADE,
name TEXT NOT NULL,
prop_type TEXT NOT NULL DEFAULT 'text',
-- title, text, number, select, multi_select, status,
-- date, person, checkbox, url, email, phone,
-- relation, rollup, formula,
-- files, unique_id,
-- created_time, created_by, last_edited_time, last_edited_by
-- Options pour select/multi_select/status
options_json TEXT DEFAULT '[]',
-- [{name:"Todo",color:"gray"}, {name:"Done",color:"green"}]
-- Format pour number
number_format TEXT DEFAULT 'number',
-- number, percent, dollar, euro, pound, yen
-- Pour relation
related_collection_id INTEGER REFERENCES collections(id),
reverse_name TEXT,
-- Pour rollup
relation_property_id INTEGER REFERENCES collection_properties(id),
target_property_id INTEGER REFERENCES collection_properties(id),
rollup_function TEXT,
-- count, sum, avg, min, max, range, unique
-- Pour formula
formula_expression TEXT,
position INTEGER NOT NULL DEFAULT 0,
required BOOLEAN NOT NULL DEFAULT 0,
visible_in_views BOOLEAN NOT NULL DEFAULT 1,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE(collection_id, name)
);
```
### 3.4 Tables Multi-Utilisateurs (v2.0)
```sql
CREATE TABLE workspaces (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
owner_id INTEGER NOT NULL REFERENCES users(id),
settings_json TEXT NOT NULL DEFAULT '{}',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE workspace_members (
id INTEGER PRIMARY KEY AUTOINCREMENT,
workspace_id INTEGER NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
user_id INTEGER NOT NULL REFERENCES users(id),
role TEXT NOT NULL DEFAULT 'editor',
-- admin | editor | commenter | viewer
joined_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE(workspace_id, user_id)
);
CREATE TABLE page_history (
id INTEGER PRIMARY KEY AUTOINCREMENT,
page_id INTEGER NOT NULL REFERENCES collection_pages(id) ON DELETE CASCADE,
user_id INTEGER REFERENCES users(id),
change_type TEXT NOT NULL,
-- created | updated | moved | deleted | restored
snapshot_json TEXT NOT NULL DEFAULT '{}',
-- Snapshot des valeurs avant modification
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE comments (
id INTEGER PRIMARY KEY AUTOINCREMENT,
page_id INTEGER NOT NULL REFERENCES collection_pages(id) ON DELETE CASCADE,
user_id INTEGER NOT NULL REFERENCES users(id),
body TEXT NOT NULL DEFAULT '',
parent_id INTEGER REFERENCES comments(id),
resolved BOOLEAN NOT NULL DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE favorites (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL REFERENCES users(id),
page_id INTEGER REFERENCES collection_pages(id),
collection_id INTEGER REFERENCES collections(id),
-- L'un des deux est NOT NULL
position INTEGER NOT NULL DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE(user_id, page_id, collection_id)
);
CREATE TABLE database_templates (
id INTEGER PRIMARY KEY AUTOINCREMENT,
workspace_id INTEGER REFERENCES workspaces(id),
name TEXT NOT NULL,
description TEXT DEFAULT '',
schema_json TEXT NOT NULL DEFAULT '[]',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
created_by INTEGER REFERENCES users(id)
);
CREATE TABLE page_templates (
id INTEGER PRIMARY KEY AUTOINCREMENT,
collection_id INTEGER NOT NULL REFERENCES collections(id) ON DELETE CASCADE,
name TEXT NOT NULL DEFAULT 'Default',
property_values_json TEXT NOT NULL DEFAULT '{}',
content_json TEXT DEFAULT '[]',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
```
### 3.5 Diagramme des relations
```
┌──────────────┐ ┌──────────────────┐ ┌───────────────────┐
│ workspaces │──1:N──│ workspace_members│──N:1──│ users │
│ │ │ - role │ │ - login │
│ - name │ │ - joined_at │ │ - email │
│ - owner_id │ └──────────────────┘ │ - avatar_url │
└──────┬───────┘ │ - is_admin │
│ 1:N └──────┬────┬────────┘
▼ │ │
┌──────────────┐ ┌──────────────────┐ │ │ (author)
│ collections │──1:N──│ collection_pages │◄─N:1───────┘ │
│ │ │ │ │
│ - name │ │ - title │ │
│ - schema_json│ │ - parent_id ────┼─── self-ref │
│ - icon │ │ - gitea_issue_id│ │
│ - gitea_* │ │ - property_values│ │
│ - is_locked │ │ _json │ │
└──────┬───────┘ └────────┬─────────┘ │
│ 1:N │ 1:N │
▼ ▼ │
┌──────────────────┐ ┌──────────────────┐ │
│collection_views │ │ comments │ │
│ - view_type │ │ - body │ │
│ - config_json │ │ - resolved │ │
└──────────────────┘ │ - parent_id │ │
└────────┬─────────┘ │
│ │
┌──────────────────┐ │ │
│collection_properties│ │ │
│ - name │ │ │
│ - prop_type │◄─────────┘ │
│ - options_json │ │
│ - related_collection_id ──┐ │
│ - relation_property_id │ self-ref │
│ - target_property_id │ │
│ - formula_expression │ │
└────────────────────────────┘ │
│
┌──────────────────┐ ┌──────────────────┐ │
│ page_history │ │ favorites │ │
│ - change_type │ │ - position │ │
│ - snapshot_json │ └──────────────────┘ │
│ - user_id ──────┼────────────────────────────────────────┘
└──────────────────┘
```
---
## 4. Routage & API
### 4.1 Routes existantes (v1.2)
```
GET / → Dashboard
GET /board/{owner}/{repo} → Board Kanban
GET /board/{owner}/{repo}/view/{view} → Fragment vue (kanban|table|status|teamload|detailed)
GET /board/{owner}/{repo}?view=&status=&filter=&sort= → Avec paramètres
GET /board/api/properties/{o}/{r} → Liste propriétés
POST /board/api/properties/{o}/{r} → Créer propriété
DEL /board/api/properties/{o}/{r} → Supprimer propriété
POST /board/api/properties/{o}/{r}/values → Set valeur propriété
GET /board/api/ai-keywords/{o}/{r} → Liste keywords
POST /board/api/ai-keywords/{o}/{r}/extract → Extraire keywords
POST /board/api/sync/{o}/{r} → Sync complète
GET /api/health → Health check
GET /api/stats → Statistiques
GET /api/projects → Projets JSON
POST /api/move → Déplacer carte
GET /api/issues/{o}/{r}/{id} → Issue detail (JSON)
GET /api/issues/{o}/{r}/{id}?format=html → Issue detail (HTML)
POST /api/issues/{o}/{r} → Créer issue
PATCH /api/issues/{o}/{r}/{id} → Update issue
POST /api/checklists/{o}/{r}/{id} → Créer checklist
POST /api/checklist-items/{o}/{r}/{id}/{cl_id} → Ajouter item
PATCH /api/checklist-items/{id} → Toggle item
GET /notes/{o}/{r} → Lire notes
POST /notes/{o}/{r} → Sauvegarder notes
GET /auth/login → Login OAuth2 Gitea
GET /auth/callback → Callback OAuth2
GET /auth/logout → Déconnexion
POST /webhooks/gitea → Webhook Gitea
GET /pages/{page_id} → Voir page
POST /board/api/pages → Créer page
GET /board/api/pages/{id} → Get page JSON
PUT /board/api/pages/{id} → Update page
PUT /board/api/pages/{id}/move → Déplacer page (drag & drop)
POST /board/api/pages/{id}/blocks → Save block editor
DEL /board/api/pages/{id} → Soft delete
GET /board/trash → Page corbeille
GET /board/api/trash → Liste trash
POST /board/api/trash/{id}/restore → Restaurer
DEL /board/api/trash/{id} → Suppression définitive
GET /library → Bibliothèque
GET /accounts → Gestion comptes
```
### 4.2 Routes Database (v1.3-v1.5)
```
GET /db → Liste collections du workspace
POST /db → Créer collection
GET /db/{collection_id} → Vue par défaut de la collection
GET /db/{collection_id}/view/{view_id} → Vue spécifique
GET /db/{collection_id}/fragment/{view_type} → Fragment HTMX
POST /db/{collection_id}/pages → Créer page
GET /db/{collection_id}/pages/{page_id} → Détail page
PUT /db/{collection_id}/pages/{page_id} → Update page
DELETE /db/{collection_id}/pages/{page_id} → Soft delete page
GET /db/{collection_id}/properties → Liste propriétés
POST /db/{collection_id}/properties → Créer propriété
PUT /db/{collection_id}/properties/{prop_id} → Update propriété
DELETE /db/{collection_id}/properties/{prop_id} → Supprimer propriété
GET /db/{collection_id}/views → Liste vues
POST /db/{collection_id}/views → Créer vue
PUT /db/{collection_id}/views/{view_id} → Update config vue
DELETE /db/{collection_id}/views/{view_id} → Supprimer vue
POST /db/{collection_id}/views/duplicate/{id} → Dupliquer vue
POST /db/{collection_id}/views/save-as → Sauvegarder état courant
GET /db/{collection_id}/templates → Page templates
POST /db/{collection_id}/templates → Créer template
POST /db/{collection_id}/templates/{tid}/apply → Appliquer template
POST /db/{collection_id}/import/csv → Import CSV
GET /db/{collection_id}/export/csv → Export CSV
```
### 4.3 Routes Sub-items & Dépendances (v1.8)
```
GET /db/{collection_id}/pages/{page_id}/sub-items → Liste sub-items
POST /db/{collection_id}/pages/{page_id}/sub-items → Créer sub-item
GET /db/{collection_id}/pages/{page_id}/dependencies → Bloque/Bloqué par
POST /db/{collection_id}/pages/{page_id}/dependencies → Ajouter dépendance
DELETE /db/{collection_id}/pages/{page_id}/dependencies/{dep_id} → Supprimer
POST /db/{collection_id}/pages/{page_id}/check-deps → Vérifier contraintes
```
### 4.4 Routes My Tasks (v1.9)
```
GET /my-tasks → Dashboard unifié
GET /my-tasks?view=calendar → Vue calendrier
GET /my-tasks?view=overdue → Tâches en retard
GET /my-tasks?view=upcoming&days=7 → 7 prochains jours
GET /my-tasks?filter=collection:3 → Filtrer par collection
```
### 4.5 Routes Multi-Utilisateurs (v2.0)
```
GET /workspace/{ws_id} → Workspace
POST /workspace → Créer workspace
PUT /workspace/{ws_id}/settings → Settings
POST /workspace/{ws_id}/members → Inviter membre
PUT /workspace/{ws_id}/members/{user_id}/role → Changer rôle
DELETE /workspace/{ws_id}/members/{user_id} → Retirer membre
GET /db/{collection_id}/pages/{page_id}/history → Historique
GET /db/{collection_id}/pages/{page_id}/comments→ Commentaires
POST /db/{collection_id}/pages/{page_id}/comments→ Ajouter commentaire
GET /favorites → Liste favoris
POST /favorites → Ajouter favori
DELETE /favorites/{id} → Retirer favori
```
---
## 5. Frontend
### 5.1 Layout
```
┌──────────────────────────────────────────────────────────────┐
│ TOPBAR (44px, sticky) │
│ ┌──────────┬──────────────────────────────────┬────────────┐ │
│ │ Logo │ Breadcrumbs: WS > Collection │ Share... │ │
│ │ FlowDeck │ (ou WS > Collection > Page) │ Settings │ │
│ └──────────┴──────────────────────────────────┴────────────┘ │
├────────────┬─────────────────────────────────────────────────┤
│ SIDEBAR │ CONTENU PRINCIPAL │
│ (240px) │ │
│ │ ┌─────────────────────────────────────────────┐│
│ Workspace │ │ DATABASE VIEW ││
│ header │ │ ┌──────┬──────┬──────┬──────┬──────────┐ ││
│ │ │ │Title │Statut│Priori│Assign│Due Date │ ││
│ 🔍 Search │ │ ├──────┼──────┼──────┼──────┼──────────┤ ││
│ │ │ │Tâche1│Todo │ P1 │Bruno │15 juil │ ││
│ ▼ Recents │ │ │Tâche2│Doing │ P2 │ — │20 juil │ ││
│ · Proj A │ │ │Tâche3│Done │ P3 │Marie │10 juil │ ││
│ · Proj B │ │ └──────┴──────┴──────┴──────┴──────────┘ ││
│ │ │ [+ New] [Filter] [Sort] [Search] [...] ││
│ ▼ Private │ └─────────────────────────────────────────────┘│
│ · Notes │ │
│ │ ┌─ Barre d'outils ────────────────────────────┐│
│ ▼ Shared │ │ [Table▼] [Kanban] [Calendar] [Gallery] ... ││
│ · ... │ │ [Group▼] [Filter▼] [Sort▼] [Properties▼] ││
│ │ └─────────────────────────────────────────────┘│
│ ────────── │ │
│ [+New page]│ ┌─ Contenu de la vue ─────────────────────────┐│
│ (sticky │ │ (table / board / calendar / gallery / ...) ││
│ footer) │ └─────────────────────────────────────────────┘│
└────────────┴─────────────────────────────────────────────────┘
```
### 5.2 Vues supportées
| Vue | Icône | Template | Description |
|-----|-------|----------|-------------|
| Table | 📊 | `table_view.html` | Colonnes = propriétés, lignes = pages |
| Board (Kanban) | 📋 | `board_fragment.html` | Groupé par propriété Select |
| Calendar | 📅 | `calendar_view.html` | Groupé par propriété Date |
| Timeline (Gantt) | 📈 | `timeline_view.html` | Barres horizontales sur axe Date |
| Gallery | 🖼️ | `gallery_view.html` | Cartes visuelles avec cover |
| List | 📝 | `list_view.html` | Compact, 1 colonne + preview |
### 5.3 Composants réutilisables
- **Property editor** — éditeur inline par type (select dropdown, date picker, text input...)
- **Filter builder** — UI de construction de filtres (propriété + opérateur + valeur)
- **Sort builder** — UI de tri multi-critères
- **Group selector** — dropdown pour choisir la propriété de groupement
- **View tabs** — onglets pour switcher entre vues
- **Page modal** — détail d'une page avec propriétés, checklists, commentaires
- **Slash menu** — menu contextuel pour l'éditeur de blocs
---
## 6. Système d'authentification
### 6.1 Flux OAuth2 Gitea
```
User ──→ GET /auth/login ──→ Redirect Gitea OAuth
│
Gitea authorize page
│
User authorize ──→ GET /auth/callback?code=xxx
│
POST /api/v1/login/oauth/access_token
│
┌─────▼──────┐
│ access_token│
└─────┬──────┘
│
GET /api/v1/user
│
┌─────▼──────────────────┐
│ Create/Update: │
│ - users (login, email) │
│ - user_tokens (token) │
│ - session cookie │
└─────────────────────────┘
```
### 6.2 Multi-utilisateurs (v2.0)
```
┌──────────────────────────────────────────────────────────┐
│ PERMISSION MODEL │
│ │
│ Workspace │
│ ├─ Owner (créateur) — tout peut faire │
│ ├─ Admin — gérer membres, config │
│ ├─ Editor — CRUD pages, propriétés │
│ ├─ Commenter — commentaires seulement │
│ └─ Viewer — lecture seule │
│ │
│ Collection (hérite du workspace par défaut) │
│ ├─ Peut être overridé par collection │
│ └─ Database lock → lecture seule pour tout le monde │
│ │
│ Page │
│ ├─ Propriétaire = created_by │
│ ├─ Mention @user → notifie l'utilisateur │
│ └─ Assignee (property type Person) │
└──────────────────────────────────────────────────────────┘
```
---
## 7. Intégration Gitea
### 7.1 Flux de données
```
Création Collection Sync bidirectionnelle
────────────────── ──────────────────────
1. Collection vide 5. POST /board/api/sync/{o}/{r}
(pas de lien Gitea) │
├─ GET issues Gitea
2. Collection liée ├─ Map issues → collection_pages
(gitea_owner + gitea_repo) │ (via _issue_column)
├─ Extract AI keywords
3. Pull initial └─ Upsert cards in DB
GET /api/v1/repos/{o}/{r}/issues
6. Webhook Gitea
4. Mapping colonnes POST /webhooks/gitea
col_mapping: │
column_name → gitea_label ├─ issue.opened → create page
├─ issue.closed → archive page
└─ issue.labeled → move column
```
### 7.2 Adaptateur de compatibilité
```python
class GiteaBoardCompat:
"""Adaptateur: board Gitea legacy → Collection."""
@staticmethod
def from_board(board_row) -> dict:
return {
"collection_id": f"gitea:{board_row.id}",
"name": f"{board_row.project_owner}/{board_row.project_name}",
"gitea_owner": board_row.project_owner,
"gitea_repo": board_row.project_name,
"schema": [
{"name": "Title", "type": "title"},
{"name": "Status", "type": "select",
"options": json.loads(board_row.columns_json)},
{"name": "Priority", "type": "select",
"options": ["P1","P2","P3","P4"]},
],
"is_gitea_linked": True,
}
@staticmethod
def from_collection_page(page_row) -> dict:
"""Convertit collection_page → card (pour templates legacy)."""
return {
"id": str(page_row.get("gitea_issue_number", page_row["id"])),
"title": page_row["title"],
"status": _derive_status(page_row),
...
}
```
---
## 8. Système de propriétés
### 8.1 Architecture
```
┌─────────────────────────────────────────────────────────────┐
│ PROPERTY SYSTEM │
│ │
│ collection_properties (schéma) property_values_json │
│ ┌──────────────────────────┐ ┌──────────────────────┐ │
│ │ id: 1 │ │ Dans collection_pages │ │
│ │ name: "Status" │ │ { │ │
│ │ prop_type: "status" │ │ "1": "Done", │ │
│ │ options_json: [ │ │ "2": "P1", │ │
│ │ {name:"Todo", │ │ "3": "2026-07-15", │ │
│ │ color:"gray"}, │ │ "4": ["bruno"], │ │
│ │ {name:"Done", │ │ "5": ["page_42"], │ │
│ │ color:"green"} │ │ } │ │
│ │ ] │ └──────────────────────┘ │
│ └──────────────────────────┘ │
│ │
│ Avantages du JSON: │
│ - Pas de migration de table pour ajouter une propriété │
│ - Requêtes via json_extract() en SQLite │
│ - Facilement requêtable avec index si nécessaire │
└─────────────────────────────────────────────────────────────┘
```
### 8.2 Types de propriétés
| Type | Stockage | Exemple |
|------|----------|---------|
| `title` | String | `"Ma tâche"` |
| `text` | String | `"Description longue..."` |
| `number` | Float | `42` / `12.5` |
| `select` | String | `"En cours"` |
| `multi_select` | JSON array | `["Frontend","Backend"]` |
| `status` | String + color | `"Done"` (green) |
| `date` | ISO 8601 string | `"2026-07-15"` |
| `person` | JSON array | `[{"id":1,"login":"bruno"}]` |
| `checkbox` | Boolean | `true` |
| `url` | String | `"https://..."` |
| `email` | String | `"[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)
- [Notion Data Sources (2025)](https://www.notionapps.com/blog/notion-data-sources-update-2025/)
- [react-notion-x](https://github.com/NotionX/react-notion-x)
- [FlowDeck — NOTION_DATABASE_TASKS_GUIDE.md](docs/NOTION_DATABASE_TASKS_GUIDE.md)
- [FlowDeck — ROADMAP.md](ROADMAP.md)