Files
buzz-sheet/lib/guides/notifications.ts
T
gunshiz 72e9f525cc
CI / Verify (push) Successful in 1m42s
CI / Build immutable images and deploy (push) Successful in 3m3s
feat(notifications) : notify guide publications and admin hearts; simplify Stygian navigation
2026-10-08 04:04:02 +07:00

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;
}
}