export type Subscription = { id: string key: string pubkey: string email: string frequency: string hour: number minute: number day_of_week?: number timezone: string created_at: number confirmed_at?: number unsubscribed_at?: number last_digest_at?: number last_confirm_sent_at?: number } export const getSubscriptionError = (sub: Subscription) => { if (!sub.email?.includes('@')) { return 'Please provide a valid email address' } if (!['daily', 'weekly'].includes(sub.frequency)) { return 'Frequency must be "daily" or "weekly"' } if (sub.hour < 0 || sub.hour > 23 || !Number.isInteger(sub.hour)) { return 'Hour must be an integer between 0 and 23' } if (sub.minute < 0 || sub.minute > 59 || !Number.isInteger(sub.minute)) { return 'Minute must be an integer between 0 and 59' } if (sub.frequency === 'weekly') { if (sub.day_of_week === undefined || sub.day_of_week === null) { return 'dayOfWeek is required for weekly frequency' } const dow = sub.day_of_week if (dow < 0 || dow > 7 || !Number.isInteger(dow)) { return 'dayOfWeek must be an integer between 0 and 7 (1=Mon, 7=Sun, 0=Sun)' } } if (!sub.timezone || typeof sub.timezone !== 'string') { return 'A valid IANA timezone is required' } try { Intl.DateTimeFormat(undefined, { timeZone: sub.timezone }) } catch { return `"${sub.timezone}" is not a valid IANA timezone` } } export const getCronExpression = (frequency: string, hour = 17, minute = 0, dayOfWeek?: number) => { if (frequency === 'daily') { return `0 ${minute} ${hour} * * *` } // Weekly: default to Monday (1) when not specified return `0 ${minute} ${hour} * * ${dayOfWeek ?? 1}` }