feat: Implement deals repository and routes for fetching best watched item prices
Some checks failed
Deploy to Test Environment / deploy-to-test (push) Has been cancelled

- Added a new DealsRepository class to interact with the database for fetching the best sale prices of watched items.
- Created a new route `/api/users/deals/best-watched-prices` to handle requests for the best prices of items the authenticated user is watching.
- Enhanced logging in the FlyerDataTransformer and FlyerProcessingService for better traceability.
- Updated tests to ensure proper logging and functionality in the FlyerProcessingService.
- Refactored logger client to support structured logging for better consistency across the application.
This commit is contained in:
2025-12-13 20:02:18 -08:00
parent 2affda25dc
commit 424cbaf0d4
22 changed files with 409 additions and 241 deletions

View File

@@ -4,8 +4,7 @@ import { z } from 'zod';
import passport from './passport.routes';
import { budgetRepo } from '../services/db/index.db';
import { logger } from '../services/logger.server';
import { UserProfile } from '../types';
import { ForeignKeyConstraintError } from '../services/db/errors.db';
import type { UserProfile } from '../types';
import { validateRequest } from '../middleware/validation.middleware';
const router = express.Router();
@@ -49,10 +48,10 @@ router.use(passport.authenticate('jwt', { session: false }));
router.get('/', async (req, res, next: NextFunction) => {
const user = req.user as UserProfile;
try {
const budgets = await budgetRepo.getBudgetsForUser(user.user_id);
const budgets = await budgetRepo.getBudgetsForUser(user.user_id, req.log);
res.json(budgets);
} catch (error) {
logger.error('Error fetching budgets:', { error, userId: user.user_id });
req.log.error({ err: error, userId: user.user_id }, 'Error fetching budgets');
next(error);
}
});
@@ -63,10 +62,10 @@ router.get('/', async (req, res, next: NextFunction) => {
router.post('/', validateRequest(createBudgetSchema), async (req, res, next: NextFunction) => {
const user = req.user as UserProfile;
try {
const newBudget = await budgetRepo.createBudget(user.user_id, req.body);
const newBudget = await budgetRepo.createBudget(user.user_id, req.body, req.log);
res.status(201).json(newBudget);
} catch (error: unknown) {
logger.error('Error creating budget:', { error, userId: user.user_id, body: req.body });
req.log.error({ err: error, userId: user.user_id, body: req.body }, 'Error creating budget');
next(error);
}
});
@@ -78,10 +77,10 @@ router.put('/:id', validateRequest(updateBudgetSchema), async (req, res, next: N
const user = req.user as UserProfile;
const budgetId = req.params.id as unknown as number;
try {
const updatedBudget = await budgetRepo.updateBudget(budgetId, user.user_id, req.body);
const updatedBudget = await budgetRepo.updateBudget(budgetId, user.user_id, req.body, req.log);
res.json(updatedBudget);
} catch (error: unknown) {
logger.error('Error updating budget:', { error, userId: user.user_id, budgetId });
req.log.error({ err: error, userId: user.user_id, budgetId }, 'Error updating budget');
next(error);
}
});
@@ -93,10 +92,10 @@ router.delete('/:id', validateRequest(budgetIdParamSchema), async (req, res, nex
const user = req.user as UserProfile;
const budgetId = req.params.id as unknown as number;
try {
await budgetRepo.deleteBudget(budgetId, user.user_id);
await budgetRepo.deleteBudget(budgetId, user.user_id, req.log);
res.status(204).send(); // No Content
} catch (error: unknown) {
logger.error('Error deleting budget:', { error, userId: user.user_id, budgetId });
req.log.error({ err: error, userId: user.user_id, budgetId }, 'Error deleting budget');
next(error);
}
});
@@ -110,10 +109,10 @@ router.get('/spending-analysis', validateRequest(spendingAnalysisSchema), async
const { startDate, endDate } = req.query;
try {
const spendingData = await budgetRepo.getSpendingByCategory(user.user_id, startDate as string, endDate as string);
const spendingData = await budgetRepo.getSpendingByCategory(user.user_id, startDate as string, endDate as string, req.log);
res.json(spendingData);
} catch (error) {
logger.error('Error fetching spending analysis:', { error, userId: user.user_id, startDate, endDate });
req.log.error({ err: error, userId: user.user_id, startDate, endDate }, 'Error fetching spending analysis');
next(error);
}
});

View File

@@ -0,0 +1,34 @@
// src/routes/deals.routes.ts
import express, { type Request, type Response, type NextFunction } from 'express';
import passport from './passport.routes';
import { dealsRepo } from '../services/db/deals.db';
import type { UserProfile } from '../types';
const router = express.Router();
// --- Middleware for all deal routes ---
// Per ADR-002, all routes in this file require an authenticated user.
// We apply the standard passport JWT middleware at the router level.
router.use(passport.authenticate('jwt', { session: false }));
/**
* @route GET /api/users/deals/best-watched-prices
* @description Fetches the best current sale price for each of the authenticated user's watched items.
* @access Private
*/
router.get('/best-watched-prices', async (req: Request, res: Response, next: NextFunction) => {
const user = req.user as UserProfile;
try {
// The controller logic is simple enough to be handled directly in the route,
// consistent with other simple GET routes in the project.
const deals = await dealsRepo.findBestPricesForWatchedItems(user.user_id);
req.log.info({ dealCount: deals.length, userId: user.user_id }, 'Successfully fetched best watched item deals.');
res.status(200).json(deals);
} catch (error) {
req.log.error({ err: error, userId: user.user_id }, 'Error fetching best watched item deals.');
next(error); // Pass errors to the global error handler
}
});
export default router;