- Schema III: per-user notifications (broadcast fan-out migration, category, link, requiredPermissions filter), notification preferences and settings endpoints, calendar withdrawal switch, occupiedFrames - Suggestions API: list/summary with permission visibility and paging, accept/dismiss/reopen incl. bulk, SuggestionView on all responses - Varroa traffic light by season and method, young colonies, treatment effect window, sample-based percentage; shared withdrawal-window helper on Berlin calendar days; GET /varroa-measurements/thresholds - ColonySplit and QueenAssigned domain events with calendar and webhook handlers - Outbox claims compare against now() rounded to timestamp(3); fixes the flaky e2e claims of rows written with SQL now() Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
19 lines
725 B
TypeScript
19 lines
725 B
TypeScript
/**
|
|
* A notification link is a relative app path: it starts with "/", not with
|
|
* "//" or "/\", carries no scheme and is at most 300 characters long. Anything
|
|
* else is a programming error, never user input (the code that records the
|
|
* notification builds the link).
|
|
*/
|
|
const APP_LINK_PATTERN = /^\/(?![/\\])[A-Za-z0-9\-._~/?=&%]{0,299}$/;
|
|
|
|
export function assertAppLink(link: string): void {
|
|
if (!APP_LINK_PATTERN.test(link)) {
|
|
throw new Error(`Notification link is not a relative app path: ${link}`);
|
|
}
|
|
}
|
|
|
|
/** One key per recipient, so a fan-out repeated for the same event writes nothing new. */
|
|
export function notificationDedupeKey(prefix: string, userId: string): string {
|
|
return `${prefix}:${userId}`;
|
|
}
|