import { ok, err, type ReadonlyDB, UnauthenticatedError, type CallerContext, } from "@tailor-platform/erp-kit/core"; import type { DB } from "../generated/kysely-tailordb"; import { ForbiddenError } from "../lib/errors.generated"; export interface GetNotificationPreferenceInput { userId: string; categoryId: string; channelId: string; } /** * Returns the caller's NotificationPreference row for a (categoryId, channelId), or null (default opt-in). Self-service only. */ export async function run( db: ReadonlyDB, input: GetNotificationPreferenceInput, 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 { userId, categoryId, channelId } = input; if (actorId !== userId) { return err(new ForbiddenError(userId)); } const preference = await db .selectFrom("NotificationPreference") .selectAll() .where("userId", "=", userId) .where("categoryId", "=", categoryId) .where("channelId", "=", channelId) .executeTakeFirst(); return ok({ preference: preference ?? null }); }