diff options
| author | Sebastien Castiel <sebastien@castiel.me> | 2023-12-05 17:39:05 -0500 |
|---|---|---|
| committer | Sebastien Castiel <sebastien@castiel.me> | 2023-12-05 17:39:05 -0500 |
| commit | ed55c696cd083bfaa5429082a9c5edd4ebde0cd8 (patch) | |
| tree | 9f387881f85716bcb4a05d6fce13bfe59fbc9c17 /src/lib | |
| parent | 1fd6e48807d455f66d9cd79db64cbf9e9f947c6c (diff) | |
First version
Diffstat (limited to 'src/lib')
| -rw-r--r-- | src/lib/api.ts | 155 | ||||
| -rw-r--r-- | src/lib/prisma.ts | 17 | ||||
| -rw-r--r-- | src/lib/schemas.ts | 50 | ||||
| -rw-r--r-- | src/lib/utils.ts | 6 |
4 files changed, 228 insertions, 0 deletions
diff --git a/src/lib/api.ts b/src/lib/api.ts new file mode 100644 index 0000000..6ea26f4 --- /dev/null +++ b/src/lib/api.ts @@ -0,0 +1,155 @@ +import { getPrisma } from '@/lib/prisma' +import { ExpenseFormValues, GroupFormValues } from '@/lib/schemas' +import { Expense } from '@prisma/client' +import { v4 as uuidv4 } from 'uuid' + +export async function createGroup(groupFormValues: GroupFormValues) { + return getPrisma().group.create({ + data: { + id: uuidv4(), + name: groupFormValues.name, + participants: { + createMany: { + data: groupFormValues.participants.map(({ name }) => ({ + id: uuidv4(), + name, + })), + }, + }, + }, + include: { participants: true }, + }) +} + +export async function createExpense( + expenseFormValues: ExpenseFormValues, + groupId: string, +): Promise<Expense> { + const group = await getGroup(groupId) + if (!group) throw new Error(`Invalid group ID: ${groupId}`) + + for (const participant of [ + expenseFormValues.paidBy, + ...expenseFormValues.paidFor, + ]) { + if (!group.participants.some((p) => p.id === participant)) + throw new Error(`Invalid participant ID: ${participant}`) + } + + return getPrisma().expense.create({ + data: { + id: uuidv4(), + groupId, + amount: expenseFormValues.amount, + title: expenseFormValues.title, + paidById: expenseFormValues.paidBy, + paidFor: { + createMany: { + data: expenseFormValues.paidFor.map((paidFor) => ({ + participantId: paidFor, + })), + }, + }, + }, + }) +} + +export async function updateExpense( + groupId: string, + expenseId: string, + expenseFormValues: ExpenseFormValues, +) { + 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, + ]) { + if (!group.participants.some((p) => p.id === participant)) + throw new Error(`Invalid participant ID: ${participant}`) + } + + return getPrisma().expense.update({ + where: { id: expenseId }, + data: { + amount: expenseFormValues.amount, + title: expenseFormValues.title, + paidById: expenseFormValues.paidBy, + paidFor: { + connectOrCreate: expenseFormValues.paidFor.map((paidFor) => ({ + where: { + expenseId_participantId: { expenseId, participantId: paidFor }, + }, + create: { participantId: paidFor }, + })), + deleteMany: existingExpense.paidFor.filter( + (paidFor) => + !expenseFormValues.paidFor.some( + (pf) => pf === paidFor.participantId, + ), + ), + }, + }, + }) +} + +export async function updateGroup( + groupId: string, + groupFormValues: GroupFormValues, +) { + const existingGroup = await getGroup(groupId) + if (!existingGroup) throw new Error('Invalid group ID') + + return getPrisma().group.update({ + where: { id: groupId }, + data: { + name: groupFormValues.name, + 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: uuidv4(), + name: participant.name, + })), + }, + }, + }, + }) +} + +export async function getGroup(groupId: string) { + return getPrisma().group.findUnique({ + where: { id: groupId }, + include: { participants: true }, + }) +} + +export async function getGroupExpenses(groupId: string) { + return getPrisma().expense.findMany({ + where: { groupId }, + include: { paidFor: { include: { participant: true } }, paidBy: true }, + }) +} + +export async function getExpense(groupId: string, expenseId: string) { + return getPrisma().expense.findUnique({ + where: { id: expenseId }, + include: { paidBy: true, paidFor: true }, + }) +} diff --git a/src/lib/prisma.ts b/src/lib/prisma.ts new file mode 100644 index 0000000..773547e --- /dev/null +++ b/src/lib/prisma.ts @@ -0,0 +1,17 @@ +import { PrismaClient } from '@prisma/client' + +let prisma: PrismaClient + +export function getPrisma() { + if (!prisma) { + if (process.env.NODE_ENV === 'production') { + prisma = new PrismaClient() + } else { + if (!(global as any).prisma) { + ;(global as any).prisma = new PrismaClient() + } + prisma = (global as any).prisma + } + } + return prisma +} diff --git a/src/lib/schemas.ts b/src/lib/schemas.ts new file mode 100644 index 0000000..0219491 --- /dev/null +++ b/src/lib/schemas.ts @@ -0,0 +1,50 @@ +import * as z from 'zod' + +export const groupFormSchema = z + .object({ + name: z + .string() + .min(2, 'Enter at least two characters.') + .max(50, 'Enter at most 50 characters.'), + participants: z + .array( + z.object({ + id: z.string().optional(), + name: z + .string() + .min(2, 'Enter at least two characters.') + .max(50, 'Enter at most 50 characters.'), + }), + ) + .min(1), + }) + .superRefine(({ participants }, ctx) => { + participants.forEach((participant, i) => { + participants.slice(0, i).forEach((otherParticipant) => { + if (otherParticipant.name === participant.name) { + ctx.addIssue({ + code: 'custom', + message: 'Another participant already has this name.', + path: ['participants', i, 'name'], + }) + } + }) + }) + }) + +export type GroupFormValues = z.infer<typeof groupFormSchema> + +export const expenseFormSchema = z.object({ + title: z + .string({ required_error: 'Please enter a title.' }) + .min(2, 'Enter at least two characters.'), + amount: z.coerce + .number({ required_error: 'You must enter an amount.' }) + .min(0.01, 'The amount must be higher than 0.01.'), + paidBy: z.string({ required_error: 'You must select a participant.' }), + paidFor: z + .array(z.string()) + .min(1, 'The expense must be paid for at least 1 participant.'), +}) + +export type ExpenseFormValues = z.infer<typeof expenseFormSchema> diff --git a/src/lib/utils.ts b/src/lib/utils.ts new file mode 100644 index 0000000..ec79801 --- /dev/null +++ b/src/lib/utils.ts @@ -0,0 +1,6 @@ +import { type ClassValue, clsx } from "clsx" +import { twMerge } from "tailwind-merge" + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)) +} |
