chore(repo): initialize planarchy workspace

This commit is contained in:
2026-03-14 14:31:09 +01:00
commit dd55d0e78b
769 changed files with 166461 additions and 0 deletions
+92
View File
@@ -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;
}),
});