2026-08-18 16:21:22 +00:00
|
|
|
import { instrument } from 'succinct-async'
|
|
|
|
|
import * as mailer from './mailer.js'
|
|
|
|
|
import * as worker from './worker/index.js'
|
|
|
|
|
import * as db from './database.js'
|
|
|
|
|
|
|
|
|
|
export class ActionError extends Error {
|
|
|
|
|
toString() {
|
|
|
|
|
return this.message
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export type RegisterSubscriptionParams = {
|
|
|
|
|
pubkey: string
|
|
|
|
|
email: string
|
|
|
|
|
frequency: string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export const registerSubscription = instrument(
|
|
|
|
|
'actions.registerSubscription',
|
|
|
|
|
async ({ pubkey, email, frequency }: RegisterSubscriptionParams) => {
|
|
|
|
|
const sub = await db.insertSubscription(pubkey, email, frequency)
|
|
|
|
|
const callback = `${process.env.BASE_URL}/notify/${sub.id}`
|
|
|
|
|
|
2026-09-03 15:47:21 +00:00
|
|
|
if (!sub.confirmed_at) {
|
2026-09-10 15:28:53 +00:00
|
|
|
// New or email-changed subscription — send a confirmation email.
|
2026-09-03 15:47:21 +00:00
|
|
|
await mailer.sendConfirm(sub)
|
2026-09-10 15:28:53 +00:00
|
|
|
} else {
|
|
|
|
|
// Already confirmed (e.g. frequency-only change) — reschedule the
|
|
|
|
|
// cron job so it uses the new cadence immediately.
|
|
|
|
|
worker.registerSubscription(sub)
|
2026-09-03 15:47:21 +00:00
|
|
|
}
|
2026-08-18 16:21:22 +00:00
|
|
|
|
|
|
|
|
return { key: sub.key, callback }
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
export type ConfirmSubscriptionParams = {
|
|
|
|
|
token: string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export const confirmSubscriptionAction = instrument(
|
|
|
|
|
'actions.confirmSubscription',
|
|
|
|
|
async ({ token }: ConfirmSubscriptionParams) => {
|
2026-09-17 20:57:30 +00:00
|
|
|
const result = await db.confirmSubscription(token)
|
2026-08-18 16:21:22 +00:00
|
|
|
|
2026-09-17 20:57:30 +00:00
|
|
|
if (!result) {
|
2026-08-18 16:21:22 +00:00
|
|
|
throw new ActionError('That confirmation code is invalid or has expired.')
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-17 20:57:30 +00:00
|
|
|
// Only register the cron job when this is a fresh confirmation.
|
|
|
|
|
// If already confirmed, the job is already running.
|
|
|
|
|
if (!result.alreadyConfirmed) {
|
|
|
|
|
worker.registerSubscription(result.sub)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result
|
2026-08-18 16:21:22 +00:00
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
export type UnsubscribeParams = {
|
|
|
|
|
token: string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export const unsubscribeAction = instrument(
|
|
|
|
|
'actions.unsubscribe',
|
|
|
|
|
async ({ token }: UnsubscribeParams) => {
|
|
|
|
|
const sub = await db.unsubscribeSubscription(token)
|
|
|
|
|
|
|
|
|
|
if (sub) {
|
|
|
|
|
worker.unregisterSubscription(sub)
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
)
|