feat: Enhance logging and type safety across various components and services
Some checks failed
Deploy to Test Environment / deploy-to-test (push) Has been cancelled

This commit is contained in:
2025-12-13 23:12:50 -08:00
parent 77454b04c2
commit 7615d7746e
16 changed files with 96 additions and 61 deletions

View File

@@ -4,21 +4,19 @@ import passport from './passport.routes';
import { isAdmin } from './passport.routes'; // Correctly imported
import multer from 'multer';// --- Zod Schemas for Admin Routes (as per ADR-003) ---
import { z } from 'zod';
import crypto from 'crypto';
import * as db from '../services/db/index.db';
import { logger } from '../services/logger.server';
import { UserProfile } from '../types';
import { clearGeocodeCache } from '../services/geocodingService.server';
import { geocodingService } from '../services/geocodingService.server';
import { requireFileUpload } from '../middleware/fileUpload.middleware'; // This was a duplicate, fixed.
import { ForeignKeyConstraintError, NotFoundError, ValidationError } from '../services/db/errors.db';
import { NotFoundError, ValidationError } from '../services/db/errors.db';
import { validateRequest } from '../middleware/validation.middleware';
// --- Bull Board (Job Queue UI) Imports ---
import { createBullBoard } from '@bull-board/api';
import { BullMQAdapter } from '@bull-board/api/bullMQAdapter';
import { ExpressAdapter } from '@bull-board/express';
import { render, screen } from '@testing-library/react';
import type { Queue } from 'bullmq';
import { backgroundJobService } from '../services/backgroundJobService';
@@ -108,7 +106,7 @@ router.use(passport.authenticate('jwt', { session: false }), isAdmin);
router.get('/corrections', async (req, res, next: NextFunction) => {
try {
const corrections = await db.adminRepo.getSuggestedCorrections();
const corrections = await db.adminRepo.getSuggestedCorrections(req.log);
res.json(corrections);
} catch (error) {
next(error);
@@ -117,7 +115,7 @@ router.get('/corrections', async (req, res, next: NextFunction) => {
router.get('/brands', async (req, res, next: NextFunction) => {
try {
const brands = await db.flyerRepo.getAllBrands();
const brands = await db.flyerRepo.getAllBrands(req.log);
res.json(brands);
} catch (error) {
next(error);
@@ -126,7 +124,7 @@ router.get('/brands', async (req, res, next: NextFunction) => {
router.get('/stats', async (req, res, next: NextFunction) => {
try {
const stats = await db.adminRepo.getApplicationStats();
const stats = await db.adminRepo.getApplicationStats(req.log);
res.json(stats);
} catch (error) {
next(error);
@@ -135,7 +133,7 @@ router.get('/stats', async (req, res, next: NextFunction) => {
router.get('/stats/daily', async (req, res, next: NextFunction) => {
try {
const dailyStats = await db.adminRepo.getDailyStatsForLast30Days();
const dailyStats = await db.adminRepo.getDailyStatsForLast30Days(req.log);
res.json(dailyStats);
} catch (error) {
next(error);
@@ -145,7 +143,7 @@ router.get('/stats/daily', async (req, res, next: NextFunction) => {
router.post('/corrections/:id/approve', validateRequest(numericIdParamSchema('id')), async (req, res, next: NextFunction) => {
try {
const correctionId = req.params.id as unknown as number;
await db.adminRepo.approveCorrection(correctionId);
await db.adminRepo.approveCorrection(correctionId, req.log);
res.status(200).json({ message: 'Correction approved successfully.' });
} catch (error) {
next(error);
@@ -155,7 +153,7 @@ router.post('/corrections/:id/approve', validateRequest(numericIdParamSchema('id
router.post('/corrections/:id/reject', validateRequest(numericIdParamSchema('id')), async (req, res, next: NextFunction) => {
try {
const correctionId = req.params.id as unknown as number;
await db.adminRepo.rejectCorrection(correctionId);
await db.adminRepo.rejectCorrection(correctionId, req.log);
res.status(200).json({ message: 'Correction rejected successfully.' });
} catch (error) {
next(error);
@@ -166,7 +164,7 @@ router.put('/corrections/:id', validateRequest(updateCorrectionSchema), async (r
const correctionId = req.params.id as unknown as number;
const { suggested_value } = req.body;
try {
const updatedCorrection = await db.adminRepo.updateSuggestedCorrection(correctionId, suggested_value);
const updatedCorrection = await db.adminRepo.updateSuggestedCorrection(correctionId, suggested_value, req.log);
res.status(200).json(updatedCorrection);
} catch (error) {
next(error);
@@ -177,7 +175,7 @@ router.put('/recipes/:id/status', validateRequest(updateRecipeStatusSchema), asy
const recipeId = req.params.id as unknown as number;
const { status } = req.body;
try {
const updatedRecipe = await db.adminRepo.updateRecipeStatus(recipeId, status); // This is still a standalone function in admin.db.ts
const updatedRecipe = await db.adminRepo.updateRecipeStatus(recipeId, status, req.log); // This is still a standalone function in admin.db.ts
res.status(200).json(updatedRecipe);
} catch (error) {
next(error); // Pass all errors to the central error handler
@@ -193,9 +191,9 @@ router.post('/brands/:id/logo', validateRequest(numericIdParamSchema('id')), upl
throw new ValidationError([], 'Logo image file is missing.');
}
const logoUrl = `/assets/${req.file.filename}`;
await db.adminRepo.updateBrandLogo(brandId, logoUrl);
await db.adminRepo.updateBrandLogo(brandId, logoUrl, req.log);
logger.info(`Brand logo updated for brand ID: ${brandId}`, { brandId, logoUrl });
logger.info({ brandId, logoUrl }, `Brand logo updated for brand ID: ${brandId}`);
res.status(200).json({ message: 'Brand logo updated successfully.', logoUrl });
} catch (error) {
next(error);
@@ -204,7 +202,7 @@ router.post('/brands/:id/logo', validateRequest(numericIdParamSchema('id')), upl
router.get('/unmatched-items', async (req, res, next: NextFunction) => {
try {
const items = await db.adminRepo.getUnmatchedFlyerItems();
const items = await db.adminRepo.getUnmatchedFlyerItems(req.log);
res.json(items);
} catch (error) {
next(error);
@@ -220,7 +218,7 @@ router.delete('/recipes/:recipeId', validateRequest(numericIdParamSchema('recipe
try {
// The isAdmin flag bypasses the ownership check in the repository method.
await db.recipeRepo.deleteRecipe(recipeId, adminUser.user_id, true);
await db.recipeRepo.deleteRecipe(recipeId, adminUser.user_id, true, req.log);
res.status(204).send();
} catch (error: unknown) {
next(error);
@@ -233,7 +231,7 @@ router.delete('/recipes/:recipeId', validateRequest(numericIdParamSchema('recipe
router.delete('/flyers/:flyerId', validateRequest(numericIdParamSchema('flyerId')), async (req, res, next: NextFunction) => {
const flyerId = req.params.flyerId as unknown as number;
try {
await db.flyerRepo.deleteFlyer(flyerId);
await db.flyerRepo.deleteFlyer(flyerId, req.log);
res.status(204).send();
} catch (error: unknown) {
next(error);
@@ -244,7 +242,7 @@ router.put('/comments/:id/status', validateRequest(updateCommentStatusSchema), a
const commentId = req.params.id as unknown as number;
const { status } = req.body;
try {
const updatedComment = await db.adminRepo.updateRecipeCommentStatus(commentId, status); // This is still a standalone function in admin.db.ts
const updatedComment = await db.adminRepo.updateRecipeCommentStatus(commentId, status, req.log); // This is still a standalone function in admin.db.ts
res.status(200).json(updatedComment);
} catch (error: unknown) {
next(error);
@@ -253,7 +251,7 @@ router.put('/comments/:id/status', validateRequest(updateCommentStatusSchema), a
router.get('/users', async (req, res, next: NextFunction) => {
try {
const users = await db.adminRepo.getAllUsers();
const users = await db.adminRepo.getAllUsers(req.log);
res.json(users);
} catch (error) {
next(error);
@@ -263,7 +261,7 @@ router.get('/users', async (req, res, next: NextFunction) => {
router.get('/activity-log', validateRequest(activityLogSchema), async (req, res, next: NextFunction) => {
const { limit, offset } = req.query as unknown as { limit: number; offset: number };
try {
const logs = await db.adminRepo.getActivityLog(limit, offset);
const logs = await db.adminRepo.getActivityLog(limit, offset, req.log);
res.json(logs);
} catch (error) {
next(error);
@@ -272,7 +270,7 @@ router.get('/activity-log', validateRequest(activityLogSchema), async (req, res,
router.get('/users/:id', validateRequest(z.object({ params: z.object({ id: z.string().uuid() }) })), async (req, res, next: NextFunction) => {
try {
const user = await db.userRepo.findUserProfileById(req.params.id);
const user = await db.userRepo.findUserProfileById(req.params.id, req.log);
res.json(user);
} catch (error) {
next(error);
@@ -283,10 +281,10 @@ router.put('/users/:id', validateRequest(updateUserRoleSchema), async (req, res,
const { role } = req.body;
try {
const updatedUser = await db.adminRepo.updateUserRole(req.params.id, role);
const updatedUser = await db.adminRepo.updateUserRole(req.params.id, role, req.log);
res.json(updatedUser);
} catch (error) {
logger.error(`Error updating user ${req.params.id}:`, { error });
logger.error({ error }, `Error updating user ${req.params.id}:`);
next(error);
}
});
@@ -297,7 +295,7 @@ router.delete('/users/:id', validateRequest(z.object({ params: z.object({ id: z.
if (adminUser.user.user_id === req.params.id) {
throw new ValidationError([], 'Admins cannot delete their own account.');
}
await db.userRepo.deleteUserById(req.params.id);
await db.userRepo.deleteUserById(req.params.id, req.log);
res.status(204).send();
} catch (error) {
next(error);
@@ -318,7 +316,7 @@ router.post('/trigger/daily-deal-check', async (req, res, next: NextFunction) =>
backgroundJobService.runDailyDealCheck();
res.status(202).json({ message: 'Daily deal check job has been triggered successfully. It will run in the background.' });
} catch (error) {
logger.error('[Admin] Failed to trigger daily deal check job.', { error });
logger.error({ error }, '[Admin] Failed to trigger daily deal check job.');
next(error);
}
});
@@ -340,7 +338,7 @@ router.post('/trigger/analytics-report', async (req, res, next: NextFunction) =>
res.status(202).json({ message: `Analytics report generation job has been enqueued successfully. Job ID: ${job.id}` });
} catch (error) {
logger.error('[Admin] Failed to enqueue analytics report job.', { error });
logger.error({ error }, '[Admin] Failed to enqueue analytics report job.');
next(error);
}
});
@@ -390,10 +388,10 @@ router.post('/system/clear-geocode-cache', async (req, res, next: NextFunction)
logger.info(`[Admin] Manual trigger for geocode cache clear received from user: ${adminUser.user_id}`);
try {
const keysDeleted = await clearGeocodeCache();
const keysDeleted = await geocodingService.clearGeocodeCache(req.log);
res.status(200).json({ message: `Successfully cleared the geocode cache. ${keysDeleted} keys were removed.` });
} catch (error) {
logger.error('[Admin] Failed to clear geocode cache.', { error });
logger.error({ error }, '[Admin] Failed to clear geocode cache.');
next(error);
}
});