feat: v6.3.0 API publique complete v2 (REST /api/v2, scopes, OpenAPI)
- Router api_v2.py (~100 endpoints) : tokens, users, workspaces/members, collections, pages, proprietes, vues/dashboards, commentaires/mentions, notifications, favoris/tags/recents, partage/publish, historique, sprints, templates, export/import, forges, recherche FTS, admin, webhooks CRUD - Helpers api_v2_helpers.py : Bearer unifie (sha256/expires_at/extension_devices), scopes hierarchiques read<write<admin, pagination + X-Total-Count, ISO-8601, RFC 7807, idempotence, audit, rate-limit par token - Migration 20 : api_tokens.scopes/expires_at, webhook_deliveries, api_audit_log, idempotency_keys - main.py : handler d'erreurs unifie StarletteHTTPException, /docs + /redoc - config : PUBLIC_API_INSECURE_OK (dev only), API_V2_RATE_LIMIT_PER_TOKEN - OpenAPI docs/openapi-v2.json (402 chemins), tests/test_public_api_v2.py (24) - Docs : CHANGELOG (v6.2.0/6.2.1 clipper + v6.3.0), ROADMAP, API_GUIDE_V6, V6_Web_Clipper, README, ARCHITECTURE, /help - Suite complete 668 verte, ruff OK
This commit is contained in:
@@ -1,5 +1,86 @@
|
||||
# Changelog - FlowDeck
|
||||
|
||||
## v6.3.0 (2026-09-21) — API publique complète v2 (REST + scopes + OpenAPI)
|
||||
|
||||
> L'API publique `/api/v2` devient une surface REST complète façon Notion : CRUD sur tous les domaines (collections, pages, propriétés, vues, commentaires, notifications, favoris, tags, partage, sprints, templates, forges, admin…), auth Bearer + scopes hiérarchiques, pagination/filtres/tri, erreurs RFC 7807, idempotence, audit et webhooks. Wrappers sur les services existants — un seul chemin de code.
|
||||
|
||||
### Migrations (v20)
|
||||
|
||||
- `api_tokens` : colonnes `scopes TEXT DEFAULT 'read,write'` et `expires_at TIMESTAMP` (ALTER idempotents, rétro-compatibles : anciens tokens → `read,write`).
|
||||
- Nouvelles tables `api_audit_log` (user, token, action, ressource, IP), `webhook_deliveries` (statut, http_code, durée, payload) et `idempotency_keys` (clé → réponse rejouée).
|
||||
|
||||
### Auth, scopes & conventions (`app/services/api_v2_helpers.py`)
|
||||
|
||||
- **Bearer unifié** `resolve_bearer_token()` / `get_bearer_user()` : hash sha256 sur `api_tokens`, support `extension_devices` (clipper) et legacy `user_tokens`, vérification `revoked` + `expires_at`, mise à jour `last_used_at`.
|
||||
- **Scopes hiérarchiques** `read < write < admin` (un scope supérieur satisfait un besoin inférieur), dépendance `require_scope()` → 403 explicite ; `validate_scopes_input()` → 400 sur scope inconnu.
|
||||
- **Pagination** `parse_pagination()` (défaut 30 / max 100) + header `X-Total-Count` ; **ISO-8601 UTC** via `to_iso8601()` ; parsing des colonnes `*_json`.
|
||||
- **Erreurs RFC 7807** `application/problem+json` (`type/title/status/detail/instance`) pour tout `/api/v2`, via le handler unifié sur `StarletteHTTPException` (`app/main.py`).
|
||||
- **Idempotence** `Idempotency-Key` sur les POST de création (`check_idempotency` / `store_idempotency`).
|
||||
- **Audit** `audit_log()` sur toutes les mutations v2 (`api_audit_log`).
|
||||
- **Rate limit par token** `check_v2_rate_limit()` (300 req/min, configurable `API_V2_RATE_LIMIT_PER_TOKEN`).
|
||||
- **`public_api_insecure_ok`** : le token de dev `fd-public-key` n'est accepté que si `PUBLIC_API_INSECURE_OK=true` (dev/test) — refusé par défaut en production.
|
||||
|
||||
### Router `app/routers/api_v2.py` (`prefix /api/v2`, ~100 endpoints)
|
||||
|
||||
- **Tokens** : `POST /tokens` (name, scopes, expires_at, montré une fois), `GET /tokens` (prefix only, jamais le hash), `DELETE /tokens/{id}`, `POST /tokens/{id}/rotate`.
|
||||
- **Users** : `GET/PATCH /users/me`, `GET /users/search`, préférences.
|
||||
- **Workspaces & membres** : CRUD + `GET/POST/PATCH/DELETE .../members`.
|
||||
- **Collections** : CRUD, `/linked`, `/task`, `/sources`.
|
||||
- **Pages** : `GET /collections/{id}/pages` (filtres `filter[prop]`, `sort`, `fields`, `query`), CRUD, `/restore`, `/move`, `/sub-items`, `/dependencies`.
|
||||
- **Propriétés** : CRUD, `/relation`, `evaluate-formula`, `compute-rollup`.
|
||||
- **Vues & dashboards** : CRUD + `save-as`.
|
||||
- **Comments / mentions / notifications / favoris / tags / recents**.
|
||||
- **Partage & publication** : `pages/{id}/shares`, `publish` / dé-publish (`/p/<slug>`).
|
||||
- **Historique** (`page_history` + `page_versions`) + restore.
|
||||
- **Sprints** (CRUD, assign, burndown), **templates** (page + database, apply), **export/import** (MD/HTML/PDF, CSV).
|
||||
- **Forges** : `GET /projects`, `/projects/{owner}/{repo}/tree`.
|
||||
- **Recherche** FTS5 (`GET /search`, repli LIKE).
|
||||
- **Admin** : users (list/patch/delete) + `GET /admin/audit-logs`.
|
||||
- **Webhooks** : CRUD subscriptions + `POST /{id}/test` + `GET /{id}/deliveries` (CRUD simple d'abord).
|
||||
|
||||
### OpenAPI & config
|
||||
|
||||
- `docs_url="/docs"` + `redoc_url="/redoc"` activés ; `docs/openapi-v2.json` généré (402 chemins).
|
||||
- `.env.example` : `PUBLIC_API_INSECURE_OK=false`, `API_V2_RATE_LIMIT_PER_TOKEN=300`.
|
||||
- `VERSION` et `app/main.py` passés à **6.3.0**.
|
||||
|
||||
### Tests & robustesse
|
||||
|
||||
- `tests/test_public_api_v2.py` — **24 tests** (auth 401, token lifecycle, scopes read/write/admin, pagination + `X-Total-Count`, CRUD collections/pages/propriétés/vues, filtres, RFC7807, idempotence, search, notifications, tags, sharing, webhooks, workspaces, sprints, templates, admin).
|
||||
- `tests/conftest.py` + fixtures locales : `PUBLIC_API_INSECURE_OK=true` (le token de dev reste testable sans impacter la prod).
|
||||
- Suite complète **668 verte** (`pytest -n auto`) ; `ruff check app tests` OK.
|
||||
|
||||
## v6.2.1 (2026-09-20) — Web Clipper : polish & fix bloc bookmark
|
||||
|
||||
> Patch UX et correctif bloc bookmark pour l'extension v6.2.0.
|
||||
|
||||
- **Extension — bouton flottant** — rond transparent draggable (évite de masquer le contenu), toggle d'affichage persistant (chrome.storage), clic → popup de capture ; position restaurée au reload.
|
||||
- **Refresh auto sidebar** — après `POST /api/v2/web-clipper/clip` le workspace sidebar est rafraîchi sans reload (polling + event `clipper:clipped`) pour que la page clippée apparaisse immédiatement.
|
||||
- **Bloc `bookmark`** — le service `create_page_from_clip()` renvoie désormais un bloc `bookmark` fidèle au payload OG (URL d'origine + `embed_src` résolu via `embeds.py`), rendu correct en éditeur / page publique `/p/<slug>` / exports MD/HTML/PDF (préserve favicon + description).
|
||||
- **Fix éditeur** `7f998fa` — SyntaxError `duplicate inner` dans `_page_editor_scripts.html` (variable `inner` redéclarée lors du drag & drop multi-sélection) corrigé ; autosave + WS realtime non bloqués.
|
||||
- **Tests & build** — `flowdeck-clipper.zip` régénéré (`static/extension/`), `ruff`/`eslint` verts, `16 tests` `test_web_clipper.py` verts.
|
||||
|
||||
## v6.2.0 (2026-09-19) — Web Clipper : extension navigateur (capture article/selection/bookmark/screenshot)
|
||||
|
||||
> Capturez n'importe quelle page web en une page FlowDeck : article complet, sélection, bookmark ou screenshot — depuis une extension Manifest V3 (Chrome/Edge/Firefox) + API directe `POST /api/v2/web-clipper/clip`.
|
||||
|
||||
### Extension navigateur (`extension/` + `static/extension/`)
|
||||
|
||||
- **Manifest V3** — `manifest.json` (permissions `activeTab, storage, scripting, contextMenus`, `host_permissions <all_urls>`), `background.js` (service worker OAuth + clip → fetch Bearer), `content.js` (bouton flottant + menu contextuel sélection + `Ctrl+Shift+C`), `popup.html`/`popup.js` (sélection workspace, type de capture), `clipper.css`, icônes 16/32/48/128, bundle `flowdeck-clipper.zip` servi à `/static/extension/`.
|
||||
- **Types de capture** — `article` (HTML complet → `sanitize_html` + `html_to_blocks`), `selection` (sélection HTML → Markdown), `bookmark` (URL + OG → carte bookmark v5.5.0), `screenshot` (base64 → upload image + page). Cap `10 MB` / `200 blocs`, garde SSRF inexistante (validation URL), sanitisation HTML côté serveur.
|
||||
|
||||
### Serveur (`app/routers/web_clipper.py` + `app/services/web_clipper.py`)
|
||||
|
||||
- **Endpoints** `prefix /api/v2/web-clipper` — `POST /clip` (crée page via `create_page_from_clip()` + `log_clip()`), `GET /status` (auth + compteurs devices/clips), `POST /auth/verify` (enregistre device `register_device()` → token `fd_…` montré une fois), `GET /devices`, `DELETE /devices/{id}` ; page HTML `GET /extensions` (téléchargement + liste devices/clips).
|
||||
- **Auth triple** — session cookie `flowdeck_session` OU Bearer `api_tokens` (hash `sha256`) OU Bearer `extension_devices` OU legacy `user_tokens` (`_user_from_request()`), rate-limit `50 clips/heure/device` (`_check_rate_limit`, 429), payload normalisé (`url`, `title`, `content`, `content_type`, `selection_html`, `image_base64`, `tags`, `target_workspace_id`).
|
||||
- **Tables migration 19** — `extension_devices` (`user_id, extension_name, device_id, device_name, token_hash, scopes, last_used_at, revoked, UNIQUE(user_id,extension_name,device_id)`) + `extension_clips` (`user_id, device_id, clip_type, source_url, target_page_id, target_workspace_id, title`) avec index `idx_ext_*`.
|
||||
- **Settings UI** — onglet Extensions dans `app/templates/settings.html` (devices, clips count, revoke, token copy).
|
||||
|
||||
### Tests & wiring
|
||||
|
||||
- `tests/test_web_clipper.py` — **16 tests** (sanitize, blocks, article/bookmark/selection/screenshot, Bearer, rate-limit, devices, `/extensions`).
|
||||
- Wiring `app/main.py:50,158` — `web_clipper_api_router` + `web_clipper_router` inclus ; `VERSION` bump `6.1.0 → 6.2.0`.
|
||||
|
||||
## v6.1.0 (2026-09-19) — Granular Permissions : page / collection / property ACL + groupes + audit
|
||||
|
||||
> Permissions fines héritables : chaque page / database / propriété peut être restreinte à des utilisateurs ou groupes explicites. L'héritage suit la chaîne page → collection → workspace (moindre privilège), avec bypass owner/admin et audit complet.
|
||||
|
||||
Reference in New Issue
Block a user