Skip to content

Commit

Permalink
feat(api): [Transaction] add create transaction api
Browse files Browse the repository at this point in the history
  • Loading branch information
duongdev committed Jun 8, 2024
1 parent a961861 commit 8cef7c9
Show file tree
Hide file tree
Showing 5 changed files with 460 additions and 291 deletions.
2 changes: 2 additions & 0 deletions apps/api/v1/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Hono } from 'hono'
import { authMiddleware } from './middlewares/auth'
import authApp from './routes/auth'
import budgetsApp from './routes/budgets'
import transactionsApp from './routes/transactions'
import usersApp from './routes/users'
import walletsApp from './routes/wallets'

Expand All @@ -12,4 +13,5 @@ hono.use('*', authMiddleware)
hono.route('/auth', authApp)
hono.route('/budgets', budgetsApp)
hono.route('/users', usersApp)
hono.route('/transactions', transactionsApp)
hono.route('/wallets', walletsApp)
95 changes: 95 additions & 0 deletions apps/api/v1/routes/transactions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { zValidator } from '@hono/zod-validator'
import { Hono } from 'hono'
import { z } from 'zod'
import { getAuthUserStrict } from '../middlewares/auth'
import { canUserReadBudget, findBudget } from '../services/budget.service'
import {
canUserCreateTransaction,
createTransaction,
} from '../services/transaction.service'
import { findUserWallet } from '../services/wallet.service'
import {
zCreateTransaction,
zUpdateTransaction,
} from '../validation/transaction.zod'

const router = new Hono()

router.get(
'/',
zValidator(
'query',
z.object({
order_by: z.enum(['date']).optional(),
order: z.enum(['asc', 'desc']).optional(),
wallet_id: z.string().optional(),
budget_id: z.string().optional(),
from_date: z.string().optional(),
to_date: z.string().optional(),
take: z.number().optional(),
skip: z.number().optional(),
cursor: z.string().optional(),
}),
),
async (c) => {
return c.json([])
},
)

router.post('/', zValidator('json', zCreateTransaction), async (c) => {
const user = getAuthUserStrict(c)
const data = c.req.valid('json')
const { budgetId, walletAccountId: walletId } = data

const budget = budgetId ? await findBudget({ budgetId }) : null
if (budgetId && (!budget || !(await canUserReadBudget({ user, budget })))) {
return c.json({ message: 'budget not found' }, 404)
}

const wallet = await findUserWallet({ user, walletId })
if (!wallet) {
return c.json({ message: 'wallet not found' }, 404)
}

if (
!(await canUserCreateTransaction({ user, budget, walletAccount: wallet }))
) {
return c.json({ message: 'user cannot create transaction' }, 403)
}

const transaction = await createTransaction({
user,
data,
})

return c.json(transaction, 201)
})

router.put(
'/:transactionId',
zValidator(
'param',
z.object({
transactionId: z.string(),
}),
),
zValidator('json', zUpdateTransaction),
async (c) => {
return c.json({ message: 'not implemented' })
},
)

router.delete(
'/:transactionId',
zValidator(
'param',
z.object({
transactionId: z.string(),
}),
),
async (c) => {
return c.json({ message: 'not implemented' })
},
)

export default router
43 changes: 43 additions & 0 deletions apps/api/v1/services/transaction.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
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'

export async function canUserCreateTransaction({
user,
budget,
walletAccount,
}: {
user: User
budget: Budget | null
walletAccount: UserWalletAccount | null
}) {
// If budget is provided, user must be a member of the budget
if (budget && !(await isUserBudgetMember({ user, budget }))) {
return false
}

// If wallet is provided, user must own the wallet
if (walletAccount && walletAccount.userId !== user.id) {
return false
}

return true
}

export async function createTransaction({
user,
data,
}: {
user: User
data: CreateTransaction
}) {
const transaction = await prisma.transaction.create({
data: {
...data,
createdByUserId: user.id,
},
})

return transaction
}
21 changes: 21 additions & 0 deletions apps/api/v1/validation/transaction.zod.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { z } from 'zod'

export const zCreateTransaction = z.object({
date: z.date(),
amount: z.number(),
currency: z.string(),
note: z.string().optional(),
budgetId: z.string().optional(),
walletAccountId: z.string(),
})
export type CreateTransaction = z.infer<typeof zCreateTransaction>

export const zUpdateTransaction = z.object({
date: z.date().optional(),
amount: z.number().optional(),
currency: z.string().optional(),
note: z.string().optional(),
budgetId: z.string().optional(),
walletId: z.string().optional(),
})
export type UpdateTransaction = z.infer<typeof zUpdateTransaction>
Loading

0 comments on commit 8cef7c9

Please sign in to comment.