diff --git a/apps/api/v1/routes/transactions.ts b/apps/api/v1/routes/transactions.ts index 7e3d1d49..88b349e1 100644 --- a/apps/api/v1/routes/transactions.ts +++ b/apps/api/v1/routes/transactions.ts @@ -5,7 +5,11 @@ import { getAuthUserStrict } from '../middlewares/auth' import { canUserReadBudget, findBudget } from '../services/budget.service' import { canUserCreateTransaction, + canUserReadTransaction, + canUserUpdateTransaction, createTransaction, + findTransaction, + updateTransaction, } from '../services/transaction.service' import { findUserWallet } from '../services/wallet.service' import { @@ -75,7 +79,45 @@ router.put( ), zValidator('json', zUpdateTransaction), async (c) => { - return c.json({ message: 'not implemented' }) + const { transactionId } = c.req.valid('param') + const user = getAuthUserStrict(c) + const data = c.req.valid('json') + const { budgetId, walletAccountId: walletId } = data + + const transaction = await findTransaction({ transactionId }) + + if ( + !(transaction && (await canUserReadTransaction({ user, transaction }))) + ) { + return c.json({ message: 'transaction not found' }, 404) + } + + const wallet = walletId ? await findUserWallet({ user, walletId }) : null + if (walletId && !wallet) { + return c.json({ message: 'wallet not found' }, 404) + } + + if ( + !(await canUserUpdateTransaction({ + user, + transaction, + walletAccount: wallet, + })) + ) { + return c.json({ message: 'user cannot update transaction' }, 403) + } + + const budget = budgetId ? await findBudget({ budgetId }) : null + if (budgetId && (!budget || !(await canUserReadBudget({ user, budget })))) { + return c.json({ message: 'budget not found' }, 404) + } + + const updatedTransaction = await updateTransaction({ + transactionId, + data, + }) + + return c.json(updatedTransaction) }, ) diff --git a/apps/api/v1/services/transaction.service.ts b/apps/api/v1/services/transaction.service.ts index db27d535..8ffa22c0 100644 --- a/apps/api/v1/services/transaction.service.ts +++ b/apps/api/v1/services/transaction.service.ts @@ -1,7 +1,19 @@ import prisma from '@/lib/prisma' -import type { Budget, User, UserWalletAccount } from '@prisma/client' -import type { CreateTransaction } from '../validation/transaction.zod' -import { isUserBudgetMember } from './budget.service' +import type { + Budget, + Transaction, + User, + UserWalletAccount, +} from '@prisma/client' +import type { + CreateTransaction, + UpdateTransaction, +} from '../validation/transaction.zod' +import { + findBudget, + isUserBudgetMember, + isUserBudgetOwner, +} from './budget.service' export async function canUserCreateTransaction({ user, @@ -25,6 +37,67 @@ export async function canUserCreateTransaction({ return true } +export async function canUserReadTransaction({ + user, + transaction, +}: { + user: User + transaction: Transaction +}) { + // If user is the creator of the transaction, they can read it + if (transaction.createdByUserId === user.id) { + return true + } + + // If user is the member of the budget of the transaction, they can read it + if (transaction.budgetId) { + const budget = await findBudget({ budgetId: transaction.budgetId }) + return budget && (await isUserBudgetMember({ user, budget })) + } + + return false +} + +export async function canUserUpdateTransaction({ + user, + transaction, + walletAccount, +}: { + user: User + transaction: Transaction + walletAccount: UserWalletAccount | null +}) { + // If wallet is provided, user must own the wallet + if (walletAccount && walletAccount.userId !== user.id) { + return false + } + + // If user is the creator of the transaction, they can update it + if (transaction.createdByUserId === user.id) { + return true + } + + // If user owns the budget of the transaction, they can update it + if (transaction.budgetId) { + const budget = await findBudget({ budgetId: transaction.budgetId }) + return budget && (await isUserBudgetOwner({ user, budget })) + } + + return false +} + +export async function findTransaction({ + transactionId, +}: { + transactionId: string +}) { + return prisma.transaction.findUnique({ + where: { + id: transactionId, + }, + }) +} + export async function createTransaction({ user, data, @@ -41,3 +114,20 @@ export async function createTransaction({ return transaction } + +export async function updateTransaction({ + transactionId, + data, +}: { + transactionId: string + data: UpdateTransaction +}) { + const transaction = await prisma.transaction.update({ + where: { + id: transactionId, + }, + data, + }) + + return transaction +} diff --git a/apps/api/v1/validation/transaction.zod.ts b/apps/api/v1/validation/transaction.zod.ts index 42112619..da0cb0a9 100644 --- a/apps/api/v1/validation/transaction.zod.ts +++ b/apps/api/v1/validation/transaction.zod.ts @@ -16,6 +16,6 @@ export const zUpdateTransaction = z.object({ currency: z.string().optional(), note: z.string().optional(), budgetId: z.string().optional(), - walletId: z.string().optional(), + walletAccountId: z.string().optional(), }) export type UpdateTransaction = z.infer