"""Storage for web-push subscriptions (postgres, asyncpg)."""
from __future__ import annotations
import uuid
from dataclasses import dataclass
@dataclass
class PushSubscription:
id: str
user_id: str
endpoint: str
p256dh: str
auth: str
user_agent: str | None = None
_UPSERT = """
INSERT INTO push_subscriptions (id, user_id, endpoint, p256dh, auth, user_agent)
VALUES ($1, $2, $3, $4, $5, $6)
ON CONFLICT (endpoint) DO UPDATE
SET user_id = EXCLUDED.user_id,
p256dh = EXCLUDED.p256dh,
auth = EXCLUDED.auth,
user_agent = EXCLUDED.user_agent
RETURNING id
"""
_SELECT_FOR_USER = """
SELECT id, user_id, endpoint, p256dh, auth, user_agent
FROM push_subscriptions WHERE user_id = $1
"""
_DELETE = "DELETE FROM push_subscriptions WHERE endpoint = $1"
_MARK_PUSHED = "UPDATE push_subscriptions SET last_push_at = now() WHERE id = $1"
class PushSubscriptionStore:
def __init__(self, pool):
self._pool = pool
async def upsert(
self,
user_id: str,
endpoint: str,
p256dh: str,
auth: str,
user_agent: str | None = None,
) -> PushSubscription:
sub_id = await self._pool.fetchval(
_UPSERT, str(uuid.uuid4()), user_id, endpoint, p256dh, auth, user_agent
)
return PushSubscription(
id=sub_id, user_id=user_id, endpoint=endpoint, p256dh=p256dh,
auth=auth, user_agent=user_agent,
)
async def delete(self, endpoint: str) -> None:
await self._pool.execute(_DELETE, endpoint)
async def list_for_user(self, user_id: str) -> list[PushSubscription]:
rows = await self._pool.fetch(_SELECT_FOR_USER, user_id)
return [
PushSubscription(
id=r["id"], user_id=r["user_id"], endpoint=r["endpoint"],
p256dh=r["p256dh"], auth=r["auth"], user_agent=r["user_agent"],
)
for r in rows
]
async def mark_pushed(self, subscription_id: str) -> None:
await self._pool.execute(_MARK_PUSHED, subscription_id)