import { prisma } from '@/services/database'; import { resolveCollaborator } from '@/services/auth'; import type { ShareRole, WeeklyCheckInCategory, Emotion } from '@prisma/client'; // ============================================ // Weekly Check-in Session CRUD // ============================================ export async function getWeeklyCheckInSessionsByUserId(userId: string) { // Get owned sessions + shared sessions const [owned, shared] = await Promise.all([ prisma.weeklyCheckInSession.findMany({ where: { userId }, include: { user: { select: { id: true, name: true, email: true } }, shares: { include: { user: { select: { id: true, name: true, email: true } }, }, }, _count: { select: { items: true, }, }, }, orderBy: { updatedAt: 'desc' }, }), prisma.wCISessionShare.findMany({ where: { userId }, include: { session: { include: { user: { select: { id: true, name: true, email: true } }, shares: { include: { user: { select: { id: true, name: true, email: true } }, }, }, _count: { select: { items: true, }, }, }, }, }, }), ]); // Mark owned sessions and merge with shared const ownedWithRole = owned.map((s) => ({ ...s, isOwner: true as const, role: 'OWNER' as const, })); const sharedWithRole = shared.map((s) => ({ ...s.session, isOwner: false as const, role: s.role, sharedAt: s.createdAt, })); const allSessions = [...ownedWithRole, ...sharedWithRole].sort( (a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime() ); // Resolve participants to users const sessionsWithResolved = await Promise.all( allSessions.map(async (s) => ({ ...s, resolvedParticipant: await resolveCollaborator(s.participant), })) ); return sessionsWithResolved; } export async function getWeeklyCheckInSessionById(sessionId: string, userId: string) { // Check if user owns the session OR has it shared const session = await prisma.weeklyCheckInSession.findFirst({ where: { id: sessionId, OR: [ { userId }, // Owner { shares: { some: { userId } } }, // Shared with user ], }, include: { user: { select: { id: true, name: true, email: true } }, items: { orderBy: [{ category: 'asc' }, { order: 'asc' }], }, shares: { include: { user: { select: { id: true, name: true, email: true } }, }, }, }, }); if (!session) return null; // Determine user's role const isOwner = session.userId === userId; const share = session.shares.find((s) => s.userId === userId); const role = isOwner ? ('OWNER' as const) : share?.role || ('VIEWER' as const); const canEdit = isOwner || role === 'EDITOR'; // Resolve participant to user if it's an email const resolvedParticipant = await resolveCollaborator(session.participant); return { ...session, isOwner, role, canEdit, resolvedParticipant }; } // Check if user can access session (owner or shared) export async function canAccessWeeklyCheckInSession(sessionId: string, userId: string) { const count = await prisma.weeklyCheckInSession.count({ where: { id: sessionId, OR: [{ userId }, { shares: { some: { userId } } }], }, }); return count > 0; } // Check if user can edit session (owner or EDITOR role) export async function canEditWeeklyCheckInSession(sessionId: string, userId: string) { const count = await prisma.weeklyCheckInSession.count({ where: { id: sessionId, OR: [{ userId }, { shares: { some: { userId, role: 'EDITOR' } } }], }, }); return count > 0; } export async function createWeeklyCheckInSession( userId: string, data: { title: string; participant: string; date?: Date } ) { return prisma.weeklyCheckInSession.create({ data: { ...data, date: data.date || new Date(), userId, }, include: { items: { orderBy: [{ category: 'asc' }, { order: 'asc' }], }, }, }); } export async function updateWeeklyCheckInSession( sessionId: string, userId: string, data: { title?: string; participant?: string; date?: Date } ) { return prisma.weeklyCheckInSession.updateMany({ where: { id: sessionId, userId }, data, }); } export async function deleteWeeklyCheckInSession(sessionId: string, userId: string) { return prisma.weeklyCheckInSession.deleteMany({ where: { id: sessionId, userId }, }); } // ============================================ // Weekly Check-in Items CRUD // ============================================ export async function createWeeklyCheckInItem( sessionId: string, data: { content: string; category: WeeklyCheckInCategory; emotion?: Emotion } ) { // Get max order for this category in this session const maxOrder = await prisma.weeklyCheckInItem.findFirst({ where: { sessionId, category: data.category }, orderBy: { order: 'desc' }, select: { order: true }, }); return prisma.weeklyCheckInItem.create({ data: { ...data, emotion: data.emotion || 'NONE', sessionId, order: (maxOrder?.order ?? -1) + 1, }, }); } export async function updateWeeklyCheckInItem( itemId: string, data: { content?: string; category?: WeeklyCheckInCategory; emotion?: Emotion; order?: number } ) { return prisma.weeklyCheckInItem.update({ where: { id: itemId }, data, }); } export async function deleteWeeklyCheckInItem(itemId: string) { return prisma.weeklyCheckInItem.delete({ where: { id: itemId }, }); } export async function moveWeeklyCheckInItem( itemId: string, newCategory: WeeklyCheckInCategory, newOrder: number ) { return prisma.weeklyCheckInItem.update({ where: { id: itemId }, data: { category: newCategory, order: newOrder, }, }); } export async function reorderWeeklyCheckInItems( sessionId: string, category: WeeklyCheckInCategory, itemIds: string[] ) { const updates = itemIds.map((id, index) => prisma.weeklyCheckInItem.update({ where: { id }, data: { order: index }, }) ); return prisma.$transaction(updates); } // ============================================ // Session Sharing // ============================================ export async function shareWeeklyCheckInSession( sessionId: string, ownerId: string, targetEmail: string, role: ShareRole = 'EDITOR' ) { // Verify owner const session = await prisma.weeklyCheckInSession.findFirst({ where: { id: sessionId, userId: ownerId }, }); if (!session) { throw new Error('Session not found or not owned'); } // Find target user const targetUser = await prisma.user.findUnique({ where: { email: targetEmail }, }); if (!targetUser) { throw new Error('User not found'); } // Can't share with yourself if (targetUser.id === ownerId) { throw new Error('Cannot share session with yourself'); } // Create or update share return prisma.wCISessionShare.upsert({ where: { sessionId_userId: { sessionId, userId: targetUser.id }, }, update: { role }, create: { sessionId, userId: targetUser.id, role, }, include: { user: { select: { id: true, name: true, email: true } }, }, }); } export async function removeWeeklyCheckInShare( sessionId: string, ownerId: string, shareUserId: string ) { // Verify owner const session = await prisma.weeklyCheckInSession.findFirst({ where: { id: sessionId, userId: ownerId }, }); if (!session) { throw new Error('Session not found or not owned'); } return prisma.wCISessionShare.deleteMany({ where: { sessionId, userId: shareUserId }, }); } export async function getWeeklyCheckInSessionShares(sessionId: string, userId: string) { // Verify access if (!(await canAccessWeeklyCheckInSession(sessionId, userId))) { throw new Error('Access denied'); } return prisma.wCISessionShare.findMany({ where: { sessionId }, include: { user: { select: { id: true, name: true, email: true } }, }, }); } // ============================================ // Session Events (for real-time sync) // ============================================ export type WCISessionEventType = | 'ITEM_CREATED' | 'ITEM_UPDATED' | 'ITEM_DELETED' | 'ITEM_MOVED' | 'ITEMS_REORDERED' | 'SESSION_UPDATED'; export async function createWeeklyCheckInSessionEvent( sessionId: string, userId: string, type: WCISessionEventType, payload: Record ) { return prisma.wCISessionEvent.create({ data: { sessionId, userId, type, payload: JSON.stringify(payload), }, }); } export async function getWeeklyCheckInSessionEvents(sessionId: string, since?: Date) { return prisma.wCISessionEvent.findMany({ where: { sessionId, ...(since && { createdAt: { gt: since } }), }, include: { user: { select: { id: true, name: true, email: true } }, }, orderBy: { createdAt: 'asc' }, }); } export async function getLatestWeeklyCheckInEventTimestamp(sessionId: string) { const event = await prisma.wCISessionEvent.findFirst({ where: { sessionId }, orderBy: { createdAt: 'desc' }, select: { createdAt: true }, }); return event?.createdAt; }