Files
flyer-crawler.projectium.com/src/routes/budget.routes.ts
Torben Sorensen 2c1de24e9a
Some checks failed
Deploy to Test Environment / deploy-to-test (push) Failing after 1m21s
undo stupid logging change
2025-12-24 16:54:56 -08:00

158 lines
5.0 KiB
TypeScript

// src/routes/budget.ts
import express, { Request, Response, NextFunction } from 'express';
import { z } from 'zod';
import passport from './passport.routes';
import { budgetRepo } from '../services/db/index.db';
import type { UserProfile } from '../types';
import { validateRequest } from '../middleware/validation.middleware';
import { requiredString, numericIdParam } from '../utils/zodUtils';
const router = express.Router();
// --- Zod Schemas for Budget Routes (as per ADR-003) ---
const budgetIdParamSchema = numericIdParam('id', "Invalid ID for parameter 'id'. Must be a number.");
const createBudgetSchema = z.object({
body: z.object({
name: requiredString('Budget name is required.'),
amount_cents: z.number().int().positive('Amount must be a positive integer.'),
period: z.enum(['weekly', 'monthly']),
start_date: z.string().date('Start date must be a valid date in YYYY-MM-DD format.'),
}),
});
const updateBudgetSchema = budgetIdParamSchema.extend({
body: createBudgetSchema.shape.body.partial().refine((data) => Object.keys(data).length > 0, {
message: 'At least one field to update must be provided.',
}),
});
const spendingAnalysisSchema = z.object({
query: z.object({
startDate: z.string().date('startDate must be a valid date in YYYY-MM-DD format.'),
endDate: z.string().date('endDate must be a valid date in YYYY-MM-DD format.'),
}),
});
// Middleware to ensure user is authenticated for all budget routes
router.use(passport.authenticate('jwt', { session: false }));
/**
* GET /api/budgets - Get all budgets for the authenticated user.
*/
router.get('/', async (req: Request, res: Response, next: NextFunction) => {
const userProfile = req.user as UserProfile;
try {
const budgets = await budgetRepo.getBudgetsForUser(userProfile.user.user_id, req.log);
res.json(budgets);
} catch (error) {
req.log.error({ error, userId: userProfile.user.user_id }, 'Error fetching budgets');
next(error);
}
});
/**
* POST /api/budgets - Create a new budget for the authenticated user.
*/
router.post(
'/',
validateRequest(createBudgetSchema),
async (req: Request, res: Response, next: NextFunction) => {
const userProfile = req.user as UserProfile;
type CreateBudgetRequest = z.infer<typeof createBudgetSchema>;
const { body } = req as unknown as CreateBudgetRequest;
try {
const newBudget = await budgetRepo.createBudget(userProfile.user.user_id, body, req.log);
res.status(201).json(newBudget);
} catch (error: unknown) {
req.log.error({ error, userId: userProfile.user.user_id, body }, 'Error creating budget');
next(error);
}
},
);
/**
* PUT /api/budgets/:id - Update an existing budget.
*/
router.put(
'/:id',
validateRequest(updateBudgetSchema),
async (req: Request, res: Response, next: NextFunction) => {
const userProfile = req.user as UserProfile;
type UpdateBudgetRequest = z.infer<typeof updateBudgetSchema>;
const { params, body } = req as unknown as UpdateBudgetRequest;
try {
const updatedBudget = await budgetRepo.updateBudget(
params.id,
userProfile.user.user_id,
body,
req.log,
);
res.json(updatedBudget);
} catch (error: unknown) {
req.log.error(
{ error, userId: userProfile.user.user_id, budgetId: params.id },
'Error updating budget',
);
next(error);
}
},
);
/**
* DELETE /api/budgets/:id - Delete a budget.
*/
router.delete(
'/:id',
validateRequest(budgetIdParamSchema),
async (req: Request, res: Response, next: NextFunction) => {
const userProfile = req.user as UserProfile;
type DeleteBudgetRequest = z.infer<typeof budgetIdParamSchema>;
const { params } = req as unknown as DeleteBudgetRequest;
try {
await budgetRepo.deleteBudget(params.id, userProfile.user.user_id, req.log);
res.status(204).send(); // No Content
} catch (error: unknown) {
req.log.error(
{ error, userId: userProfile.user.user_id, budgetId: params.id },
'Error deleting budget',
);
next(error);
}
},
);
/**
* GET /api/spending-analysis - Get spending breakdown by category for a date range.
* Query params: startDate (YYYY-MM-DD), endDate (YYYY-MM-DD)
*/
router.get(
'/spending-analysis',
validateRequest(spendingAnalysisSchema),
async (req: Request, res: Response, next: NextFunction) => {
const userProfile = req.user as UserProfile;
type SpendingAnalysisRequest = z.infer<typeof spendingAnalysisSchema>;
const {
query: { startDate, endDate },
} = req as unknown as SpendingAnalysisRequest;
try {
const spendingData = await budgetRepo.getSpendingByCategory(
userProfile.user.user_id,
startDate,
endDate,
req.log,
);
res.json(spendingData);
} catch (error) {
req.log.error(
{ error, userId: userProfile.user.user_id, startDate, endDate },
'Error fetching spending analysis',
);
next(error);
}
},
);
export default router;