import { prisma } from '@/services/database'; import type { SwotCategory, ShareRole } from '@prisma/client'; // ============================================ // Session CRUD // ============================================ export async function getSessionsByUserId(userId: string) { // Get owned sessions + shared sessions const [owned, shared] = await Promise.all([ prisma.session.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, actions: true, }, }, }, orderBy: { updatedAt: 'desc' }, }), prisma.sessionShare.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, actions: 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, })); return [...ownedWithRole, ...sharedWithRole].sort( (a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime() ); } export async function getSessionById(sessionId: string, userId: string) { // Check if user owns the session OR has it shared const session = await prisma.session.findFirst({ where: { id: sessionId, OR: [ { userId }, // Owner { shares: { some: { userId } } }, // Shared with user ], }, include: { user: { select: { id: true, name: true, email: true } }, items: { orderBy: { order: 'asc' }, }, actions: { include: { links: { include: { swotItem: true, }, }, }, orderBy: { createdAt: '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'; return { ...session, isOwner, role, canEdit }; } // Check if user can access session (owner or shared) export async function canAccessSession(sessionId: string, userId: string) { const count = await prisma.session.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 canEditSession(sessionId: string, userId: string) { const count = await prisma.session.count({ where: { id: sessionId, OR: [{ userId }, { shares: { some: { userId, role: 'EDITOR' } } }], }, }); return count > 0; } 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 } ) { return prisma.session.updateMany({ where: { id: sessionId, userId }, data, }); } export async function deleteSession(sessionId: string, userId: string) { return prisma.session.deleteMany({ where: { id: sessionId, userId }, }); } // ============================================ // 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; } ) { return prisma.action.update({ where: { id: actionId }, data, }); } 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 async function shareSession( sessionId: string, ownerId: string, targetEmail: string, role: ShareRole = 'EDITOR' ) { // Verify owner const session = await prisma.session.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.sessionShare.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 removeShare(sessionId: string, ownerId: string, shareUserId: string) { // Verify owner const session = await prisma.session.findFirst({ where: { id: sessionId, userId: ownerId }, }); if (!session) { throw new Error('Session not found or not owned'); } return prisma.sessionShare.deleteMany({ where: { sessionId, userId: shareUserId }, }); } export async function getSessionShares(sessionId: string, userId: string) { // Verify access if (!(await canAccessSession(sessionId, userId))) { throw new Error('Access denied'); } return prisma.sessionShare.findMany({ where: { sessionId }, include: { user: { select: { id: true, name: true, email: true } }, }, }); } // ============================================ // 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 async function createSessionEvent( sessionId: string, userId: string, type: SessionEventType, payload: Record ) { return prisma.sessionEvent.create({ data: { sessionId, userId, type, payload: JSON.stringify(payload), }, }); } export async function getSessionEvents(sessionId: string, since?: Date) { return prisma.sessionEvent.findMany({ where: { sessionId, ...(since && { createdAt: { gt: since } }), }, include: { user: { select: { id: true, name: true, email: true } }, }, orderBy: { createdAt: 'asc' }, }); } export async function getLatestEventTimestamp(sessionId: string) { const event = await prisma.sessionEvent.findFirst({ where: { sessionId }, orderBy: { createdAt: 'desc' }, select: { createdAt: true }, }); return event?.createdAt; }