feat(backend): watches report alert counts, alerts carry watch params and time away, webhooks their last delivery
This commit is contained in:
1 parent
9da0c06ac1
commit
6a4fd6ad47
4 files changed
+76
-37
No files matched your search
@@ -1,3 +1,4 @@
|
||||
import json
|
||||
import secrets
|
||||
from datetime import datetime
|
||||
|
||||
@@ -11,12 +12,21 @@ STATUS_DELIVERED = "delivered"
|
||||
STATUS_FAILED = "failed"
|
||||
DELIVERY_LOG = 20
|
||||
|
||||
_COLS = "id, account_id, url, secret, enabled, watch_ids, created_at, updated_at"
|
||||
_DELIVERY_COLS = (
|
||||
"id, webhook_id, alert_id, event, status, attempts, next_attempt_at, "
|
||||
"response_code, last_error, created_at, finished_at"
|
||||
)
|
||||
|
||||
_HOOKS = f"""
|
||||
SELECT w.id, w.account_id, w.url, w.secret, w.enabled, w.watch_ids,
|
||||
w.created_at, w.updated_at, to_jsonb(d) AS last_delivery
|
||||
FROM webhooks w
|
||||
LEFT JOIN LATERAL (
|
||||
SELECT {_DELIVERY_COLS} FROM webhook_deliveries
|
||||
WHERE webhook_id = w.id ORDER BY id DESC LIMIT 1
|
||||
) d ON true
|
||||
""" # noqa: S608
|
||||
|
||||
_CLAIM = """
|
||||
UPDATE webhook_deliveries d SET status = 'sending', attempts = d.attempts + 1
|
||||
FROM webhooks w
|
||||
@@ -43,20 +53,24 @@ def new_secret() -> str:
|
||||
return secrets.token_hex(32)
|
||||
|
||||
|
||||
def _to_webhook(row: asyncpg.Record) -> WebhookView:
|
||||
data = dict(row)
|
||||
last = data.pop("last_delivery")
|
||||
return WebhookView(
|
||||
**data, last_delivery=DeliveryView(**json.loads(last)) if last else None
|
||||
)
|
||||
|
||||
|
||||
async def list_webhooks(pool: asyncpg.Pool, account_id: int) -> list[WebhookView]:
|
||||
rows = await pool.fetch(
|
||||
f"SELECT {_COLS} FROM webhooks WHERE account_id = $1 ORDER BY id", # noqa: S608
|
||||
account_id,
|
||||
f"{_HOOKS} WHERE w.account_id = $1 ORDER BY w.id", account_id
|
||||
)
|
||||
return [WebhookView(**dict(row)) for row in rows]
|
||||
return [_to_webhook(row) for row in rows]
|
||||
|
||||
|
||||
async def get_webhook(pool: asyncpg.Pool, webhook_id: int) -> WebhookView | None:
|
||||
row = await pool.fetchrow(
|
||||
f"SELECT {_COLS} FROM webhooks WHERE id = $1", # noqa: S608
|
||||
webhook_id,
|
||||
)
|
||||
return WebhookView(**dict(row)) if row else None
|
||||
row = await pool.fetchrow(f"{_HOOKS} WHERE w.id = $1", webhook_id)
|
||||
return _to_webhook(row) if row else None
|
||||
|
||||
|
||||
async def create_webhook(
|
||||
@@ -66,17 +80,17 @@ async def create_webhook(
|
||||
watch_ids: list[int] | None,
|
||||
*,
|
||||
enabled: bool,
|
||||
) -> WebhookView:
|
||||
row = await pool.fetchrow(
|
||||
"INSERT INTO webhooks (account_id, url, secret, enabled, watch_ids) " # noqa: S608
|
||||
f"VALUES ($1, $2, $3, $4, $5) RETURNING {_COLS}",
|
||||
) -> WebhookView | None:
|
||||
webhook_id = await pool.fetchval(
|
||||
"INSERT INTO webhooks (account_id, url, secret, enabled, watch_ids) "
|
||||
"VALUES ($1, $2, $3, $4, $5) RETURNING id",
|
||||
account_id,
|
||||
url,
|
||||
new_secret(),
|
||||
enabled,
|
||||
watch_ids,
|
||||
)
|
||||
return WebhookView(**dict(row))
|
||||
return await get_webhook(pool, webhook_id)
|
||||
|
||||
|
||||
async def update_webhook(
|
||||
@@ -88,32 +102,31 @@ async def update_webhook(
|
||||
enabled: bool,
|
||||
) -> WebhookView | None:
|
||||
async with pool.acquire() as conn, conn.transaction():
|
||||
row = await conn.fetchrow(
|
||||
"UPDATE webhooks SET url = $2, enabled = $3, watch_ids = $4, " # noqa: S608
|
||||
f"updated_at = now() WHERE id = $1 RETURNING {_COLS}",
|
||||
found = await conn.fetchval(
|
||||
"UPDATE webhooks SET url = $2, enabled = $3, watch_ids = $4, "
|
||||
"updated_at = now() WHERE id = $1 RETURNING id",
|
||||
webhook_id,
|
||||
url,
|
||||
enabled,
|
||||
watch_ids,
|
||||
)
|
||||
if row is not None and not enabled:
|
||||
if found is not None and not enabled:
|
||||
await conn.execute(
|
||||
"UPDATE webhook_deliveries SET status = 'failed', "
|
||||
"last_error = 'endpoint disabled', finished_at = now() "
|
||||
"WHERE webhook_id = $1 AND status = 'pending'",
|
||||
webhook_id,
|
||||
)
|
||||
return WebhookView(**dict(row)) if row else None
|
||||
return await get_webhook(pool, webhook_id)
|
||||
|
||||
|
||||
async def rotate_secret(pool: asyncpg.Pool, webhook_id: int) -> WebhookView | None:
|
||||
row = await pool.fetchrow(
|
||||
"UPDATE webhooks SET secret = $2, updated_at = now() " # noqa: S608
|
||||
f"WHERE id = $1 RETURNING {_COLS}",
|
||||
await pool.execute(
|
||||
"UPDATE webhooks SET secret = $2, updated_at = now() WHERE id = $1",
|
||||
webhook_id,
|
||||
new_secret(),
|
||||
)
|
||||
return WebhookView(**dict(row)) if row else None
|
||||
return await get_webhook(pool, webhook_id)
|
||||
|
||||
|
||||
async def delete_webhook(pool: asyncpg.Pool, webhook_id: int) -> bool:
|
||||
|
||||
Reference in new issue
Block a user