import { eq, sql } from "drizzle-orm"; import { reminders, reminderMessages, reminderTargets, type Reminder, } from "@cmbot/db"; import { db } from "../db.js"; import { DEFAULT_TIMEZONE } from "@cmbot/shared"; export type CreateReminderInput = { accountId: string; groupId: string; name: string; scheduledAt: Date; text?: string | null; mediaId?: string | null; caption?: string | null; createdBy: string; timezone?: string; }; export type ReminderWithDetails = Reminder & { targets: { groupId: string }[]; messages: { id: string; position: number; kind: string; textContent: string | null; mediaId: string | null }[]; }; export async function createReminder(input: CreateReminderInput): Promise { return await db.transaction(async (tx) => { const [rem] = await tx .insert(reminders) .values({ accountId: input.accountId, name: input.name, scheduleKind: "one_off", scheduledAt: input.scheduledAt, timezone: input.timezone ?? DEFAULT_TIMEZONE, status: "active", createdBy: input.createdBy, }) .returning({ id: reminders.id }); await tx.insert(reminderTargets).values({ reminderId: rem!.id, groupId: input.groupId, position: 0, }); let position = 0; if (input.text && !input.mediaId) { await tx.insert(reminderMessages).values({ reminderId: rem!.id, position: position++, kind: "text", textContent: input.text, mediaId: null, }); } else if (input.mediaId) { await tx.insert(reminderMessages).values({ reminderId: rem!.id, position: position++, kind: "media", textContent: input.caption ?? input.text ?? null, mediaId: input.mediaId, }); } return rem!.id; }); } export async function getReminderWithDetails(id: string): Promise { const rem = await db.query.reminders.findFirst({ where: (r, { eq }) => eq(r.id, id), }); if (!rem) return null; const targets = await db.query.reminderTargets.findMany({ where: (t, { eq }) => eq(t.reminderId, id), }); const messages = await db.query.reminderMessages.findMany({ where: (m, { eq }) => eq(m.reminderId, id), orderBy: (m, { asc }) => [asc(m.position)], }); return { ...rem, targets, messages }; } export async function listRemindersForOperator( operatorId: string, limit = 50, ): Promise<(Reminder & { accountLabel: string; groupCount: number })[]> { // Use parameterized SQL via drizzle's sql tag for safety. operatorId is a // server-controlled UUID, but parameterizing is the right habit anyway. const rows = await db.execute(sql` SELECT r.*, wa.label as account_label, (SELECT count(*) FROM reminder_targets rt WHERE rt.reminder_id = r.id) as group_count FROM reminders r JOIN whatsapp_accounts wa ON wa.id = r.account_id WHERE wa.operator_id = ${operatorId} ORDER BY r.scheduled_at DESC NULLS LAST, r.created_at DESC LIMIT ${limit} `); return rows.rows as never; } export async function deleteReminder(id: string): Promise { await db.delete(reminders).where(eq(reminders.id, id)); }