feat: v6.3.0 API publique complete v2 (REST /api/v2, scopes, OpenAPI)
FlowDeck CI / lint (push) Successful in 1m13s
FlowDeck CI / test (push) Successful in 9m20s
FlowDeck CI / docker (push) Successful in 1m10s

- 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:
2026-09-20 13:19:29 -04:00
parent ea19d1d050
commit 95bc861cdb
25 changed files with 25078 additions and 66 deletions
+61
View File
@@ -843,6 +843,67 @@ def _migration_web_clipper(conn: sqlite3.Connection) -> None:
)
@register(20, "v6.3.0: api v2 — scopes, expires_at, audit, webhooks, idempotency")
def _migration_v630_api_v2(conn: sqlite3.Connection) -> None:
"""v6.3.0 — API publique complète v2.
``api_tokens`` — adds ``scopes`` + ``expires_at`` (idempotent ALTER).
``webhook_deliveries`` — delivery log for outbound webhooks (CRUD simple phase 1).
``api_audit_log`` — immutable audit trail for v2 mutations.
``idempotency_keys`` — Idempotency-Key support for POST creations.
"""
# api_tokens extra columns
_cols = {r[1] for r in conn.execute("PRAGMA table_info(api_tokens)").fetchall()}
if "scopes" not in _cols:
conn.execute("ALTER TABLE api_tokens ADD COLUMN scopes TEXT NOT NULL DEFAULT 'read,write'")
if "expires_at" not in _cols:
conn.execute("ALTER TABLE api_tokens ADD COLUMN expires_at TIMESTAMP")
# Backfill existing tokens without scopes
try:
conn.execute("UPDATE api_tokens SET scopes='read,write' WHERE scopes='' OR scopes IS NULL")
except Exception:
pass
conn.execute(
"""CREATE TABLE IF NOT EXISTS api_audit_log (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER REFERENCES users(id) ON DELETE SET NULL,
token_id INTEGER REFERENCES api_tokens(id) ON DELETE SET NULL,
action TEXT NOT NULL,
resource_type TEXT NOT NULL DEFAULT '',
resource_id TEXT NOT NULL DEFAULT '',
ip_address TEXT NOT NULL DEFAULT '',
detail TEXT NOT NULL DEFAULT '',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)"""
)
conn.execute("CREATE INDEX IF NOT EXISTS idx_api_audit_user ON api_audit_log(user_id, created_at)")
conn.execute("CREATE INDEX IF NOT EXISTS idx_api_audit_resource ON api_audit_log(resource_type, resource_id)")
conn.execute(
"""CREATE TABLE IF NOT EXISTS webhook_deliveries (
id INTEGER PRIMARY KEY AUTOINCREMENT,
webhook_id INTEGER NOT NULL REFERENCES webhook_subscriptions(id) ON DELETE CASCADE,
status TEXT NOT NULL DEFAULT 'pending',
http_code INTEGER,
error TEXT NOT NULL DEFAULT '',
duration_ms INTEGER NOT NULL DEFAULT 0,
attempt INTEGER NOT NULL DEFAULT 0,
payload TEXT NOT NULL DEFAULT '{}',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)"""
)
conn.execute("CREATE INDEX IF NOT EXISTS idx_wd_webhook ON webhook_deliveries(webhook_id, created_at)")
conn.execute(
"""CREATE TABLE IF NOT EXISTS idempotency_keys (
key TEXT PRIMARY KEY,
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
response_json TEXT NOT NULL DEFAULT '{}',
status_code INTEGER NOT NULL DEFAULT 200,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)"""
)
conn.execute("CREATE INDEX IF NOT EXISTS idx_idemp_user ON idempotency_keys(user_id, created_at)")
@register(17, "v6.0.0: sync_version columns")
def _migration_sync_version_columns(conn: sqlite3.Connection) -> None:
"""v6.0.0 — optimistic-concurrency version counters for offline sync.