37 lines
1.7 KiB
TypeScript
37 lines
1.7 KiB
TypeScript
import "server-only";
|
|
|
|
import { and, asc, eq, gt, isNull, lte, sql } from "drizzle-orm";
|
|
import { getDb } from "@/db";
|
|
import { guides, users } from "@/db/schema";
|
|
import type { OutboxEventLike } from "@/lib/events/invalidation";
|
|
import { createNotifications } from "@/lib/notifications/repository";
|
|
|
|
export async function sendGuidePublicationNotifications(event: OutboxEventLike) {
|
|
const publishedAt = typeof event.payload.publishedAt === "string"
|
|
? new Date(event.payload.publishedAt) : null;
|
|
if (event.eventType !== "guide.published" || event.topic !== "notifications:guide"
|
|
|| !publishedAt || Number.isNaN(publishedAt.getTime())) {
|
|
throw new Error("Invalid guide publication notification event");
|
|
}
|
|
const db = getDb();
|
|
const [guide] = await db.select({ name: guides.name, slug: guides.slug }).from(guides)
|
|
.where(and(eq(guides.id, event.aggregateId), eq(guides.isPublic, true), isNull(guides.trashedAt))).limit(1);
|
|
if (!guide) return;
|
|
|
|
let cursor: string | undefined;
|
|
while (true) {
|
|
const recipients: { id: string }[] = await db.select({ id: users.id }).from(users)
|
|
.where(and(sql`${users.banned} is not true`, lte(users.createdAt, publishedAt),
|
|
cursor === undefined ? undefined : gt(users.id, cursor)))
|
|
.orderBy(asc(users.id)).limit(100);
|
|
if (!recipients.length) return;
|
|
await createNotifications(recipients.map(user => ({ userId: user.id, url: `/${guide.slug}` })), {
|
|
eventKey: `guide:${event.aggregateId}:published`,
|
|
kind: "guide_published",
|
|
title: "เผยแพร่ Guide ใหม่",
|
|
body: `อ่าน Guide ${guide.name} ได้แล้ว`,
|
|
});
|
|
cursor = recipients[recipients.length - 1].id;
|
|
}
|
|
}
|