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
1309 lines
61 KiB
Markdown
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)
|