import { prisma } from '@/lib/prisma' import { ExpenseFormValues, GroupFormValues } from '@/lib/schemas' import { ActivityType, Expense } from '@prisma/client' import { nanoid } from 'nanoid' export function randomId() { return nanoid() } export async function createGroup(groupFormValues: GroupFormValues) { return prisma.group.create({ data: { id: randomId(), name: groupFormValues.name, currency: groupFormValues.currency, participants: { createMany: { data: groupFormValues.participants.map(({ name }) => ({ id: randomId(), name, })), }, }, }, include: { participants: true }, }) } export async function createExpense( expenseFormValues: ExpenseFormValues, groupId: string, participantId?: string, ): Promise { const group = await getGroup(groupId) if (!group) throw new Error(`Invalid group ID: ${groupId}`) for (const participant of [ expenseFormValues.paidBy, ...expenseFormValues.paidFor.map((p) => p.participant), ]) { if (!group.participants.some((p) => p.id === participant)) throw new Error(`Invalid participant ID: ${participant}`) } const expenseId = randomId() await logActivity(groupId, ActivityType.CREATE_EXPENSE, { participantId, expenseId, data: expenseFormValues.title, }) return prisma.expense.create({ data: { id: expenseId, groupId, expenseDate: expenseFormValues.expenseDate, categoryId: expenseFormValues.category, amount: expenseFormValues.amount, title: expenseFormValues.title, paidById: expenseFormValues.paidBy, splitMode: expenseFormValues.splitMode, paidFor: { createMany: { data: expenseFormValues.paidFor.map((paidFor) => ({ participantId: paidFor.participant, shares: paidFor.shares, })), }, }, isReimbursement: expenseFormValues.isReimbursement, documents: { createMany: { data: expenseFormValues.documents.map((doc) => ({ id: randomId(), url: doc.url, width: doc.width, height: doc.height, })), }, }, notes: expenseFormValues.notes, }, }) } export async function deleteExpense( groupId: string, expenseId: string, participantId?: string, ) { const existingExpense = await getExpense(groupId, expenseId) await logActivity(groupId, ActivityType.DELETE_EXPENSE, { participantId, expenseId, data: existingExpense?.title, }) await prisma.expense.delete({ where: { id: expenseId }, include: { paidFor: true, paidBy: true }, }) } export async function getGroupExpensesParticipants(groupId: string) { const expenses = await getGroupExpenses(groupId) return Array.from( new Set( expenses.flatMap((e) => [ e.paidBy.id, ...e.paidFor.map((pf) => pf.participant.id), ]), ), ) } export async function getGroups(groupIds: string[]) { return ( await prisma.group.findMany({ where: { id: { in: groupIds } }, include: { _count: { select: { participants: true } } }, }) ).map((group) => ({ ...group, createdAt: group.createdAt.toISOString(), })) } export async function updateExpense( groupId: string, expenseId: string, expenseFormValues: ExpenseFormValues, participantId?: string, ) { const group = await getGroup(groupId) if (!group) throw new Error(`Invalid group ID: ${groupId}`) const existingExpense = await getExpense(groupId, expenseId) if (!existingExpense) throw new Error(`Invalid expense ID: ${expenseId}`) for (const participant of [ expenseFormValues.paidBy, ...expenseFormValues.paidFor.map((p) => p.participant), ]) { if (!group.participants.some((p) => p.id === participant)) throw new Error(`Invalid participant ID: ${participant}`) } await logActivity(groupId, ActivityType.UPDATE_EXPENSE, { participantId, expenseId, data: expenseFormValues.title, }) return prisma.expense.update({ where: { id: expenseId }, data: { expenseDate: expenseFormValues.expenseDate, amount: expenseFormValues.amount, title: expenseFormValues.title, categoryId: expenseFormValues.category, paidById: expenseFormValues.paidBy, splitMode: expenseFormValues.splitMode, paidFor: { create: expenseFormValues.paidFor .filter( (p) => !existingExpense.paidFor.some( (pp) => pp.participantId === p.participant, ), ) .map((paidFor) => ({ participantId: paidFor.participant, shares: paidFor.shares, })), update: expenseFormValues.paidFor.map((paidFor) => ({ where: { expenseId_participantId: { expenseId, participantId: paidFor.participant, }, }, data: { shares: paidFor.shares, }, })), deleteMany: existingExpense.paidFor.filter( (paidFor) => !expenseFormValues.paidFor.some( (pf) => pf.participant === paidFor.participantId, ), ), }, isReimbursement: expenseFormValues.isReimbursement, documents: { connectOrCreate: expenseFormValues.documents.map((doc) => ({ create: doc, where: { id: doc.id }, })), deleteMany: existingExpense.documents .filter( (existingDoc) => !expenseFormValues.documents.some( (doc) => doc.id === existingDoc.id, ), ) .map((doc) => ({ id: doc.id, })), }, notes: expenseFormValues.notes, }, }) } export async function updateGroup( groupId: string, groupFormValues: GroupFormValues, participantId?: string, ) { const existingGroup = await getGroup(groupId) if (!existingGroup) throw new Error('Invalid group ID') await logActivity(groupId, ActivityType.UPDATE_GROUP, { participantId }) return prisma.group.update({ where: { id: groupId }, data: { name: groupFormValues.name, currency: groupFormValues.currency, participants: { deleteMany: existingGroup.participants.filter( (p) => !groupFormValues.participants.some((p2) => p2.id === p.id), ), updateMany: groupFormValues.participants .filter((participant) => participant.id !== undefined) .map((participant) => ({ where: { id: participant.id }, data: { name: participant.name, }, })), createMany: { data: groupFormValues.participants .filter((participant) => participant.id === undefined) .map((participant) => ({ id: randomId(), name: participant.name, })), }, }, }, }) } export async function getGroup(groupId: string) { return prisma.group.findUnique({ where: { id: groupId }, include: { participants: true }, }) } export async function getCategories() { return prisma.category.findMany() } export async function getGroupExpenses( groupId: string, options?: { offset: number; length: number }, ) { return prisma.expense.findMany({ select: { amount: true, category: true, createdAt: true, expenseDate: true, id: true, isReimbursement: true, paidBy: { select: { id: true, name: true } }, paidFor: { select: { participant: { select: { id: true, name: true } }, shares: true, }, }, splitMode: true, title: true, }, where: { groupId }, orderBy: [{ expenseDate: 'desc' }, { createdAt: 'desc' }], skip: options && options.offset, take: options && options.length, }) } export async function getGroupExpenseCount(groupId: string) { return prisma.expense.count({ where: { groupId } }) } export async function getExpense(groupId: string, expenseId: string) { return prisma.expense.findUnique({ where: { id: expenseId }, include: { paidBy: true, paidFor: true, category: true, documents: true }, }) } export async function getActivities(groupId: string) { return prisma.activity.findMany({ where: { groupId }, orderBy: [{ time: 'desc' }], }) } export async function logActivity( groupId: string, activityType: ActivityType, extra?: { participantId?: string; expenseId?: string; data?: string }, ) { return prisma.activity.create({ data: { id: randomId(), groupId, activityType, ...extra, }, }) }