Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions backend/src/controllers/ai.controller.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Response } from 'express'
import { NextFunction, Response } from 'express'
import { AuthRequest } from '../middlewares/auth.middleware'
import * as aiService from '../services/ai.service'

export const chat = async (req: AuthRequest, res: Response) => {
export const chat = async (req: AuthRequest, res: Response, next: NextFunction) => {
try {
const { message } = req.body
if (!message || typeof message !== 'string' || message.trim().length === 0) {
Expand All @@ -12,15 +12,17 @@ export const chat = async (req: AuthRequest, res: Response) => {
const result = await aiService.chat(req.user!.id, message.trim())
res.status(200).json(result)
} catch (error: any) {
res.status(500).json({ message: error.message })
res.status(500)
next(error)
}
}

export const getInsights = async (req: AuthRequest, res: Response) => {
export const getInsights = async (req: AuthRequest, res: Response, next: NextFunction) => {
try {
const result = await aiService.getInsights(req.user!.id)
res.status(200).json(result)
} catch (error: any) {
res.status(500).json({ message: error.message })
res.status(500)
next(error)
}
}
39 changes: 30 additions & 9 deletions backend/src/controllers/auth.controller.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { Request, Response } from 'express'
import { NextFunction, Request, Response } from 'express'
import * as authService from '../services/auth.service'
import { registerSchema, loginSchema } from '../validators/auth.validator'
import { registerSchema, loginSchema, updateProfileSchema } from '../validators/auth.validator'
import { AuthRequest } from '../middlewares/auth.middleware'

export const register = async (req: Request, res: Response) => {
export const register = async (req: Request, res: Response, next: NextFunction) => {
try {
const parsed = registerSchema.safeParse(req.body)
if (!parsed.success) {
Expand All @@ -27,12 +27,12 @@ export const register = async (req: Request, res: Response) => {
token: result.token,
})
} catch (error: any) {
const status = error.message === 'User already exists' ? 409 : 400
res.status(status).json({ message: error.message })
res.status(error.message === 'User already exists' ? 409 : 400)
next(error)
}
}

export const login = async (req: Request, res: Response) => {
export const login = async (req: Request, res: Response, next: NextFunction) => {
try {
const parsed = loginSchema.safeParse(req.body)
if (!parsed.success) {
Expand All @@ -56,16 +56,37 @@ export const login = async (req: Request, res: Response) => {
token: result.token,
})
} catch (error: any) {
res.status(401).json({ message: error.message })
res.status(401)
next(error)
}
}

export const getMe = async (req: AuthRequest, res: Response) => {
export const getMe = async (req: AuthRequest, res: Response, next: NextFunction) => {
try {
const userId = req.user!.id
const user = await authService.getMe(userId)
res.status(200).json({ user })
} catch (error: any) {
res.status(404).json({ message: error.message })
res.status(404)
next(error)
}
}

export const updateProfile = async (req: AuthRequest, res: Response, next: NextFunction) => {
try {
const parsed = updateProfileSchema.safeParse(req.body)
if (!parsed.success) {
return res.status(400).json({ message: parsed.error.issues[0].message })
}

const user = await authService.updateProfile(req.user!.id, parsed.data.name.trim())
res.status(200).json({ message: 'Profile updated', user })
} catch (error: any) {
res.status(400)
next(error)
}
}

export const updateAvatarPlaceholder = async (_req: AuthRequest, res: Response) => {
res.status(501).json({ message: 'Profile photo upload is not implemented yet' })
}
35 changes: 19 additions & 16 deletions backend/src/controllers/goal.controller.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { Response } from 'express'
import { NextFunction, Response } from 'express'
import { AuthRequest } from '../middlewares/auth.middleware'
import * as goalService from '../services/goal.service'
import { createGoalSchema, updateGoalSchema, contributeSchema } from '../validators/goal.validator'

export const create = async (req: AuthRequest, res: Response) => {
export const create = async (req: AuthRequest, res: Response, next: NextFunction) => {
try {
const parsed = createGoalSchema.safeParse(req.body)
if (!parsed.success) {
Expand All @@ -12,29 +12,32 @@ export const create = async (req: AuthRequest, res: Response) => {
const goal = await goalService.create(req.user!.id, parsed.data)
res.status(201).json({ message: 'Goal created', goal })
} catch (error: any) {
res.status(400).json({ message: error.message })
res.status(400)
next(error)
}
}

export const getAll = async (req: AuthRequest, res: Response) => {
export const getAll = async (req: AuthRequest, res: Response, next: NextFunction) => {
try {
const goals = await goalService.getAll(req.user!.id)
res.status(200).json({ goals })
} catch (error: any) {
res.status(400).json({ message: error.message })
res.status(400)
next(error)
}
}

export const getById = async (req: AuthRequest, res: Response) => {
export const getById = async (req: AuthRequest, res: Response, next: NextFunction) => {
try {
const goal = await goalService.getById(req.user!.id, req.params.id as string)
res.status(200).json({ goal })
} catch (error: any) {
res.status(404).json({ message: error.message })
res.status(404)
next(error)
}
}

export const update = async (req: AuthRequest, res: Response) => {
export const update = async (req: AuthRequest, res: Response, next: NextFunction) => {
try {
const parsed = updateGoalSchema.safeParse(req.body)
if (!parsed.success) {
Expand All @@ -43,22 +46,22 @@ export const update = async (req: AuthRequest, res: Response) => {
const goal = await goalService.update(req.user!.id, req.params.id as string, parsed.data)
res.status(200).json({ message: 'Goal updated', goal })
} catch (error: any) {
const status = error.message === 'Goal not found' ? 404 : 400
res.status(status).json({ message: error.message })
res.status(error.message === 'Goal not found' ? 404 : 400)
next(error)
}
}

export const remove = async (req: AuthRequest, res: Response) => {
export const remove = async (req: AuthRequest, res: Response, next: NextFunction) => {
try {
const result = await goalService.remove(req.user!.id, req.params.id as string)
res.status(200).json(result)
} catch (error: any) {
const status = error.message === 'Goal not found' ? 404 : 400
res.status(status).json({ message: error.message })
res.status(error.message === 'Goal not found' ? 404 : 400)
next(error)
}
}

export const contribute = async (req: AuthRequest, res: Response) => {
export const contribute = async (req: AuthRequest, res: Response, next: NextFunction) => {
try {
const parsed = contributeSchema.safeParse(req.body)
if (!parsed.success) {
Expand All @@ -67,7 +70,7 @@ export const contribute = async (req: AuthRequest, res: Response) => {
const goal = await goalService.contribute(req.user!.id, req.params.id as string, parsed.data.amount)
res.status(200).json({ message: 'Contribution added', goal })
} catch (error: any) {
const status = error.message === 'Goal not found' ? 404 : 400
res.status(status).json({ message: error.message })
res.status(error.message === 'Goal not found' ? 404 : 400)
next(error)
}
}
12 changes: 7 additions & 5 deletions backend/src/controllers/passport.controller.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
import { Response } from 'express'
import { NextFunction, Response } from 'express'
import { AuthRequest } from '../middlewares/auth.middleware'
import * as passportService from '../services/passport.service'

export const getPassport = async (req: AuthRequest, res: Response) => {
export const getPassport = async (req: AuthRequest, res: Response, next: NextFunction) => {
try {
const passport = await passportService.getPassport(req.user!.id)
res.status(200).json({ passport })
} catch (error: any) {
res.status(500).json({ message: error.message })
res.status(500)
next(error)
}
}

export const getBreakdown = async (req: AuthRequest, res: Response) => {
export const getBreakdown = async (req: AuthRequest, res: Response, next: NextFunction) => {
try {
const breakdown = await passportService.getBreakdown(req.user!.id)
res.status(200).json({ breakdown })
} catch (error: any) {
res.status(500).json({ message: error.message })
res.status(500)
next(error)
}
}
34 changes: 19 additions & 15 deletions backend/src/controllers/transaction.controller.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { Response } from 'express'
import { NextFunction, Response } from 'express'
import { AuthRequest } from '../middlewares/auth.middleware'
import * as transactionService from '../services/transaction.service'
import { createTransactionSchema, updateTransactionSchema } from '../validators/transaction.validator'

export const create = async (req: AuthRequest, res: Response) => {
export const create = async (req: AuthRequest, res: Response, next: NextFunction) => {
try {
const parsed = createTransactionSchema.safeParse(req.body)
if (!parsed.success) {
Expand All @@ -12,11 +12,12 @@ export const create = async (req: AuthRequest, res: Response) => {
const transaction = await transactionService.create(req.user!.id, parsed.data)
res.status(201).json({ message: 'Transaction created', transaction })
} catch (error: any) {
res.status(400).json({ message: error.message })
res.status(400)
next(error)
}
}

export const getAll = async (req: AuthRequest, res: Response) => {
export const getAll = async (req: AuthRequest, res: Response, next: NextFunction) => {
try {
const filters = {
type: req.query.type as string,
Expand All @@ -29,20 +30,22 @@ export const getAll = async (req: AuthRequest, res: Response) => {
const result = await transactionService.getAll(req.user!.id, filters)
res.status(200).json(result)
} catch (error: any) {
res.status(400).json({ message: error.message })
res.status(400)
next(error)
}
}

export const getById = async (req: AuthRequest, res: Response) => {
export const getById = async (req: AuthRequest, res: Response, next: NextFunction) => {
try {
const transaction = await transactionService.getById(req.user!.id, req.params.id as string)
res.status(200).json({ transaction })
} catch (error: any) {
res.status(404).json({ message: error.message })
res.status(404)
next(error)
}
}

export const update = async (req: AuthRequest, res: Response) => {
export const update = async (req: AuthRequest, res: Response, next: NextFunction) => {
try {
const parsed = updateTransactionSchema.safeParse(req.body)
if (!parsed.success) {
Expand All @@ -51,26 +54,27 @@ export const update = async (req: AuthRequest, res: Response) => {
const transaction = await transactionService.update(req.user!.id, req.params.id as string, parsed.data)
res.status(200).json({ message: 'Transaction updated', transaction })
} catch (error: any) {
const status = error.message === 'Transaction not found' ? 404 : 400
res.status(status).json({ message: error.message })
res.status(error.message === 'Transaction not found' ? 404 : 400)
next(error)
}
}

export const remove = async (req: AuthRequest, res: Response) => {
export const remove = async (req: AuthRequest, res: Response, next: NextFunction) => {
try {
const result = await transactionService.remove(req.user!.id, req.params.id as string)
res.status(200).json(result)
} catch (error: any) {
const status = error.message === 'Transaction not found' ? 404 : 400
res.status(status).json({ message: error.message })
res.status(error.message === 'Transaction not found' ? 404 : 400)
next(error)
}
}

export const getSummary = async (req: AuthRequest, res: Response) => {
export const getSummary = async (req: AuthRequest, res: Response, next: NextFunction) => {
try {
const summary = await transactionService.getSummary(req.user!.id)
res.status(200).json({ summary })
} catch (error: any) {
res.status(400).json({ message: error.message })
res.status(400)
next(error)
}
}
42 changes: 35 additions & 7 deletions backend/src/middlewares/error.middleware.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,40 @@
import { Request, Response, NextFunction } from 'express'
import { NextFunction, Request, Response } from 'express'
import { Prisma } from '@prisma/client'

export const errorHandler = (err: Error, req: Request, res: Response, _next: NextFunction) => {
console.error(`[Error] ${req.method} ${req.path}:`, err.message)
const CONNECTION_ERROR_MESSAGE = 'Connection error. Please check your internet connection.'

function isPrismaConnectionError(err: unknown) {
if (
err instanceof Prisma.PrismaClientInitializationError ||
err instanceof Prisma.PrismaClientRustPanicError
) {
return true
}

if (err instanceof Prisma.PrismaClientKnownRequestError) {
return ['P1000', 'P1001', 'P1002', 'P1017'].includes(err.code)
}

if (err instanceof Error) {
return /prisma|database|connection/i.test(err.message)
}

return false
}

export const errorHandler = (err: unknown, req: Request, res: Response, _next: NextFunction) => {
const statusCode = res.statusCode !== 200 ? res.statusCode : 500
const isConnectionError = isPrismaConnectionError(err)

console.error(`[Error] ${req.method} ${req.path}:`, err)

if (isConnectionError || statusCode >= 500) {
return res.status(isConnectionError ? 503 : 500).json({
message: CONNECTION_ERROR_MESSAGE,
})
}

const message = err instanceof Error && err.message ? err.message : 'Something went wrong. Please try again.'

res.status(statusCode).json({
message: err.message || 'Internal server error',
...(process.env.NODE_ENV !== 'production' && { stack: err.stack }),
})
return res.status(statusCode).json({ message })
}
4 changes: 3 additions & 1 deletion backend/src/routes/auth.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,7 @@ const router = Router()
router.post('/register', authController.register)
router.post('/login', authController.login)
router.get('/me', protect, authController.getMe)
router.put('/profile', protect, authController.updateProfile)
router.put('/profile/avatar', protect, authController.updateAvatarPlaceholder)

export default router
export default router
17 changes: 16 additions & 1 deletion backend/src/services/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,4 +86,19 @@ export const getMe = async ( userId: string ) => {
}

return user
}
}

export const updateProfile = async (userId: string, name: string) => {
const user = await prisma.user.update({
where: { id: userId },
data: { name },
select: {
id: true,
name: true,
email: true,
createdAt: true
}
})

return user
}
Loading
Loading