chore(repo): initialize planarchy workspace
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
import { z } from "zod";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { createTRPCRouter, managerProcedure, protectedProcedure } from "../trpc.js";
|
||||
import { emitNotificationCreated } from "../sse/event-bus.js";
|
||||
|
||||
/** Resolve the DB user id from the session email. Throws UNAUTHORIZED if not found. */
|
||||
async function resolveUserId(ctx: {
|
||||
db: { user: { findUnique: (args: { where: { email: string }; select: { id: true } }) => Promise<{ id: string } | null> } };
|
||||
session: { user?: { email?: string | null } | null };
|
||||
}): Promise<string> {
|
||||
const email = ctx.session.user?.email;
|
||||
if (!email) throw new TRPCError({ code: "UNAUTHORIZED" });
|
||||
const user = await ctx.db.user.findUnique({ where: { email }, select: { id: true } });
|
||||
if (!user) throw new TRPCError({ code: "UNAUTHORIZED" });
|
||||
return user.id;
|
||||
}
|
||||
|
||||
export const notificationRouter = createTRPCRouter({
|
||||
/** List notifications for the current user */
|
||||
list: protectedProcedure
|
||||
.input(
|
||||
z.object({
|
||||
unreadOnly: z.boolean().optional(),
|
||||
limit: z.number().min(1).max(100).default(50),
|
||||
}),
|
||||
)
|
||||
.query(async ({ ctx, input }) => {
|
||||
const userId = await resolveUserId(ctx);
|
||||
return ctx.db.notification.findMany({
|
||||
where: {
|
||||
userId,
|
||||
...(input.unreadOnly ? { readAt: null } : {}),
|
||||
},
|
||||
orderBy: { createdAt: "desc" },
|
||||
take: input.limit,
|
||||
});
|
||||
}),
|
||||
|
||||
/** Count unread notifications */
|
||||
unreadCount: protectedProcedure.query(async ({ ctx }) => {
|
||||
const userId = await resolveUserId(ctx);
|
||||
return ctx.db.notification.count({
|
||||
where: { userId, readAt: null },
|
||||
});
|
||||
}),
|
||||
|
||||
/** Mark one or all as read */
|
||||
markRead: protectedProcedure
|
||||
.input(z.object({ id: z.string().optional() }))
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const userId = await resolveUserId(ctx);
|
||||
const now = new Date();
|
||||
if (input.id) {
|
||||
await ctx.db.notification.update({
|
||||
where: { id: input.id, userId },
|
||||
data: { readAt: now },
|
||||
});
|
||||
} else {
|
||||
await ctx.db.notification.updateMany({
|
||||
where: { userId, readAt: null },
|
||||
data: { readAt: now },
|
||||
});
|
||||
}
|
||||
}),
|
||||
|
||||
/** Create a notification — restricted to managers and admins */
|
||||
create: managerProcedure
|
||||
.input(
|
||||
z.object({
|
||||
userId: z.string(),
|
||||
type: z.string(),
|
||||
title: z.string(),
|
||||
body: z.string().optional(),
|
||||
entityId: z.string().optional(),
|
||||
entityType: z.string().optional(),
|
||||
}),
|
||||
)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const n = await ctx.db.notification.create({
|
||||
data: {
|
||||
userId: input.userId,
|
||||
type: input.type,
|
||||
title: input.title,
|
||||
...(input.body !== undefined ? { body: input.body } : {}),
|
||||
...(input.entityId !== undefined ? { entityId: input.entityId } : {}),
|
||||
...(input.entityType !== undefined ? { entityType: input.entityType } : {}),
|
||||
},
|
||||
});
|
||||
emitNotificationCreated(input.userId, n.id);
|
||||
return n;
|
||||
}),
|
||||
});
|
||||
Reference in New Issue
Block a user