import { z } from 'zod' import { router, memberProcedure, adminProcedure } from '../trpc' import { sendPushNotifications } from '@/lib/notifications' const NewsInput = z.object({ title: z.string().min(3), body: z.string().min(10), kategorie: z.enum(['Wichtig', 'Pruefung', 'Foerderung', 'Veranstaltung', 'Allgemein']), publishedAt: z.string().datetime().optional().nullable(), }) export const newsRouter = router({ /** * List published news for org members */ list: memberProcedure .input( z.object({ kategorie: z .enum(['Wichtig', 'Pruefung', 'Foerderung', 'Veranstaltung', 'Allgemein']) .optional(), includeUnpublished: z.boolean().default(false), }) ) .query(async ({ ctx, input }) => { const news = await ctx.prisma.news.findMany({ where: { orgId: ctx.orgId, ...(input.kategorie && { kategorie: input.kategorie }), ...(!input.includeUnpublished && { publishedAt: { not: null } }), ...(input.includeUnpublished && ctx.role !== 'admin' && { publishedAt: { not: null } }), }, include: { author: { select: { name: true } }, attachments: true, reads: { where: { userId: ctx.session.user.id }, select: { id: true }, }, }, orderBy: [{ publishedAt: 'desc' }, { createdAt: 'desc' }], }) return news.map((n) => ({ ...n, isRead: n.reads.length > 0, reads: undefined, })) }), /** * Get single news article */ byId: memberProcedure .input(z.object({ id: z.string() })) .query(async ({ ctx, input }) => { const news = await ctx.prisma.news.findFirstOrThrow({ where: { id: input.id, orgId: ctx.orgId, ...(ctx.role !== 'admin' && { publishedAt: { not: null } }), }, include: { author: { select: { name: true, betrieb: true } }, attachments: true, }, }) return news }), /** * Mark news as read */ markRead: memberProcedure .input(z.object({ newsId: z.string() })) .mutation(async ({ ctx, input }) => { await ctx.prisma.newsRead.upsert({ where: { newsId_userId: { newsId: input.newsId, userId: ctx.session.user.id }, }, update: {}, create: { newsId: input.newsId, userId: ctx.session.user.id, }, }) return { success: true } }), /** * Create news article (admin only) */ create: adminProcedure.input(NewsInput).mutation(async ({ ctx, input }) => { const member = await ctx.prisma.member.findFirst({ where: { userId: ctx.session.user.id, orgId: ctx.orgId }, }) const news = await ctx.prisma.news.create({ data: { orgId: ctx.orgId, authorId: member?.id, title: input.title, body: input.body, kategorie: input.kategorie, publishedAt: input.publishedAt ? new Date(input.publishedAt) : null, }, }) // Trigger push notifications if publishing now if (news.publishedAt) { sendPushNotifications(ctx.orgId, news.title).catch(console.error) } return news }), /** * Update news article (admin only) */ update: adminProcedure .input(z.object({ id: z.string(), data: NewsInput.partial() })) .mutation(async ({ ctx, input }) => { const wasUnpublished = await ctx.prisma.news.findFirst({ where: { id: input.id, orgId: ctx.orgId, publishedAt: null }, }) const news = await ctx.prisma.news.updateMany({ where: { id: input.id, orgId: ctx.orgId }, data: { ...input.data, publishedAt: input.data.publishedAt ? new Date(input.data.publishedAt) : undefined, }, }) // Trigger push if just published if (wasUnpublished && input.data.publishedAt && input.data.title) { sendPushNotifications(ctx.orgId, input.data.title).catch(console.error) } return news }), /** * Delete news article (admin only) */ delete: adminProcedure .input(z.object({ id: z.string() })) .mutation(async ({ ctx, input }) => { await ctx.prisma.news.deleteMany({ where: { id: input.id, orgId: ctx.orgId }, }) return { success: true } }), /** * Get read stats for admin */ readStats: adminProcedure .input(z.object({ newsId: z.string() })) .query(async ({ ctx, input }) => { const [totalMembers, readers] = await Promise.all([ ctx.prisma.member.count({ where: { orgId: ctx.orgId, status: 'aktiv' } }), ctx.prisma.newsRead.count({ where: { newsId: input.newsId } }), ]) return { totalMembers, readers, readRate: totalMembers ? readers / totalMembers : 0 } }), })