import type { ObjectId } from 'mongodb'; /** A `notifications` document, as written by node-red (see docs/legacy-behavior.md). */ export interface NotificationDoc { _id: ObjectId; userId: string; subsId?: ObjectId; content: string; geo: { type: 'Point'; coordinates: [number, number] }; // [lng, lat] type: 'mobile' | 'web'; notified?: boolean; notifiedAt?: Date; emailNotified?: boolean; emailNotifiedAt?: Date; when: Date; sealed: string; createdAt?: Date; updatedAt?: Date; // provenance set by our poller (invisible to the old system) claimedBy?: string; claimedAt?: Date; } export interface UserDoc { _id: string; lang?: string; fireBaseToken?: string | null; fireBaseTokenDeadAt?: Date; profile?: { name?: { first?: string } }; emails?: Array<{ address: string; verified?: boolean }>; services?: { password?: { bcrypt?: string } }; } export type Channel = 'fcm' | 'email'; /** Maps a notification `type` to a channel. */ export function channelOfType(type: NotificationDoc['type']): Channel { return type === 'mobile' ? 'fcm' : 'email'; } /** Record in `notification_sends` — persistent idempotency (unique on notificationId+channel). */ export interface SendRecord { notificationId: ObjectId; channel: Channel; status: 'sending' | 'sent' | 'skipped' | 'failed' | 'dry-run' | 'dead-token' | 'no-recipient'; mode: OperatingMode; detail?: string; createdAt: Date; } export type OperatingMode = 'dry-run' | 'shadow' | 'canary' | 'full'; /** A queued send job (payload persisted in Redis by BullMQ). */ export interface SendJob { notificationId: string; // ObjectId hex channel: Channel; }