"""Shared opaque keyset-pagination cursor. A cursor is base64(``|``). The sort key is whatever DESC-ordered timestamp a feed paginates on (gallery: effective_date; posts: COALESCE(post_date, downloaded_at); artists: created_at). `decode_cursor` rejects a malformed cursor with ValueError, which the API layer maps to HTTP 400. This was hand-rolled identically in gallery_service and post_feed_service (with artist_service importing gallery's copy). Two divergent copies of a cursor format silently break pagination in whichever feed drifts, so it lives once here now (DRY pattern sweep 2026-06-10). """ import base64 from datetime import datetime CURSOR_SEPARATOR = "|" def encode_cursor(sort_key: datetime, row_id: int) -> str: raw = f"{sort_key.isoformat()}{CURSOR_SEPARATOR}{row_id}" return base64.urlsafe_b64encode(raw.encode()).decode() def decode_cursor(cursor: str) -> tuple[datetime, int]: try: raw = base64.urlsafe_b64decode(cursor.encode()).decode() ts_part, id_part = raw.split(CURSOR_SEPARATOR, 1) return datetime.fromisoformat(ts_part), int(id_part) except Exception as exc: raise ValueError(f"invalid cursor: {cursor!r}") from exc