import { buildPaginatedResult, DEFAULT_PAGE_SIZE, ok, err, type PaginationInput, UnauthenticatedError, type CallerContext, type ReadonlyDB, } from "@tailor-platform/erp-kit/core"; import type { DB } from "../generated/kysely-tailordb"; export interface ListInboxNotificationsInput extends PaginationInput { includeArchived?: boolean; } /** * Returns the caller's paginated IN_APP inbox feed (SENT/DELIVERED, archived excluded by default) plus the unread count over the caller's deliverable, unread, non-archived set. */ export async function run( db: ReadonlyDB, input: ListInboxNotificationsInput, ctx: CallerContext, ) { // Every result here is scoped to the caller, so there is nothing to return when // there is no caller. Queries carry no permission gate of their own. const { actorId } = ctx; if (actorId === null) return err(new UnauthenticatedError()); const limit = input.limit ?? DEFAULT_PAGE_SIZE; const offset = input.offset ?? 0; const includeArchived = input.includeArchived === true; // The inbox is the IN_APP surface only; rows dispatched on other channels // (e.g. EMAIL) are delivered by their adapter and must not appear in the feed. let feedQuery = db .selectFrom("Notification") .innerJoin("NotificationChannel", "NotificationChannel.id", "Notification.channelId") .where("NotificationChannel.channelId", "=", "IN_APP") .selectAll("Notification") .where("recipientUserId", "=", actorId) .where("deliveryStatus", "in", ["SENT", "DELIVERED"]); if (!includeArchived) { feedQuery = feedQuery.where("archivedAt", "is", null); } const page = await feedQuery .orderBy("createdAt", "desc") .limit(limit + 1) .offset(offset) .execute(); const unread = await db .selectFrom("Notification") .innerJoin("NotificationChannel", "NotificationChannel.id", "Notification.channelId") .where("NotificationChannel.channelId", "=", "IN_APP") .where("recipientUserId", "=", actorId) .where("deliveryStatus", "in", ["SENT", "DELIVERED"]) .where("readAt", "is", null) .where("archivedAt", "is", null) .select((eb) => eb.fn.countAll().as("count")) .executeTakeFirst(); const unreadCount = Number(unread?.count ?? 0); return ok({ ...buildPaginatedResult(page, limit), unreadCount }); }