All checks were successful
CI / Lint, typecheck, test (push) Successful in 3m26s
CI / Build container images (push) Has been skipped
CD / Build and push images (push) Successful in 3m49s
CD / Deploy to Test (push) Successful in 9s
CD / Smoke tests against Test (push) Successful in 1m12s
CD / Promote to Int (push) Successful in 10s
CI / Auth e2e pack (push) Successful in 5m27s
CI / Import/export fidelity gate (push) Successful in 46s
New notifications table (payload denormalized for join-free rendering; mailed_at already prepares the #95 digests). Generation fans page events out to page and pond watchers, excluding the actors, and re-checks page read permission per watcher at delivery time — a revoked watcher gets nothing. Sources: named version snapshots (api), new comments (api), and the collab server's automatic session-close snapshots — announced over a new pg NOTIFY channel (the reverse of the established api→collab bus) consumed by a dedicated LISTEN client in the api, since the collab server has no permission resolution of its own. API: paginated list (unread first via nulls-first ordering), mark read, mark all read. UI: bell with unread badge in the top bar (30 s polling, no push in v1) and a dropdown whose entries navigate and mark themselves read; comment notifications deep-link with ?comments=1, which now opens the comments panel on load. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EwZ4jR4KFAPvpjWevfUGX1
81 lines
3.2 KiB
TypeScript
81 lines
3.2 KiB
TypeScript
import { z } from 'zod';
|
|
|
|
/**
|
|
* Types and schemas for the short-lived collaboration tokens (issue #34,
|
|
* ADR 0003/0007). These are browser-safe (no Node built-ins) so the web app
|
|
* can import them from the package barrel. The signing/verifying helpers live
|
|
* in `./token-crypto` (Node `crypto`) and are imported only by the api and the
|
|
* collab server.
|
|
*/
|
|
|
|
export const collabTokenModeSchema = z.enum(['rw', 'ro']);
|
|
export type CollabTokenMode = z.infer<typeof collabTokenModeSchema>;
|
|
|
|
/**
|
|
* PostgreSQL `LISTEN/NOTIFY` channel over which the api announces that the
|
|
* access situation of a pond changed (issue #39, permissions.md §Performance).
|
|
* The notification payload is the pond id. The api emits it whenever a
|
|
* permission-relevant change happens (interim: pond soft-delete; from M5 on:
|
|
* grant changes, #53); the collab server listens and re-validates every open
|
|
* connection to that pond's pages by closing them so clients reconnect and
|
|
* re-acquire a token reflecting the current access.
|
|
*/
|
|
export const POND_ACCESS_CHANGED_CHANNEL = 'pond_access_changed';
|
|
|
|
/**
|
|
* PostgreSQL `NOTIFY` channel over which the api asks the collab server to
|
|
* restore a page to an earlier version (issue #42, ADR 0013). The api does the
|
|
* permission check, then emits this; the collab server owns the live document,
|
|
* so it snapshots the current state (a `PRE_RESTORE` version) and applies the
|
|
* restored content as a normal edit through the document, converging every open
|
|
* client. Payload is a JSON {@link PageRestoreRequest}.
|
|
*/
|
|
export const PAGE_RESTORE_CHANNEL = 'page_restore';
|
|
|
|
/**
|
|
* PostgreSQL `NOTIFY` channel over which the collab server announces that it
|
|
* wrote an automatic version snapshot (issue #94): the api listens and fans
|
|
* the change out to watchers as notifications — permission-checked there,
|
|
* where the resolution lives. Payload is a JSON {@link PageVersionCreatedEvent}.
|
|
*/
|
|
export const PAGE_VERSION_CREATED_CHANNEL = 'page_version_created';
|
|
|
|
/** JSON payload carried on {@link PAGE_VERSION_CREATED_CHANNEL}. */
|
|
export interface PageVersionCreatedEvent {
|
|
pageId: string;
|
|
/** Everyone who contributed to the snapshot — all excluded from fan-out. */
|
|
contributorIds: string[];
|
|
}
|
|
|
|
/** JSON payload carried on {@link PAGE_RESTORE_CHANNEL}. */
|
|
export interface PageRestoreRequest {
|
|
pageId: string;
|
|
versionId: string;
|
|
/** The user who triggered the restore (recorded on the pre-restore snapshot). */
|
|
userId: string;
|
|
}
|
|
|
|
/** The application claims carried by a collaboration token. */
|
|
export const collabTokenClaimsSchema = z.object({
|
|
// `null` for an anonymous visitor holding a public read-only token (issue
|
|
// #53); a user id for a signed-in participant (used for presence/versioning).
|
|
userId: z.string().min(1).nullable(),
|
|
pageId: z.string().min(1),
|
|
mode: collabTokenModeSchema,
|
|
});
|
|
export type CollabTokenClaims = z.infer<typeof collabTokenClaimsSchema>;
|
|
|
|
/** Response of `GET /pages/:id/collab-token`. */
|
|
export interface CollabTokenResponse {
|
|
token: string;
|
|
mode: CollabTokenMode;
|
|
expiresInSeconds: number;
|
|
}
|
|
|
|
export type CollabTokenVerification =
|
|
| { valid: true; claims: CollabTokenClaims }
|
|
| {
|
|
valid: false;
|
|
reason: 'malformed' | 'bad_algorithm' | 'bad_signature' | 'expired' | 'invalid_claims';
|
|
};
|