- Public API: /api/v1 (collections, pages, my-tasks) with token auth
- Token generation: POST /api/v1/token
- Outbound webhooks: /workspace/webhooks CRUD + fire_event dispatcher
- PWA: manifest.json endpoint
- CSRF exempt for /api/v1
- 73/73 tests passent (+6 tests v2.1)
- Version 2.0.0 → 2.1.0
- Docs: ROADMAP updated — 7/7 blocs, 52/52 features ✅
52 lines
1.7 KiB
Python
52 lines
1.7 KiB
Python
"""FlowDeck — Webhook outbound dispatcher (v2.1.0)."""
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
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()
|