docs: Guide Notion Database & Tasks — analyse complète pour FlowDeck
FlowDeck CI / test (push) Failing after 4s
FlowDeck CI / docker (push) Has been skipped

- Anatomie des Databases Notion (Collection/CollectionView/Pages)
- 21 types de propriétés, 6 types de vues, Relations & Rollups
- Architecture des Tasks (sub-items auto-référencés, dépendances)
- Plan d'implémentation en 6 phases avec SQL, Python et HTML
- Rétrocompatibilité avec les boards Gitea existants
This commit is contained in:
2026-07-09 17:41:21 -04:00
parent f04003f060
commit 1dea15ad90
+822
View File
@@ -0,0 +1,822 @@
# Guide Technique — Recréer Database & Tasks de Notion dans FlowDeck
> **Version:** 1.0 · **Date:** 2026-07-10
> **Auteur:** Hermes-Deepin · **Cible:** FlowDeck v1.2+
---
## Table des matières
1. [Introduction](#introduction)
2. [Comprendre Notion : le modèle « Blocs & Collections »](#1-comprendre-notion--le-modèle-blocs--collections)
3. [La Database Notion : anatomie complète](#2-la-database-notion--anatomie-complète)
4. [Les Tasks Notion : un cas spécial de database](#3-les-tasks-notion--un-cas-spécial-de-database)
5. [Comment Notion connecte Database et Tasks](#4-comment-notion-connecte-database-et-tasks)
6. [Implémentation dans FlowDeck : plan pas à pas](#5-implémentation-dans-flowdeck--plan-pas-à-pas)
7. [Références](#6-références)
---
## Introduction
L'objectif est de **recréer dans FlowDeck** les deux fonctionnalités les plus puissantes de Notion :
- **Les Databases** — tables structurées avec propriétés typées, vues multiples, filtres et tris
- **Les Tasks** — sous-tâches hiérarchiques avec dépendances, liées aux databases
Ces deux systèmes sont **profondément interconnectés** dans Notion : les tâches ne sont qu'une forme spécialisée de database, avec un parent auto-référencé et des propriétés de dépendance.
### État actuel de FlowDeck (v1.2)
FlowDeck a déjà une base solide :
```
✅ Boards (Kanban lié à Gitea) → table `boards`
✅ Cards (issues → colonnes) → table `cards`
✅ Colonnes custom → `project_properties` + `property_values`
✅ Checklists par issue → `checklists` + `checklist_items`
✅ Vues multiples → Kanban, Table, Status, TeamLoad, Detailed
✅ Notes Markdown → `notes`
✅ Pages (ébauche Notion editor) → `pages` (content, parent_id, sort_order, content_format)
⚠️ Database concept → ABSENT (pas de notion de collection/row)
❌ Relations entre projets → ABSENT
❌ Sub-tasks / Parent-item → ABSENT
❌ Rollups / Formules → ABSENT
❌ Dependencies (bloque/bloqué par) → ABSENT
```
---
## 1. Comprendre Notion : le modèle « Blocs & Collections »
### 1.1 Le principe fondateur : « Everything is a Block »
Dans Notion, **chaque élément de contenu est un bloc**. Un bloc peut être :
```
┌──────────────────────────────────────┐
│ BLOC │
│ ├─ id: UUID │
│ ├─ type: "paragraph" │
│ │ "heading_1" │
│ │ "to_do" │
│ │ "bulleted_list_item" │
│ │ "child_page" │
│ │ "child_database" │
│ │ "collection_view_page" │ ← Database
│ │ ... │
│ ├─ content: [...] (blocs enfants) │
│ ├─ properties: {} (metadata) │
│ └─ parent: {type, id} │
└──────────────────────────────────────┘
```
### 1.2 Les 3 couches d'une Database Notion
Une database Notion est un assemblage de **3 concepts distincts** :
```
┌────────────────────────────────────────────────────────────┐
│ DATABASE NOTION │
│ │
│ ┌─────────────┐ ┌──────────────────┐ ┌────────────┐│
│ │ COLLECTION │ │ COLLECTION_VIEW │ │ PAGES ││
│ │ (schema) │◄───│ (présentation) │◄───│ (data) ││
│ │ │ │ │ │ ││
│ │ properties │ │ type: table │ │ row 1 ││
│ │ - Name │ │ board │ │ row 2 ││
│ │ - Status │ │ calendar │ │ row 3 ││
│ │ - Priority │ │ gallery │ │ ... ││
│ │ - Due Date │ │ list │ │ ││
│ │ - Assignee │ │ timeline │ │ ││
│ │ │ │ │ │ ││
│ │ │ │ filters │ │ ││
│ │ │ │ sorts │ │ ││
│ │ │ │ grouping │ │ ││
│ │ │ │ format options │ │ ││
│ └─────────────┘ └──────────────────┘ └────────────┘│
│ │
│ 1 SCHÉMA N VUES (filtres N LIGNES │
│ partagé + tris différents) (pages filles) │
└────────────────────────────────────────────────────────────┘
```
**Concept clé** : Une database n'est PAS une table SQL. C'est une **collection de pages**, chaque page ayant un schéma de propriétés commun, affiché via des vues configurables.
### 1.3 Le cycle de vie d'une Database
```
Création Consultation Modification
───────── ──────────── ────────────
1. L'utilisateur choisit 4. Les vues lisent le 7. Édition inline :
un type de vue schéma + les pages - click → édite valeur
(table, board...) - drag → réorganise
5. Filtres/sorts/tri - shortcuts → ajoute ligne
2. Notion crée : appliqués
- 1 Collection 8. Changement de vue =
- 1 CollectionView 6. Rendu en temps réel même données,
- 1 CollectionViewPage autre présentation
- + pages vides optionnelles
9. Ajout propriété =
3. L'utilisateur définit nouveau champ sur
les propriétés (schéma) TOUTES les pages
```
---
## 2. La Database Notion : anatomie complète
### 2.1 Propriétés (le schéma)
Notion supporte **21 types de propriétés**. Toutes sont définies au niveau de la Collection :
```
Type │ Description │ Exemple de valeur
──────────────────┼────────────────────────────┼──────────────────
Title │ Le titre de la page (obligatoire, unique)
Text │ Texte libre │ "Description..."
Number │ Nombre (décimales, format) │ 42 / 12.5%
Select │ Choix unique │ "En cours"
Multi-select │ Choix multiples │ ["Frontend","Backend"]
Status │ Select avec couleur forcée │ "Done" (vert)
Date │ Date + heure optionnelle │ 2026-07-15
Person │ Mention utilisateur │ ["bruno"]
Files & media │ Uploads │ [url1, url2]
Checkbox │ Booléen │ true / false
URL │ Lien cliquable │ "https://..."
Email │ Email cliquable │ "[email protected]"
Phone │ Téléphone cliquable │ "+1..."
Formula │ Calcul (JS-like) │ prop("Prix")*prop("Qté") ← CLÉ
Relation │ Lien vers autre database │ [page_id_1, page_id_2] ← CLÉ
Rollup │ Agrégation via relation │ sum, avg, count, min, max ← CLÉ
Created time │ Date de création (auto) │ 2026-01-15T10:30:00Z
Created by │ Auteur (auto) │ "bruno"
Last edited time │ Dernière édition (auto) │ 2026-07-09T18:00:00Z
Last edited by │ Dernier éditeur (auto) │ "bruno"
Unique ID │ ID incrémental (auto) │ 42
Button │ Déclenche action (nouveau) │ —
AI Summary │ Résumé AI (nouveau) │ "Ce projet..."
```
### 2.2 Views (les vues)
Une database peut avoir **N vues**, chaque vue avec :
```
┌─────────────────────────────────────────┐
│ Vue "Kanban" │
│ ├─ type: board │
│ ├─ group_by: "Status" │ ← Groupement
│ ├─ filter: {Status ≠ "Archivé"} │ ← Filtres
│ ├─ sort: [Due Date ASC, Priority DESC] │ ← Tris
│ ├─ visible_properties: [Name, Assignee, Due Date] │
│ ├─ card_size: medium │
│ └─ cover_image_property: "Files" │
└─────────────────────────────────────────┘
```
**6 types de vues :**
| Vue | Usage | Particularité |
|-----|-------|---------------|
| **Table** | Liste classique | Colonnes = propriétés |
| **Board** | Kanban | Groupé par propriété Select/Status |
| **Timeline** | Gantt | Axe X = propriété Date |
| **Calendar** | Calendrier | Groupé par propriété Date (jour/semaine/mois) |
| **Gallery** | Cartes visuelles | Cover image + preview |
| **List** | Liste compacte | Comme table mais 1 colonne + preview |
**Filtres avancés :**
```
Filtre 1 : Status IS "En cours" → filtre simple
Filtre 2 : Due Date IS BEFORE "today" → filtre date
Filtre 3 : Priority CONTAINS "P1" → filtre multi-select
Groupe : AND / OR entre les filtres
Sous-groupe : (FiltreA OR FiltreB) AND FiltreC
```
### 2.3 Relations et Rollups (les super-pouvoirs)
C'est LE mécanisme qui rend Notion puissant :
```
┌─────────────────────┐ ┌──────────────────────┐
│ DATABASE A │ │ DATABASE B │
│ « Projets » │ │ « Tâches » │
│ │ │ │
│ ┌─────────────┐ │ relation│ ┌──────────────┐ │
│ │ Nom │ │◄───────►│ │ Titre │ │
│ │ Description │ │ │ │ Priorité │ │
│ │ Statut │ │ │ │ Projet ──────┼──┐ │
│ └─────────────┘ │ │ │ Assignee │ │ │
│ │ │ └──────────────┘ │ │
│ │ │ │ │
│ ROLLUP « Nb tâches»│◄────────┼─── count(Tâches) │ │
│ ROLLUP « Avancement│◄────────┼─── avg(%Complété) │ │
│ ROLLUP « Prochaine │◄────────┼─── min(Due Date) │ │
└─────────────────────┘ └───────────────────┘──┘
```
**Relation** = pointeur bidirectionnel entre pages de 2 databases différentes
**Rollup** = agrégation (COUNT, SUM, AVG, MIN, MAX, RANGE, UNIQUE) sur une propriété de la database liée
---
## 3. Les Tasks Notion : un cas spécial de database
### 3.1 Architecture des tâches
Contrairement à ce qu'on pourrait penser, **Notion n'a pas de module « Tasks » séparé**.
```
Les tâches sont simplement des PAGES dans une DATABASE
qui possède des propriétés spécifiques de type tâche.
```
Voici comment Notion les distingue :
```
┌─────────────────────────────────────────────────────────────┐
│ DATABASE « Tasks » (ou n'importe quelle DB) │
│ │
│ Propriétés « tâche » │ Propriétés « standard » │
│ ────────────────────────── │ ────────────────────────── │
│ ✅ Sub-item (parent) │ • Titre (obligatoire) │
│ ✅ Dependencies (bloque) │ • Status (Select) │
│ ✅ Due Date │ • Priority (Select) │
│ ✅ Assignee │ • Tags (Multi-select) │
│ ✅ Reminder │ • ... │
│ │
│ La magie : ces propriétés SONT la database. │
│ Pas de modèle « Task » séparé. │
└─────────────────────────────────────────────────────────────┘
```
### 3.2 Sub-items (sous-tâches)
Les sous-tâches utilisent une **relation auto-référencée** :
```
┌──────────────────────────────────────┐
│ Table « tasks » (dans la DB) │
│ │
│ id │ title │ parent_id │
│ ───┼────────────────┼───────────────│
│ 1 │ Refonte UI │ NULL │ ← Tâche parent
│ 2 │ Nouveau header │ 1 │ ← Sous-tâche de 1
│ 3 │ Dark mode │ 1 │ ← Sous-tâche de 1
│ 4 │ Tests header │ 2 │ ← Sous-sous-tâche de 2
│ 5 │ Déploiement │ NULL │ ← Autre tâche parent
│ │
│ Propriétés spéciales : │
│ ├─ « Sub-item » = relation vers │
│ │ la même database │
│ └─ « Parent item » = l'inverse │
│ (auto-créé par Notion) │
└──────────────────────────────────────┘
```
**Comment ça fonctionne techniquement :**
1. L'utilisateur active « Sub-items » dans une database
2. Notion crée automatiquement 2 propriétés de type **Relation** pointant vers la même database :
- `Sub-item` (relation → même DB)
- `Parent item` (relation inverse, auto-générée)
3. Dans l'interface Kanban, les sous-tâches apparaissent imbriquées sous le parent
4. La récursion est illimitée (tâche → sous-tâche → sous-sous-tâche → ...)
### 3.3 Dependencies (dépendances)
Les dépendances bloquantes sont une **seconde relation auto-référencée** :
```
┌──────────────────────────────────────┐
│ Relation « Bloque » │
│ │
│ Tâche A ──bloque──► Tâche B │
│ Tâche B ──bloquée par──► Tâche A │
│ │
│ Règles métier : │
│ ├─ B ne peut pas être « Done » │
│ │ tant que A n'est pas « Done » │
│ ├─ Si A est déplacée, B suit │
│ │ (optionnel, selon config) │
│ └─ Timeline affiche les flèches │
│ A ────────► B │
└──────────────────────────────────────┘
```
### 3.4 La vue « My Tasks » (vue agrégée)
La killer feature de Notion : un dashboard qui regroupe TOUTES les tâches, peu importe leur database d'origine.
```
┌──────────────────────────────────────────────────┐
│ MY TASKS │
│ ┌───────────────────────────────────────────┐ │
│ │ Projet Alpha ┌──────────────────────┐│ │
│ │ ├─ Design maquette│ Assignee: Bruno ││ │
│ │ └─ Tests UI │ Due: 15 juillet ││ │
│ │ └──────────────────────┘│ │
│ │ Projet Beta │ │
│ │ ├─ API endpoint │ │
│ │ └─ Documentation │ │
│ │ Daily Tasks │ │
│ │ └─ Réunion standup │ │
│ └───────────────────────────────────────────┘ │
│ │
│ Agrège TOUTES les databases ayant : │
│ ├─ Une propriété « Person » = current user │
│ └─ Un Status ≠ « Done » │
└──────────────────────────────────────────────────┘
```
**Fonctionnement interne :**
1. Notion scanne TOUTES les databases du workspace
2. Filtre les pages où `Assignee = current_user` ET `Status ≠ Done/Completed`
3. Regroupe par database d'origine (ou par projet parent)
4. Affiche avec tri par `Due Date` ASC
5. Cette vue est **read-only logique** (pas une vraie database)
---
## 4. Comment Notion connecte Database et Tasks
### 4.1 Le diagramme de dépendance conceptuel
```
DATABASE (concept racine)
│
┌──────────────┼──────────────┐
▼ ▼ ▼
COLLECTION PAGES (rows) COLLECTION_VIEWS
(schéma) │ (présentations)
│ │ │
│ ├─ Title ├─ Table
├─ Text ├─ Properties ├─ Board
├─ Select │ (valeurs ├─ Calendar
├─ Date │ par page) ├─ Timeline
├─ Person │ ├─ Gallery
├─ Relation ────┼──► SELF └─ List
│ └── Sub-items │ ┌── Parent
│ └── Deps │ └── Blocked by
├─ Rollup ←──────┼──┘ (count, sum, avg via relation)
├─ Formula │
└─ ... │
│
┌──────┴──────┐
│ TASKS │ (surcouche métier)
│ │
│ Ne sont PAS │
│ un modèle │
│ séparé — │
│ ce sont des │
│ PAGES avec │
│ propriétés │
│ spécifiques │
└─────────────┘
```
### 4.2 Les invariants du modèle
1. **Une database = 1 Collection** (schéma de propriétés)
2. **1 Collection = N CollectionViews** (vues)
3. **1 CollectionView = 1 type** (table | board | calendar | timeline | gallery | list)
4. **Les pages d'une database sont des blocs** (comme toute page Notion)
5. **Une propriété « Relation » pointe vers une autre database** (pas une table SQL)
6. **Une propriété « Rollup » agrège via une Relation existante**
7. **Les sub-items sont une Relation auto-référencée**
8. **Les dépendances sont une Relation auto-référencée avec contrainte métier**
---
## 5. Implémentation dans FlowDeck : plan pas à pas
### 5.1 Phase 1 — Le concept de Database (semaine 1)
**Objectif :** Abstraire le concept de « board Gitea » en « database » réutilisable.
#### Nouvelle table : `collections`
```sql
CREATE TABLE collections (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
description TEXT DEFAULT '',
icon TEXT DEFAULT '📋',
-- Peut être liée à un projet Gitea (optionnel)
gitea_owner TEXT,
gitea_repo TEXT,
-- Schéma de propriétés en JSON (flexible, évolutif)
schema_json TEXT NOT NULL DEFAULT '[]',
-- Format: [{"name":"Status","type":"select","options":["Todo","Done"],"position":0}, ...]
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
```
#### Nouvelle table : `collection_pages` (remplace `cards`)
```sql
CREATE TABLE collection_pages (
id INTEGER PRIMARY KEY AUTOINCREMENT,
collection_id INTEGER NOT NULL REFERENCES collections(id) ON DELETE CASCADE,
-- Peut être liée à une issue Gitea
gitea_issue_id INTEGER,
gitea_issue_number INTEGER,
-- Titre de la page (obligatoire)
title TEXT NOT NULL DEFAULT '',
-- Position dans la collection
position INTEGER NOT NULL DEFAULT 0,
-- Sub-items (auto-référence)
parent_id INTEGER REFERENCES collection_pages(id),
-- Valeurs des propriétés en JSON
property_values_json TEXT NOT NULL DEFAULT '{}',
-- Format: {"Status":"Done","Priority":"P1","DueDate":"2026-07-15"}
-- Métadonnées temporelles
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
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);
```
#### Nouvelle table : `collection_views`
```sql
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
-- Configuration JSON
config_json TEXT NOT NULL DEFAULT '{}',
-- Format:
-- {
-- "group_by": "Status", ← board/calendar uniquement
-- "filters": [ ← tous types
-- {"property":"Status","operator":"is_not","value":"Done"},
-- {"property":"DueDate","operator":"is_before","value":"today"}
-- ],
-- "filter_conjunction": "and", ← "and" | "or"
-- "sorts": [ ← tous types
-- {"property":"Priority","direction":"asc"},
-- {"property":"DueDate","direction":"desc"}
-- ],
-- "visible_properties": ["Title","Status","Assignee","DueDate"],
-- "card_size": "medium", ← board/gallery
-- "cover_property": "Files", ← board/gallery
-- "date_property": "DueDate", ← calendar/timeline
-- "date_range_property": "EndDate" ← timeline (optionnel)
-- }
position INTEGER NOT NULL DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
```
### 5.2 Phase 2 — Le système de propriétés (semaine 1-2)
#### Nouvelle table : `collection_properties` (remplace `project_properties`)
```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',
-- Types: title, text, number, select, multi_select, status,
-- date, person, checkbox, url, email, phone,
-- relation, rollup, formula,
-- created_time, created_by, last_edited_time, last_edited_by
-- Options pour select/multi_select/status
options_json TEXT DEFAULT '[]',
-- Format: [{"name":"Todo","color":"gray"},{"name":"Done","color":"green"}]
-- Pour les relations
related_collection_id INTEGER REFERENCES collections(id),
reverse_name TEXT, -- nom de la relation inverse (auto-généré)
-- Pour les rollups
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 les formules
formula_expression TEXT,
-- Position dans la liste des propriétés
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)
);
```
**Mapping des types Notion → FlowDeck :**
```python
PROPERTY_TYPES = {
"title": {
"storage": "string",
"validation": "max 2000 chars",
"indexed": True,
"unique_per_collection": False,
},
"text": {
"storage": "string",
"validation": "any text",
},
"number": {
"storage": "float",
"validation": "numeric",
"format_options": ["number", "percent", "currency", "dollar", "euro", "pound", "yen"],
},
"select": {
"storage": "string (from options_json)",
"validation": "must match one option",
},
"multi_select": {
"storage": "JSON array of strings",
"validation": "each must match option",
},
"status": {
"storage": "string (from options_json) + color",
"validation": "must match one option",
"colors": ["gray","brown","orange","yellow","green","blue","purple","pink","red"],
},
"date": {
"storage": "ISO 8601 string or range",
"validation": "valid datetime",
"include_time": True, # configurable
},
"person": {
"storage": "JSON array of {id, login, avatar_url}",
"validation": "must be workspace user",
},
"checkbox": {
"storage": "boolean",
"validation": "true/false",
},
"relation": {
"storage": "JSON array of page IDs",
"validation": "must be existing page in related_collection",
"bidirectional": True,
},
"rollup": {
"storage": "computed — not stored directly",
"functions": ["count","count_values","empty","not_empty",
"sum","average","median","min","max","range"],
"depends_on": ["relation_property_id","target_property_id"],
},
"formula": {
"storage": "computed — not stored directly",
"expression": "JavaScript-like DSL",
"functions_available": [
"prop()", "now()", "today()", "if()", "concat()", "round()",
"dateAdd()", "dateSubtract()", "formatDate()", "toNumber()",
"contains()", "length()", "replace()", "replaceAll()",
],
},
}
```
### 5.3 Phase 3 — Sub-items et Dépendances (semaine 2)
#### Implémentation des sub-items (relation auto-référencée)
```python
# Dans le schema d'une database, activer Sub-items crée automatiquement :
def enable_sub_items(collection_id: int):
"""Active les sub-items sur une collection."""
# 1. Créer la propriété relation « Parent item » (si pas déjà)
parent_prop = create_property(
collection_id=collection_id,
name="Parent item",
prop_type="relation",
related_collection_id=collection_id, # AUTO-RÉFÉRENCE
reverse_name="Sub-item", # relation inverse
visible_in_views=False, # cachée dans les vues
)
# 2. Marquer la collection comme ayant des sub-items
mark_collection_feature(collection_id, "has_sub_items", True)
# 3. Mettre à jour la vue board pour afficher l'imbrication
# Le rendu Kanban doit maintenant supporter :
# - Groupe parent → sous-tâches indentées
# - Bouton « + Add sub-item » sous chaque carte
# - État du parent = agrégation des enfants
```
#### Rendu des sub-items dans le board
```html
<!-- Dans board_fragment.html, modifier le rendu des cartes -->
<div class="kanban-card" data-page-id="{{ page.id }}">
<!-- Carte parent -->
<div class="card-title">{{ page.title }}</div>
<!-- Sous-tâches -->
{% for sub in page.sub_items %}
<div class="sub-item" data-page-id="{{ sub.id }}">
├─ {{ sub.title }}
<span class="sub-status">{{ sub.properties.Status }}</span>
</div>
{% endfor %}
<!-- Bouton ajout sous-tâche -->
<button hx-post="/api/pages/{{ page.id }}/sub-items"
hx-swap="beforeend">
+ Sub-item
</button>
</div>
```
#### Implémentation des dépendances
```python
def enable_dependencies(collection_id: int):
"""Active les dépendances bloquantes."""
# Création de 2 propriétés relation
blocks = create_property(
collection_id=collection_id,
name="Blocks",
prop_type="relation",
related_collection_id=collection_id,
reverse_name="Blocked by",
)
# Contrainte métier : au niveau applicatif
# (pas de contrainte SQL pour garder la flexibilité)
def check_dependency_constraint(page_id: int, new_status: str):
"""Vérifie qu'une page peut passer à 'Done'
seulement si toutes les pages qu'elle bloque sont Done."""
blocked_pages = get_blocked_pages(page_id)
if new_status == "Done" and blocked_pages:
not_done = [p for p in blocked_pages
if p.status not in ("Done", "Cancelled")]
if not_done:
raise DependencyError(
f"Cannot mark as Done: still blocking {len(not_done)} tasks"
)
```
### 5.4 Phase 4 — « My Tasks » (vue agrégée) (semaine 2-3)
```python
# GET /my-tasks
async def my_tasks(request: Request):
"""Dashboard personnel : toutes les tâches assignées à l'utilisateur."""
user = get_current_user(request)
# 1. Scanner TOUTES les collections
collections = db.fetch_all("SELECT * FROM collections")
my_tasks = []
for col in collections:
schema = json.loads(col.schema_json)
# 2. Vérifier si la collection a une propriété Person
person_props = [p for p in schema if p["type"] == "person"]
if not person_props:
continue
# 3. Récupérer les pages assignées à l'utilisateur
pages = db.fetch_all("""
SELECT cp.*, cpv.value as property_values
FROM collection_pages cp
JOIN collection_properties cprop
ON cprop.collection_id = cp.collection_id
AND cprop.prop_type = 'person'
WHERE cp.collection_id = ?
AND cp.property_values_json LIKE ?
""", (col.id, f'%"{user.login}"%'))
my_tasks.extend([
{"collection": col, "page": p, "schema": schema}
for p in pages
])
# 4. Grouper par collection
grouped = {}
for task in my_tasks:
col_name = task["collection"].name
if col_name not in grouped:
grouped[col_name] = []
grouped[col_name].append(task)
# 5. Trier par Due Date
for tasks in grouped.values():
tasks.sort(key=lambda t: t["page"].get_property("DueDate") or "9999")
return render_template("my_tasks.html", tasks=grouped)
```
### 5.5 Phase 5 — Rétrocompatibilité avec Gitea
**Stratégie de migration progressive :**
```python
# Phase 5a : Wrapper — les boards Gitea existants
# deviennent des collections avec une couche de compatibilité
class GiteaBoardCompat:
"""Adaptateur : ancien board Gitea → nouvelle Collection."""
@staticmethod
def from_board(board_row):
"""Convertit un board existant en collection."""
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,
}
# Phase 5b : Nouvelle route hybride
# GET /board/{owner}/{repo} → détecte si c'est un board Gitea legacy
# ou une collection pure, et route vers le bon renderer
```
### 5.6 Résumé du plan d'implémentation
```
Semaine 1 Semaine 2 Semaine 3
──────── ───────── ─────────
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Database │ │ Properties │ │ My Tasks │
│ Concept │ ──► │ Avancées │ ──► │ Dashboard │
│ │ │ │ │ │
│ • collections│ │ • Relations │ │ • Vue agrégée│
│ • pages │ │ • Rollups │ │ • Filtres │
│ • views │ │ • Formules │ │ globaux │
│ • filters │ │ • Auto-prop │ │ • Calendrier │
│ • sorts │ │ computed │ │ intégré │
│ │ │ │ │ │
│ Sub-items │ │ Dependencies │ │ UI polish │
│ (parent_id) │ ──► │ + contraintes│ ──► │ + tests │
└──────────────┘ └──────────────┘ └──────────────┘
```
---
## 6. Références
- [Notion API — Property Object](https://developers.notion.com/reference/property-object)
- [Notion Help — Intro to 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)
- [react-notion-x — Block types reference](https://github.com/NotionX/react-notion-x)
- [Notion Data Sources update (2025)](https://www.notionapps.com/blog/notion-data-sources-update-2025/)
### Différences clés FlowDeck vs Notion
| Concept | Notion | FlowDeck (cible) |
|---------|--------|------------------|
| **Database** | Collection + Pages + Views | `collections` + `collection_pages` + `collection_views` |
| **Schéma** | Properties sur la Collection | `collection_properties` au niveau collection |
| **Valeurs** | Dans chaque page (bloc) | `property_values_json` dans chaque `collection_page` |
| **Sub-items** | Relation auto-référencée | `parent_id` auto-référence dans `collection_pages` |
| **Dependencies** | Relation auto-référencée + contrainte | Idem + check applicatif au changement de statut |
| **My Tasks** | Scan cross-database + filtre | Route `/my-tasks` + JOIN sur `collection_pages` |
| **Gitea sync** | N/A (pas de Gitea) | Les pages peuvent être liées à `gitea_issue_id` |
| **Stockage** | Cloud distribué (PostgreSQL + cache) | SQLite local (1 fichier) |