more refactor
Some checks failed
Deploy to Test Environment / deploy-to-test (push) Has been cancelled
Some checks failed
Deploy to Test Environment / deploy-to-test (push) Has been cancelled
This commit is contained in:
@@ -23,7 +23,6 @@ const wrapper = ({ children }: { children: ReactNode }) => <UserDataProvider>{ch
|
||||
|
||||
// 4. Mock data for testing
|
||||
const mockUser: UserProfile = createMockUserProfile({
|
||||
user_id: 'user-123',
|
||||
full_name: 'Test User',
|
||||
points: 100,
|
||||
user: { user_id: 'user-123', email: 'test@example.com' },
|
||||
|
||||
@@ -30,7 +30,6 @@ const mockedApiClient = apiClient as Mocked<typeof apiClient>;
|
||||
|
||||
// --- Mock Data ---
|
||||
const mockProfile: UserProfile = createMockUserProfile({
|
||||
user_id: 'user-123',
|
||||
user: createMockUser({ user_id: 'user-123', email: 'test@example.com' }),
|
||||
full_name: 'Test User',
|
||||
avatar_url: 'http://example.com/avatar.jpg',
|
||||
|
||||
@@ -237,12 +237,12 @@ router.get('/unmatched-items', async (req, res, next: NextFunction) => {
|
||||
* DELETE /api/admin/recipes/:recipeId - Admin endpoint to delete any recipe.
|
||||
*/
|
||||
router.delete('/recipes/:recipeId', validateRequest(numericIdParamSchema('recipeId')), async (req: Request, res: Response, next: NextFunction) => {
|
||||
const adminUser = req.user as UserProfile;
|
||||
const userProfile = req.user as UserProfile;
|
||||
// Infer the type directly from the schema generator function. // This was a duplicate, fixed.
|
||||
const { params } = req as unknown as z.infer<ReturnType<typeof numericIdParamSchema>>;
|
||||
try {
|
||||
// The isAdmin flag bypasses the ownership check in the repository method.
|
||||
await db.recipeRepo.deleteRecipe(params.recipeId, adminUser.user.user_id, true, req.log);
|
||||
await db.recipeRepo.deleteRecipe(params.recipeId, userProfile.user.user_id, true, req.log);
|
||||
res.status(204).send();
|
||||
} catch (error: unknown) {
|
||||
next(error);
|
||||
@@ -323,11 +323,11 @@ router.put('/users/:id', validateRequest(updateUserRoleSchema), async (req: Requ
|
||||
});
|
||||
|
||||
router.delete('/users/:id', validateRequest(uuidParamSchema('id', 'A valid user ID is required.')), async (req: Request, res: Response, next: NextFunction) => {
|
||||
const adminUser = req.user as UserProfile;
|
||||
const userProfile = req.user as UserProfile;
|
||||
// Apply ADR-003 pattern for type safety
|
||||
const { params } = req as unknown as z.infer<ReturnType<typeof uuidParamSchema>>;
|
||||
try {
|
||||
if (adminUser.user.user_id === params.id) {
|
||||
if (userProfile.user.user_id === params.id) {
|
||||
throw new ValidationError([], 'Admins cannot delete their own account.');
|
||||
}
|
||||
await db.userRepo.deleteUserById(params.id, req.log);
|
||||
@@ -342,8 +342,8 @@ router.delete('/users/:id', validateRequest(uuidParamSchema('id', 'A valid user
|
||||
* This is useful for testing or forcing an update without waiting for the cron schedule.
|
||||
*/
|
||||
router.post('/trigger/daily-deal-check', async (req: Request, res: Response, next: NextFunction) => {
|
||||
const adminUser = req.user as UserProfile;
|
||||
logger.info(`[Admin] Manual trigger for daily deal check received from user: ${adminUser.user.user_id}`);
|
||||
const userProfile = req.user as UserProfile;
|
||||
logger.info(`[Admin] Manual trigger for daily deal check received from user: ${userProfile.user.user_id}`);
|
||||
|
||||
try {
|
||||
// We call the function but don't wait for it to finish (no `await`).
|
||||
@@ -361,8 +361,8 @@ router.post('/trigger/daily-deal-check', async (req: Request, res: Response, nex
|
||||
* This is useful for testing or re-generating a report without waiting for the cron schedule.
|
||||
*/
|
||||
router.post('/trigger/analytics-report', async (req: Request, res: Response, next: NextFunction) => {
|
||||
const adminUser = req.user as UserProfile;
|
||||
logger.info(`[Admin] Manual trigger for analytics report generation received from user: ${adminUser.user.user_id}`);
|
||||
const userProfile = req.user as UserProfile;
|
||||
logger.info(`[Admin] Manual trigger for analytics report generation received from user: ${userProfile.user.user_id}`);
|
||||
|
||||
try {
|
||||
const reportDate = new Date().toISOString().split('T')[0]; // YYYY-MM-DD
|
||||
@@ -383,10 +383,10 @@ router.post('/trigger/analytics-report', async (req: Request, res: Response, nex
|
||||
* This is triggered by an admin after they have verified the flyer processing was successful.
|
||||
*/
|
||||
router.post('/flyers/:flyerId/cleanup', validateRequest(numericIdParamSchema('flyerId')), async (req: Request, res: Response, next: NextFunction) => {
|
||||
const adminUser = req.user as UserProfile;
|
||||
const userProfile = req.user as UserProfile;
|
||||
// Infer type from the schema generator for type safety, as per ADR-003.
|
||||
const { params } = req as unknown as z.infer<ReturnType<typeof numericIdParamSchema>>; // This was a duplicate, fixed.
|
||||
logger.info(`[Admin] Manual trigger for flyer file cleanup received from user: ${adminUser.user.user_id} for flyer ID: ${params.flyerId}`);
|
||||
logger.info(`[Admin] Manual trigger for flyer file cleanup received from user: ${userProfile.user.user_id} for flyer ID: ${params.flyerId}`);
|
||||
|
||||
// Enqueue the cleanup job. The worker will handle the file deletion.
|
||||
try {
|
||||
@@ -402,8 +402,8 @@ router.post('/flyers/:flyerId/cleanup', validateRequest(numericIdParamSchema('fl
|
||||
* This is for testing the retry mechanism and Bull Board UI.
|
||||
*/
|
||||
router.post('/trigger/failing-job', async (req: Request, res: Response, next: NextFunction) => {
|
||||
const adminUser = req.user as UserProfile;
|
||||
logger.info(`[Admin] Manual trigger for a failing job received from user: ${adminUser.user.user_id}`);
|
||||
const userProfile = req.user as UserProfile;
|
||||
logger.info(`[Admin] Manual trigger for a failing job received from user: ${userProfile.user.user_id}`);
|
||||
|
||||
try {
|
||||
// Add a job with a special 'forceFail' flag that the worker will recognize.
|
||||
@@ -419,8 +419,8 @@ router.post('/trigger/failing-job', async (req: Request, res: Response, next: Ne
|
||||
* Requires admin privileges.
|
||||
*/
|
||||
router.post('/system/clear-geocode-cache', async (req: Request, res: Response, next: NextFunction) => {
|
||||
const adminUser = req.user as UserProfile;
|
||||
logger.info(`[Admin] Manual trigger for geocode cache clear received from user: ${adminUser.user.user_id}`);
|
||||
const userProfile = req.user as UserProfile;
|
||||
logger.info(`[Admin] Manual trigger for geocode cache clear received from user: ${userProfile.user.user_id}`);
|
||||
|
||||
try {
|
||||
const keysDeleted = await geocodingService.clearGeocodeCache(req.log);
|
||||
@@ -476,7 +476,7 @@ router.get('/queues/status', async (req: Request, res: Response, next: NextFunct
|
||||
* POST /api/admin/jobs/:queueName/:jobId/retry - Retries a specific failed job.
|
||||
*/
|
||||
router.post('/jobs/:queueName/:jobId/retry', validateRequest(jobRetrySchema), async (req: Request, res: Response, next: NextFunction) => {
|
||||
const adminUser = req.user as UserProfile;
|
||||
const userProfile = req.user as UserProfile;
|
||||
const { params: { queueName, jobId } } = req as unknown as z.infer<typeof jobRetrySchema>;
|
||||
|
||||
const queueMap: { [key: string]: Queue } = {
|
||||
@@ -501,7 +501,7 @@ router.post('/jobs/:queueName/:jobId/retry', validateRequest(jobRetrySchema), as
|
||||
if (jobState !== 'failed') throw new ValidationError([], `Job is not in a 'failed' state. Current state: ${jobState}.`); // This was a duplicate, fixed.
|
||||
|
||||
await job.retry();
|
||||
logger.info(`[Admin] User ${adminUser.user.user_id} manually retried job ${jobId} in queue ${queueName}.`);
|
||||
logger.info(`[Admin] User ${userProfile.user.user_id} manually retried job ${jobId} in queue ${queueName}.`);
|
||||
res.status(200).json({ message: `Job ${jobId} has been successfully marked for retry.` });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
@@ -512,8 +512,8 @@ router.post('/jobs/:queueName/:jobId/retry', validateRequest(jobRetrySchema), as
|
||||
* POST /api/admin/trigger/weekly-analytics - Manually trigger the weekly analytics report job.
|
||||
*/
|
||||
router.post('/trigger/weekly-analytics', async (req: Request, res: Response, next: NextFunction) => {
|
||||
const adminUser = req.user as UserProfile; // This was a duplicate, fixed.
|
||||
logger.info(`[Admin] Manual trigger for weekly analytics report received from user: ${adminUser.user.user_id}`);
|
||||
const userProfile = req.user as UserProfile; // This was a duplicate, fixed.
|
||||
logger.info(`[Admin] Manual trigger for weekly analytics report received from user: ${userProfile.user.user_id}`);
|
||||
|
||||
try {
|
||||
const { year: reportYear, week: reportWeek } = getSimpleWeekAndYear();
|
||||
|
||||
@@ -79,7 +79,7 @@ describe('Admin Stats Routes (/api/admin/stats)', () => {
|
||||
|
||||
describe('GET /stats', () => {
|
||||
it('should return application stats on success', async () => {
|
||||
const mockStats = { flyerCount: 150, userCount: 42, flyerItemCount: 10000, storeCount: 12, pendingCorrectionCount: 5 };
|
||||
const mockStats = { flyerCount: 150, userCount: 42, flyerItemCount: 10000, storeCount: 12, pendingCorrectionCount: 5, recipeCount: 50 };
|
||||
vi.mocked(adminRepo.getApplicationStats).mockResolvedValue(mockStats);
|
||||
const response = await supertest(app).get('/api/admin/stats');
|
||||
expect(response.status).toBe(200);
|
||||
|
||||
@@ -48,12 +48,12 @@ 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 user = req.user as UserProfile;
|
||||
const userProfile = req.user as UserProfile;
|
||||
try {
|
||||
const budgets = await budgetRepo.getBudgetsForUser(user.user_id, req.log);
|
||||
const budgets = await budgetRepo.getBudgetsForUser(userProfile.user.user_id, req.log);
|
||||
res.json(budgets);
|
||||
} catch (error) {
|
||||
req.log.error({ error, userId: user.user_id }, 'Error fetching budgets');
|
||||
req.log.error({ error, userId: userProfile.user.user_id }, 'Error fetching budgets');
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
@@ -62,14 +62,14 @@ router.get('/', async (req: Request, res: Response, next: NextFunction) => {
|
||||
* POST /api/budgets - Create a new budget for the authenticated user.
|
||||
*/
|
||||
router.post('/', validateRequest(createBudgetSchema), async (req: Request, res: Response, next: NextFunction) => {
|
||||
const user = req.user as UserProfile;
|
||||
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(user.user_id, body, req.log);
|
||||
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: user.user_id, body }, 'Error creating budget');
|
||||
req.log.error({ error, userId: userProfile.user.user_id, body }, 'Error creating budget');
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
@@ -78,14 +78,14 @@ router.post('/', validateRequest(createBudgetSchema), async (req: Request, res:
|
||||
* PUT /api/budgets/:id - Update an existing budget.
|
||||
*/
|
||||
router.put('/:id', validateRequest(updateBudgetSchema), async (req: Request, res: Response, next: NextFunction) => {
|
||||
const user = req.user as UserProfile;
|
||||
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, user.user_id, body, req.log);
|
||||
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: user.user_id, budgetId: params.id }, 'Error updating budget');
|
||||
req.log.error({ error, userId: userProfile.user.user_id, budgetId: params.id }, 'Error updating budget');
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
@@ -94,14 +94,14 @@ router.put('/:id', validateRequest(updateBudgetSchema), async (req: Request, res
|
||||
* DELETE /api/budgets/:id - Delete a budget.
|
||||
*/
|
||||
router.delete('/:id', validateRequest(budgetIdParamSchema), async (req: Request, res: Response, next: NextFunction) => {
|
||||
const user = req.user as UserProfile;
|
||||
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, user.user_id, req.log);
|
||||
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: user.user_id, budgetId: params.id }, 'Error deleting budget');
|
||||
req.log.error({ error, userId: userProfile.user.user_id, budgetId: params.id }, 'Error deleting budget');
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
@@ -111,15 +111,15 @@ router.delete('/:id', validateRequest(budgetIdParamSchema), async (req: Request,
|
||||
* Query params: startDate (YYYY-MM-DD), endDate (YYYY-MM-DD)
|
||||
*/
|
||||
router.get('/spending-analysis', validateRequest(spendingAnalysisSchema), async (req: Request, res: Response, next: NextFunction) => {
|
||||
const user = req.user as UserProfile;
|
||||
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(user.user_id, startDate, endDate, req.log);
|
||||
const spendingData = await budgetRepo.getSpendingByCategory(userProfile.user.user_id, startDate, endDate, req.log);
|
||||
res.json(spendingData);
|
||||
} catch (error) {
|
||||
req.log.error({ error, userId: user.user_id, startDate, endDate }, 'Error fetching spending analysis');
|
||||
req.log.error({ error, userId: userProfile.user.user_id, startDate, endDate }, 'Error fetching spending analysis');
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -26,11 +26,11 @@ router.use(passport.authenticate('jwt', { session: false }));
|
||||
* @access Private
|
||||
*/
|
||||
router.get('/best-watched-prices', validateRequest(bestWatchedPricesSchema), async (req: Request, res: Response, next: NextFunction) => {
|
||||
const user = req.user as UserProfile;
|
||||
const userProfile = 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);
|
||||
const deals = await dealsRepo.findBestPricesForWatchedItems(userProfile.user.user_id, req.log);
|
||||
req.log.info({ dealCount: deals.length }, 'Successfully fetched best watched item deals.');
|
||||
res.status(200).json(deals);
|
||||
} catch (error) {
|
||||
|
||||
@@ -75,12 +75,12 @@ router.get(
|
||||
'/me',
|
||||
passport.authenticate('jwt', { session: false }),
|
||||
async (req, res, next: NextFunction): Promise<void> => {
|
||||
const user = req.user as UserProfile;
|
||||
const userProfile = req.user as UserProfile;
|
||||
try {
|
||||
const userAchievements = await gamificationRepo.getUserAchievements(user.user_id, req.log);
|
||||
const userAchievements = await gamificationRepo.getUserAchievements(userProfile.user.user_id, req.log);
|
||||
res.json(userAchievements);
|
||||
} catch (error) {
|
||||
logger.error({ error, userId: user.user_id }, 'Error fetching user achievements:');
|
||||
logger.error({ error, userId: userProfile.user.user_id }, 'Error fetching user achievements:');
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -484,7 +484,7 @@ describe('Admin DB Service', () => {
|
||||
|
||||
describe('updateUserRole', () => {
|
||||
it('should update the user role and return the updated user', async () => {
|
||||
const mockProfile: Profile = createMockProfile({ user_id: '1', role: 'admin' });
|
||||
const mockProfile: Profile = createMockProfile({ role: 'admin' });
|
||||
mockPoolInstance.query.mockResolvedValue({ rows: [mockProfile], rowCount: 1 });
|
||||
const result = await adminRepo.updateUserRole('1', 'admin', mockLogger);
|
||||
expect(mockPoolInstance.query).toHaveBeenCalledWith('UPDATE public.profiles SET role = $1 WHERE user_id = $2 RETURNING *', ['admin', '1']);
|
||||
|
||||
Reference in New Issue
Block a user