import { prisma } from '@/services/database'; import { resolveCollaborator } from '@/services/auth'; import { getTeamMemberIdsForAdminTeams } from '@/services/teams'; import { createSessionPermissionChecks } from '@/services/session-permissions'; import { createShareAndEventHandlers } from '@/services/session-share-events'; import { mergeSessionsByUserId, fetchTeamCollaboratorSessions, getSessionByIdGeneric, } from '@/services/session-queries'; import type { SwotCategory, ShareRole } from '@prisma/client'; const sessionInclude = { user: { select: { id: true, name: true, email: true } }, shares: { include: { user: { select: { id: true, name: true, email: true } } } }, _count: { select: { items: true, actions: true } }, }; // ============================================ // Session CRUD // ============================================ export async function getSessionsByUserId(userId: string) { return mergeSessionsByUserId( (uid) => prisma.session.findMany({ where: { userId: uid }, include: sessionInclude, orderBy: { updatedAt: 'desc' }, }), (uid) => prisma.sessionShare.findMany({ where: { userId: uid }, include: { session: { include: sessionInclude } }, }), userId, (s) => resolveCollaborator(s.collaborator).then((r) => ({ resolvedCollaborator: r })) ); } /** Sessions owned by team members (where user is team admin) that are NOT shared with the user. */ export async function getTeamCollaboratorSessionsForAdmin(userId: string) { return fetchTeamCollaboratorSessions( (teamMemberIds, uid) => prisma.session.findMany({ where: { userId: { in: teamMemberIds }, shares: { none: { userId: uid } } }, include: sessionInclude, orderBy: { updatedAt: 'desc' }, }), getTeamMemberIdsForAdminTeams, userId, (s) => resolveCollaborator(s.collaborator).then((r) => ({ resolvedCollaborator: r })) ); } const sessionByIdInclude = { user: { select: { id: true, name: true, email: true } }, items: { orderBy: { order: 'asc' } as const }, actions: { include: { links: { include: { swotItem: true } } }, orderBy: { createdAt: 'asc' } as const, }, shares: { include: { user: { select: { id: true, name: true, email: true } } } }, }; export async function getSessionById(sessionId: string, userId: string) { return getSessionByIdGeneric( sessionId, userId, (sid, uid) => prisma.session.findFirst({ where: { id: sid, OR: [{ userId: uid }, { shares: { some: { userId: uid } } }] }, include: sessionByIdInclude, }), (sid) => prisma.session.findUnique({ where: { id: sid }, include: sessionByIdInclude }), (s) => resolveCollaborator(s.collaborator).then((r) => ({ resolvedCollaborator: r })) ); } const sessionPermissions = createSessionPermissionChecks(prisma.session); const sessionShareEvents = createShareAndEventHandlers< | 'ITEM_CREATED' | 'ITEM_UPDATED' | 'ITEM_DELETED' | 'ITEM_MOVED' | 'ACTION_CREATED' | 'ACTION_UPDATED' | 'ACTION_DELETED' | 'SESSION_UPDATED' >(prisma.session, prisma.sessionShare, prisma.sessionEvent, sessionPermissions.canAccess); export const canAccessSession = sessionPermissions.canAccess; export const canEditSession = sessionPermissions.canEdit; export const canDeleteSession = sessionPermissions.canDelete; export async function createSession(userId: string, data: { title: string; collaborator: string }) { return prisma.session.create({ data: { ...data, userId, }, }); } export async function updateSession( sessionId: string, userId: string, data: { title?: string; collaborator?: string } ) { if (!(await canEditSession(sessionId, userId))) { return { count: 0 }; } return prisma.session.updateMany({ where: { id: sessionId }, data, }); } export async function deleteSession(sessionId: string, userId: string) { if (!(await canDeleteSession(sessionId, userId))) { return { count: 0 }; } return prisma.session.deleteMany({ where: { id: sessionId }, }); } // ============================================ // SWOT Items CRUD // ============================================ export async function createSwotItem( sessionId: string, data: { content: string; category: SwotCategory } ) { // Get max order for this category const maxOrder = await prisma.swotItem.aggregate({ where: { sessionId, category: data.category }, _max: { order: true }, }); return prisma.swotItem.create({ data: { ...data, sessionId, order: (maxOrder._max.order ?? -1) + 1, }, }); } export async function updateSwotItem( itemId: string, data: { content?: string; category?: SwotCategory; order?: number } ) { return prisma.swotItem.update({ where: { id: itemId }, data, }); } export async function deleteSwotItem(itemId: string) { return prisma.swotItem.delete({ where: { id: itemId }, }); } export async function duplicateSwotItem(itemId: string) { const original = await prisma.swotItem.findUnique({ where: { id: itemId }, }); if (!original) { throw new Error('Item not found'); } // Get max order for this category const maxOrder = await prisma.swotItem.aggregate({ where: { sessionId: original.sessionId, category: original.category }, _max: { order: true }, }); return prisma.swotItem.create({ data: { content: original.content, category: original.category, sessionId: original.sessionId, order: (maxOrder._max.order ?? -1) + 1, }, }); } export async function reorderSwotItems( sessionId: string, category: SwotCategory, itemIds: string[] ) { const updates = itemIds.map((id, index) => prisma.swotItem.update({ where: { id }, data: { order: index }, }) ); return prisma.$transaction(updates); } export async function moveSwotItem(itemId: string, newCategory: SwotCategory, newOrder: number) { return prisma.swotItem.update({ where: { id: itemId }, data: { category: newCategory, order: newOrder, }, }); } // ============================================ // Actions CRUD // ============================================ export async function createAction( sessionId: string, data: { title: string; description?: string; priority?: number; linkedItemIds: string[]; } ) { return prisma.action.create({ data: { title: data.title, description: data.description, priority: data.priority ?? 0, sessionId, links: { create: data.linkedItemIds.map((swotItemId) => ({ swotItemId, })), }, }, include: { links: { include: { swotItem: true, }, }, }, }); } export async function updateAction( actionId: string, data: { title?: string; description?: string; priority?: number; status?: string; dueDate?: Date | null; linkedItemIds?: string[]; } ) { const { linkedItemIds, ...updateData } = data; // If linkedItemIds is provided, update the links if (linkedItemIds !== undefined) { // Delete all existing links await prisma.actionLink.deleteMany({ where: { actionId }, }); // Create new links if (linkedItemIds.length > 0) { await prisma.actionLink.createMany({ data: linkedItemIds.map((swotItemId) => ({ actionId, swotItemId, })), }); } } return prisma.action.update({ where: { id: actionId }, data: updateData, include: { links: { include: { swotItem: true, }, }, }, }); } export async function deleteAction(actionId: string) { return prisma.action.delete({ where: { id: actionId }, }); } export async function linkItemToAction(actionId: string, swotItemId: string) { return prisma.actionLink.create({ data: { actionId, swotItemId, }, }); } export async function unlinkItemFromAction(actionId: string, swotItemId: string) { return prisma.actionLink.deleteMany({ where: { actionId, swotItemId, }, }); } // ============================================ // Session Sharing // ============================================ export const shareSession = sessionShareEvents.share; export const removeShare = sessionShareEvents.removeShare; export const getSessionShares = sessionShareEvents.getShares; // ============================================ // Session Events (for real-time sync) // ============================================ export type SessionEventType = | 'ITEM_CREATED' | 'ITEM_UPDATED' | 'ITEM_DELETED' | 'ITEM_MOVED' | 'ACTION_CREATED' | 'ACTION_UPDATED' | 'ACTION_DELETED' | 'SESSION_UPDATED'; export const createSessionEvent = sessionShareEvents.createEvent; export const getSessionEvents = sessionShareEvents.getEvents; export const getLatestEventTimestamp = sessionShareEvents.getLatestEventTimestamp;