- 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
823 lines
36 KiB
Markdown
823 lines
36 KiB
Markdown
# 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) |
|