Skip to content

Commit

Permalink
feat(api): add budget API routes (#18)
Browse files Browse the repository at this point in the history
## Added APIs

- `GET /budgets?permission`
- `POST /budgets`
- `GET /budgets/:budgetId`
- `PUT /budgets/:budgetId`
- `DELETE /budgets/:budgetId`
  • Loading branch information
duongdev committed Jun 8, 2024
1 parent 71a5b40 commit f90164d
Show file tree
Hide file tree
Showing 6 changed files with 423 additions and 0 deletions.
2 changes: 2 additions & 0 deletions apps/api/v1/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Hono } from 'hono'
import { authMiddleware } from './middlewares/auth'
import authApp from './routes/auth'
import budgetsApp from './routes/budgets'
import usersApp from './routes/users'
import walletsApp from './routes/wallets'

Expand All @@ -9,5 +10,6 @@ export const hono = new Hono()
hono.use('*', authMiddleware)

hono.route('/auth', authApp)
hono.route('/budgets', budgetsApp)
hono.route('/users', usersApp)
hono.route('/wallets', walletsApp)
121 changes: 121 additions & 0 deletions apps/api/v1/routes/budgets.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import { BudgetUserPermissionSchema } from '@/prisma/generated/zod'
import { zValidator } from '@hono/zod-validator'
import { Hono } from 'hono'
import { z } from 'zod'
import { getAuthUserStrict } from '../middlewares/auth'
import {
canUserCreateBudget,
canUserDeleteBudget,
canUserReadBudget,
canUserUpdateBudget,
createBudget,
deleteBudget,
findBudget,
findBudgetsOfUser,
updateBudget,
} from '../services/budget.service'
import { zCreateBudget, zUpdateBudget } from '../validation'

const router = new Hono()

const zBudgetIdParamValidator = zValidator(
'param',
z.object({
budgetId: z.string(),
}),
)

router.get(
'/',
zValidator(
'query',
z.object({
permission: BudgetUserPermissionSchema.optional(),
}),
),
async (c) => {
const user = getAuthUserStrict(c)
const { permission } = c.req.valid('query')

const budgets = await findBudgetsOfUser({ user, permission })

return c.json(budgets)
},
)

router.post('/', zValidator('json', zCreateBudget), async (c) => {
const user = getAuthUserStrict(c)

if (!(await canUserCreateBudget({ user }))) {
return c.json({ message: 'User cannot create budget' }, 403)
}

const createBudgetData = c.req.valid('json')

const budget = await createBudget({ user, data: createBudgetData })

return c.json(budget, 201)
})

router.get('/:budgetId', zBudgetIdParamValidator, async (c) => {
const user = getAuthUserStrict(c)
const { budgetId } = c.req.valid('param')

const budget = await findBudget({ budgetId })

if (!(budget && (await canUserReadBudget({ user, budget })))) {
return c.json(null, 404)
}

return c.json(budget)
})

router.put(
'/:budgetId',
zBudgetIdParamValidator,
zValidator('json', zUpdateBudget),
async (c) => {
const user = getAuthUserStrict(c)
const { budgetId } = c.req.valid('param')

const budget = await findBudget({ budgetId })

if (!(budget && (await canUserReadBudget({ user, budget })))) {
return c.json(null, 404)
}

if (!(await canUserUpdateBudget({ user, budget }))) {
return c.json({ message: 'User cannot update budget' }, 403)
}

const updateBudgetData = c.req.valid('json')

const updatedBudget = await updateBudget({
budgetId,
data: updateBudgetData,
})

return c.json(updatedBudget)
},
)

router.delete('/:budgetId', zBudgetIdParamValidator, async (c) => {
const user = getAuthUserStrict(c)
const { budgetId } = c.req.valid('param')

const budget = await findBudget({ budgetId })

if (!(budget && (await canUserReadBudget({ user, budget })))) {
return c.json(null, 404)
}

if (!(await canUserDeleteBudget({ user, budget }))) {
return c.json({ message: 'User cannot delete budget' }, 403)
}

await deleteBudget({ budgetId })

return c.json(budget)
})

export default router
64 changes: 64 additions & 0 deletions apps/api/v1/services/budget-invitation.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import prisma from '@/lib/prisma'
import {
type Budget,
type BudgetUserInvitation,
BudgetUserPermission,
type User,
} from '@prisma/client'
import { isUserBudgetOwner } from './budget.service'

export async function canUserInviteUserToBudget({
user,
budget,
}: {
user: User
budget: Budget
}): Promise<boolean> {
return isUserBudgetOwner({ user, budget })
}

export async function inviteUserToBudget({
inviter,
budget,
email,
permission = BudgetUserPermission.MEMBER,
}: {
inviter: User
budget: Budget
email: string
permission?: BudgetUserPermission
}) {
// Check if inviter has permission to invite user