66 lines
3.6 KiB
TypeScript
66 lines
3.6 KiB
TypeScript
import "server-only";
|
|
|
|
import { and, eq, ne, or, sql } from "drizzle-orm";
|
|
import webPush from "web-push";
|
|
import { createNotifications } from "@/lib/notifications/repository";
|
|
import { getDb } from "@/db";
|
|
import { commissionPushSubscriptions, users } from "@/db/schema";
|
|
|
|
export function pushPublicKey() {
|
|
return process.env.WEB_PUSH_PUBLIC_KEY && process.env.WEB_PUSH_PRIVATE_KEY && process.env.WEB_PUSH_SUBJECT
|
|
? process.env.WEB_PUSH_PUBLIC_KEY : null;
|
|
}
|
|
|
|
export function validPushEndpoint(value: string) {
|
|
try {
|
|
const url = new URL(value);
|
|
const host = url.hostname.toLowerCase();
|
|
return url.protocol === "https:" && !url.username && !url.password &&
|
|
(host === "fcm.googleapis.com" || host === "updates.push.services.mozilla.com" ||
|
|
host === "web.push.apple.com" || host.endsWith(".push.apple.com") ||
|
|
host.endsWith(".notify.windows.com"));
|
|
} catch { return false; }
|
|
}
|
|
|
|
export async function sendCommissionMessagePush(message: {
|
|
id: string; ticketId: string; ticketTitle: string; customerId: string; authorId: string;
|
|
authorName: string; text: string | null;
|
|
}) {
|
|
const recipients = await getDb().select({ id: users.id, role: users.role, verified: users.emailVerified }).from(users)
|
|
.where(and(ne(users.id, message.authorId), sql`${users.banned} is not true`, or(eq(users.id, message.customerId), and(eq(users.role, "admin"), eq(users.emailVerified, true)))));
|
|
await createNotifications(recipients.map(user => {
|
|
const admin = user.role === "admin" && user.verified && user.id !== message.customerId;
|
|
return { userId: user.id, adminOnly: admin, url: `/${admin ? "admin/commission" : "commission/tickets"}/${message.ticketId}` };
|
|
}), { eventKey: `commission:${message.id}`, kind: "commission_message", title: `${message.authorName} - ${message.ticketTitle}`, body: message.text?.slice(0, 300) || "ส่งรูปภาพ", messageId: message.id });
|
|
if (!pushPublicKey()) return;
|
|
const subscriptions = await getDb().select({ subscription: commissionPushSubscriptions })
|
|
.from(commissionPushSubscriptions)
|
|
.innerJoin(users, eq(users.id, commissionPushSubscriptions.userId))
|
|
.where(and(ne(commissionPushSubscriptions.userId, message.authorId), eq(users.banned, false),
|
|
or(and(eq(commissionPushSubscriptions.scope, "customer"),
|
|
eq(commissionPushSubscriptions.userId, message.customerId)),
|
|
and(eq(commissionPushSubscriptions.scope, "admin"), eq(users.role, "admin"),
|
|
eq(users.emailVerified, true)))));
|
|
webPush.setVapidDetails(process.env.WEB_PUSH_SUBJECT!, process.env.WEB_PUSH_PUBLIC_KEY!,
|
|
process.env.WEB_PUSH_PRIVATE_KEY!);
|
|
for (let offset = 0; offset < subscriptions.length; offset += 10) {
|
|
await Promise.all(subscriptions.slice(offset, offset + 10).map(async ({ subscription }) => {
|
|
const payload = JSON.stringify({
|
|
id: message.id, title: `${message.authorName} - ${message.ticketTitle}`,
|
|
body: message.text?.slice(0, 300) || "ส่งรูปภาพ",
|
|
url: `/${subscription.scope === "admin" ? "admin/commission" : "commission/tickets"}/${message.ticketId}`,
|
|
});
|
|
try {
|
|
await webPush.sendNotification({ endpoint: subscription.endpoint,
|
|
keys: { p256dh: subscription.p256dh, auth: subscription.auth } }, payload,
|
|
{ TTL: 60 * 60 * 24, timeout: 5000 });
|
|
} catch (cause) {
|
|
if (cause && typeof cause === "object" && "statusCode" in cause &&
|
|
(cause.statusCode === 404 || cause.statusCode === 410))
|
|
await getDb().delete(commissionPushSubscriptions)
|
|
.where(eq(commissionPushSubscriptions.endpoint, subscription.endpoint));
|
|
}
|
|
}));
|
|
}
|
|
}
|