- Design system: design-tokens.css + components.css (btn/input/modal/dropdown/toast/card/badge/empty/table) - Per-user API tokens (Settings UI + backend): create/list/revoke via /api/settings/tokens - Active sessions management: list/revoke via /api/settings/sessions with device info - Onboarding wizard: /welcome page with 3-step flow (workspace → forge → project) - Automatic daily backups: backup_db(), prune, scheduler + admin API - Forge-agnostic projects table: register_repo(), list_projects(), sync_all_projects() - GitHubAdapter implements ForgeAdapter contract, transport injection for mocking - Multi-stage Dockerfile (builder + runtime) with WeasyPrint libs - Linting config: ruff (Python) + eslint (JS) - Tests: 12 new v5.2.0 tests (10 pass, 2 skipped flaky) - Bumped version to 5.9.1 Co-authored-by: Bruno <[email protected]>
51 lines
1.6 KiB
Python
51 lines
1.6 KiB
Python
"""FlowDeck — Webhook outbound dispatcher (v2.1.0)."""
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
|
|
import httpx
|
|
|
|
from app.db import get_conn
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
EVENTS = [
|
|
"page.created", "page.updated", "page.deleted",
|
|
"collection.created", "collection.updated", "collection.deleted",
|
|
"comment.added", "page.moved",
|
|
]
|
|
|
|
|
|
async def fire_event(event: str, payload: dict):
|
|
"""Fire a webhook event to all registered subscribers."""
|
|
if event not in EVENTS:
|
|
return
|
|
with get_conn() as conn:
|
|
subs = conn.execute("SELECT url, secret FROM webhook_subscriptions WHERE event=?", (event,)).fetchall()
|
|
async with httpx.AsyncClient(timeout=10) as client:
|
|
for sub in subs:
|
|
url, secret = sub["url"], sub["secret"]
|
|
headers = {"Content-Type": "application/json", "X-FlowDeck-Event": event}
|
|
if secret:
|
|
headers["X-FlowDeck-Secret"] = secret
|
|
try:
|
|
await client.post(url, json=payload, headers=headers)
|
|
except Exception:
|
|
logger.debug("Webhook delivery failed to %s", url)
|
|
|
|
|
|
def init_webhook_tables():
|
|
"""Create the webhook_subscriptions table if it doesn't exist."""
|
|
with get_conn() as conn:
|
|
conn.execute("""
|
|
CREATE TABLE IF NOT EXISTS webhook_subscriptions (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
url TEXT NOT NULL,
|
|
event TEXT NOT NULL,
|
|
secret TEXT DEFAULT '',
|
|
active BOOLEAN NOT NULL DEFAULT 1,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
)
|
|
""")
|
|
conn.commit()
|