110 lines
3.2 KiB
TypeScript
110 lines
3.2 KiB
TypeScript
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<string> {
|
|
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<ReminderWithDetails | null> {
|
|
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<void> {
|
|
await db.delete(reminders).where(eq(reminders.id, id));
|
|
}
|