DB refactor for easier testsing
Some checks failed
Deploy to Web Server flyer-crawler.projectium.com / deploy (push) Failing after 5m16s
Some checks failed
Deploy to Web Server flyer-crawler.projectium.com / deploy (push) Failing after 5m16s
App.ts refactor into hooks unit tests
This commit is contained in:
@@ -8,14 +8,16 @@ 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 { ForeignKeyConstraintError } from '../services/db/errors.db';
|
||||
|
||||
// --- 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 type { Queue } from 'bullmq';
|
||||
import { backgroundJobService } from '../services/backgroundJobService';
|
||||
import { backgroundJobService } from '../services/backgroundJobService';
|
||||
import { flyerQueue, emailQueue, analyticsQueue, cleanupQueue, flyerWorker, emailWorker, analyticsWorker, cleanupWorker } from '../services/queueService.server'; // Import your queues
|
||||
import { getSimpleWeekAndYear } from '../utils/dateUtils';
|
||||
|
||||
const router = Router();
|
||||
|
||||
@@ -41,7 +43,8 @@ createBullBoard({
|
||||
new BullMQAdapter(flyerQueue),
|
||||
new BullMQAdapter(emailQueue),
|
||||
new BullMQAdapter(analyticsQueue),
|
||||
new BullMQAdapter(cleanupQueue) // Add the new cleanup queue here
|
||||
new BullMQAdapter(cleanupQueue), // Add the new cleanup queue here
|
||||
new BullMQAdapter((await import('../services/queueService.server')).weeklyAnalyticsQueue),
|
||||
],
|
||||
serverAdapter: serverAdapter,
|
||||
});
|
||||
@@ -58,7 +61,7 @@ router.use(passport.authenticate('jwt', { session: false }), isAdmin);
|
||||
|
||||
router.get('/corrections', async (req, res, next: NextFunction) => {
|
||||
try {
|
||||
const corrections = await db.getSuggestedCorrections();
|
||||
const corrections = await db.adminRepo.getSuggestedCorrections();
|
||||
res.json(corrections);
|
||||
} catch (error) {
|
||||
next(error);
|
||||
@@ -67,7 +70,7 @@ router.get('/corrections', async (req, res, next: NextFunction) => {
|
||||
|
||||
router.get('/brands', async (req, res, next: NextFunction) => {
|
||||
try {
|
||||
const brands = await db.getAllBrands();
|
||||
const brands = await db.flyerRepo.getAllBrands();
|
||||
res.json(brands);
|
||||
} catch (error) {
|
||||
next(error);
|
||||
@@ -76,7 +79,7 @@ router.get('/brands', async (req, res, next: NextFunction) => {
|
||||
|
||||
router.get('/stats', async (req, res, next: NextFunction) => {
|
||||
try {
|
||||
const stats = await db.getApplicationStats();
|
||||
const stats = await db.adminRepo.getApplicationStats();
|
||||
res.json(stats);
|
||||
} catch (error) {
|
||||
next(error);
|
||||
@@ -85,7 +88,7 @@ router.get('/stats', async (req, res, next: NextFunction) => {
|
||||
|
||||
router.get('/stats/daily', async (req, res, next: NextFunction) => {
|
||||
try {
|
||||
const dailyStats = await db.getDailyStatsForLast30Days();
|
||||
const dailyStats = await db.adminRepo.getDailyStatsForLast30Days();
|
||||
res.json(dailyStats);
|
||||
} catch (error) {
|
||||
next(error);
|
||||
@@ -99,7 +102,7 @@ router.post('/corrections/:id/approve', async (req, res, next: NextFunction) =>
|
||||
return res.status(400).json({ message: 'Invalid correction ID provided.' });
|
||||
}
|
||||
try {
|
||||
await db.approveCorrection(correctionId);
|
||||
await db.adminRepo.approveCorrection(correctionId);
|
||||
res.status(200).json({ message: 'Correction approved successfully.' });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
@@ -109,7 +112,7 @@ router.post('/corrections/:id/approve', async (req, res, next: NextFunction) =>
|
||||
router.post('/corrections/:id/reject', async (req, res, next: NextFunction) => {
|
||||
try {
|
||||
const correctionId = parseInt(req.params.id, 10);
|
||||
await db.rejectCorrection(correctionId);
|
||||
await db.adminRepo.rejectCorrection(correctionId);
|
||||
res.status(200).json({ message: 'Correction rejected successfully.' });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
@@ -123,10 +126,9 @@ router.put('/corrections/:id', async (req, res, next: NextFunction) => {
|
||||
return res.status(400).json({ message: 'A new suggested_value is required.' });
|
||||
}
|
||||
try {
|
||||
const updatedCorrection = await db.updateSuggestedCorrection(correctionId, suggested_value);
|
||||
const updatedCorrection = await db.adminRepo.updateSuggestedCorrection(correctionId, suggested_value);
|
||||
res.status(200).json(updatedCorrection);
|
||||
} catch (error) {
|
||||
// Check if the error message indicates "not found" to return a 404
|
||||
if (error instanceof Error && error.message.includes('not found')) {
|
||||
return res.status(404).json({ message: error.message });
|
||||
}
|
||||
@@ -142,9 +144,12 @@ router.put('/recipes/:id/status', async (req, res, next: NextFunction) => {
|
||||
return res.status(400).json({ message: 'A valid status (private, pending_review, public, rejected) is required.' });
|
||||
}
|
||||
try {
|
||||
const updatedRecipe = await db.updateRecipeStatus(recipeId, status);
|
||||
const updatedRecipe = await db.adminRepo.updateRecipeStatus(recipeId, status); // This is still a standalone function in admin.db.ts
|
||||
res.status(200).json(updatedRecipe);
|
||||
} catch (error) {
|
||||
if (error instanceof Error && error.message.includes('not found')) {
|
||||
return res.status(404).json({ message: error.message });
|
||||
}
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
@@ -157,7 +162,7 @@ router.post('/brands/:id/logo', upload.single('logoImage'), async (req, res, nex
|
||||
|
||||
try {
|
||||
const logoUrl = `/assets/${req.file.filename}`;
|
||||
await db.updateBrandLogo(brandId, logoUrl);
|
||||
await db.adminRepo.updateBrandLogo(brandId, logoUrl);
|
||||
|
||||
logger.info(`Brand logo updated for brand ID: ${brandId}`, { brandId, logoUrl });
|
||||
res.status(200).json({ message: 'Brand logo updated successfully.', logoUrl });
|
||||
@@ -168,7 +173,7 @@ router.post('/brands/:id/logo', upload.single('logoImage'), async (req, res, nex
|
||||
|
||||
router.get('/unmatched-items', async (req, res, next: NextFunction) => {
|
||||
try {
|
||||
const items = await db.getUnmatchedFlyerItems();
|
||||
const items = await db.adminRepo.getUnmatchedFlyerItems();
|
||||
res.json(items);
|
||||
} catch (error) {
|
||||
next(error);
|
||||
@@ -183,16 +188,19 @@ router.put('/comments/:id/status', async (req, res, next: NextFunction) => {
|
||||
return res.status(400).json({ message: 'A valid status (visible, hidden, reported) is required.' });
|
||||
}
|
||||
try {
|
||||
const updatedComment = await db.updateRecipeCommentStatus(commentId, status);
|
||||
const updatedComment = await db.adminRepo.updateRecipeCommentStatus(commentId, status); // This is still a standalone function in admin.db.ts
|
||||
res.status(200).json(updatedComment);
|
||||
} catch (error) {
|
||||
if (error instanceof Error && error.message.includes('not found')) {
|
||||
return res.status(404).json({ message: error.message });
|
||||
}
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/users', async (req, res, next: NextFunction) => {
|
||||
try {
|
||||
const users = await db.getAllUsers();
|
||||
const users = await db.adminRepo.getAllUsers();
|
||||
res.json(users);
|
||||
} catch (error) {
|
||||
next(error);
|
||||
@@ -203,7 +211,7 @@ router.get('/activity-log', async (req, res, next: NextFunction) => {
|
||||
const limit = parseInt(req.query.limit as string, 10) || 50;
|
||||
const offset = parseInt(req.query.offset as string, 10) || 0;
|
||||
try {
|
||||
const logs = await db.getActivityLog(limit, offset);
|
||||
const logs = await db.adminRepo.getActivityLog(limit, offset);
|
||||
res.json(logs);
|
||||
} catch (error) {
|
||||
next(error);
|
||||
@@ -212,7 +220,7 @@ router.get('/activity-log', async (req, res, next: NextFunction) => {
|
||||
|
||||
router.get('/users/:id', async (req, res, next: NextFunction) => {
|
||||
try {
|
||||
const user = await db.findUserProfileById(req.params.id);
|
||||
const user = await db.userRepo.findUserProfileById(req.params.id);
|
||||
if (!user) {
|
||||
return res.status(404).json({ message: 'User not found.' });
|
||||
}
|
||||
@@ -229,11 +237,12 @@ router.put('/users/:id', async (req, res, next: NextFunction) => {
|
||||
}
|
||||
|
||||
try {
|
||||
const updatedUser = await db.updateUserRole(req.params.id, role);
|
||||
const updatedUser = await db.adminRepo.updateUserRole(req.params.id, role);
|
||||
res.json(updatedUser);
|
||||
} catch (error) {
|
||||
// Check if the error message indicates "not found" to return a 404
|
||||
if (error instanceof Error && error.message.includes('not found')) {
|
||||
if (error instanceof ForeignKeyConstraintError) { // This error is thrown by the DB layer
|
||||
return res.status(404).json({ message: `User with ID ${req.params.id} not found.` });
|
||||
} else if (error instanceof Error && error.message.includes('not found')) { // This handles the generic "not found" from the repo
|
||||
return res.status(404).json({ message: error.message });
|
||||
}
|
||||
logger.error(`Error updating user ${req.params.id}:`, { error });
|
||||
@@ -247,7 +256,7 @@ router.delete('/users/:id', async (req, res, next: NextFunction) => {
|
||||
return res.status(400).json({ message: 'Admins cannot delete their own account.' });
|
||||
}
|
||||
try {
|
||||
await db.deleteUserById(req.params.id);
|
||||
await db.userRepo.deleteUserById(req.params.id);
|
||||
res.status(204).send();
|
||||
} catch (error) {
|
||||
next(error);
|
||||
@@ -432,4 +441,24 @@ router.post('/jobs/:queueName/:jobId/retry', async (req, res, next: NextFunction
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* POST /api/admin/trigger/weekly-analytics - Manually trigger the weekly analytics report job.
|
||||
*/
|
||||
router.post('/trigger/weekly-analytics', async (req, res, next: NextFunction) => {
|
||||
const adminUser = req.user as UserProfile;
|
||||
logger.info(`[Admin] Manual trigger for weekly analytics report received from user: ${adminUser.user_id}`);
|
||||
|
||||
try {
|
||||
const { year: reportYear, week: reportWeek } = getSimpleWeekAndYear();
|
||||
const { weeklyAnalyticsQueue } = await import('../services/queueService.server');
|
||||
const job = await weeklyAnalyticsQueue.add('generate-weekly-report', { reportYear, reportWeek }, {
|
||||
jobId: `manual-weekly-report-${reportYear}-${reportWeek}-${Date.now()}` // Add timestamp to avoid ID conflict
|
||||
});
|
||||
|
||||
res.status(202).json({ message: 'Successfully enqueued weekly analytics job.', jobId: job.id });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
||||
Reference in New Issue
Block a user