refactor: simplify async route handlers by making 'next' optional
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
// src/pages/admin/components/SystemCheck.tsx
|
||||
/// <reference types="vite/client" />
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import toast from 'react-hot-toast';
|
||||
import { loginUser, pingBackend, checkDbSchema, checkStorage, checkDbPoolHealth, checkPm2Status, checkRedisHealth, triggerFailingJob, clearGeocodeCache } from '../../../services/apiClient';
|
||||
|
||||
@@ -7,6 +7,7 @@ import multer from 'multer';
|
||||
import * as db from '../services/db';
|
||||
import { logger } from '../services/logger.server';
|
||||
import { UserProfile } from '../types';
|
||||
import { AsyncRequestHandler } from '../types/express';
|
||||
import { clearGeocodeCache } from '../services/geocodingService.server';
|
||||
|
||||
// --- Bull Board (Job Queue UI) Imports ---
|
||||
@@ -55,27 +56,27 @@ router.use(passport.authenticate('jwt', { session: false }), isAdmin);
|
||||
|
||||
// --- Admin Routes ---
|
||||
|
||||
router.get('/corrections', async (req: Request, res: Response, next: NextFunction) => {
|
||||
router.get('/corrections', async (req, res, next) => {
|
||||
const corrections = await db.getSuggestedCorrections();
|
||||
res.json(corrections);
|
||||
});
|
||||
|
||||
router.get('/brands', async (req: Request, res: Response, next: NextFunction) => {
|
||||
router.get('/brands', async (req, res, next) => {
|
||||
const brands = await db.getAllBrands();
|
||||
res.json(brands);
|
||||
});
|
||||
|
||||
router.get('/stats', async (req: Request, res: Response, next: NextFunction) => {
|
||||
router.get('/stats', async (req, res, next) => {
|
||||
const stats = await db.getApplicationStats();
|
||||
res.json(stats);
|
||||
});
|
||||
|
||||
router.get('/stats/daily', async (req: Request, res: Response, next: NextFunction) => {
|
||||
router.get('/stats/daily', async (req, res, next) => {
|
||||
const dailyStats = await db.getDailyStatsForLast30Days();
|
||||
res.json(dailyStats);
|
||||
});
|
||||
|
||||
router.post('/corrections/:id/approve', async (req: Request, res: Response, next: NextFunction) => {
|
||||
router.post('/corrections/:id/approve', async (req, res, next) => {
|
||||
const correctionId = parseInt(req.params.id, 10);
|
||||
// Add validation to ensure the ID is a valid number.
|
||||
if (isNaN(correctionId)) {
|
||||
@@ -86,13 +87,13 @@ router.post('/corrections/:id/approve', async (req: Request, res: Response, next
|
||||
res.status(200).json({ message: 'Correction approved successfully.' });
|
||||
});
|
||||
|
||||
router.post('/corrections/:id/reject', async (req: Request, res: Response, next: NextFunction) => {
|
||||
router.post('/corrections/:id/reject', async (req, res, next) => {
|
||||
const correctionId = parseInt(req.params.id, 10);
|
||||
await db.rejectCorrection(correctionId);
|
||||
res.status(200).json({ message: 'Correction rejected successfully.' });
|
||||
});
|
||||
|
||||
router.put('/corrections/:id', async (req: Request, res: Response, next: NextFunction) => {
|
||||
router.put('/corrections/:id', async (req, res, next) => {
|
||||
const correctionId = parseInt(req.params.id, 10);
|
||||
const { suggested_value } = req.body;
|
||||
if (!suggested_value) {
|
||||
@@ -110,7 +111,7 @@ router.put('/corrections/:id', async (req: Request, res: Response, next: NextFun
|
||||
}
|
||||
});
|
||||
|
||||
router.put('/recipes/:id/status', async (req: Request, res: Response, next: NextFunction) => {
|
||||
router.put('/recipes/:id/status', async (req, res, next) => {
|
||||
const recipeId = parseInt(req.params.id, 10);
|
||||
const { status } = req.body;
|
||||
|
||||
@@ -121,7 +122,7 @@ router.put('/recipes/:id/status', async (req: Request, res: Response, next: Next
|
||||
res.status(200).json(updatedRecipe);
|
||||
});
|
||||
|
||||
router.post('/brands/:id/logo', upload.single('logoImage'), async (req: Request, res: Response, next: NextFunction) => {
|
||||
router.post('/brands/:id/logo', upload.single('logoImage'), async (req, res, next) => {
|
||||
const brandId = parseInt(req.params.id, 10);
|
||||
if (!req.file) {
|
||||
return res.status(400).json({ message: 'Logo image file is required.' });
|
||||
@@ -134,12 +135,12 @@ router.post('/brands/:id/logo', upload.single('logoImage'), async (req: Request,
|
||||
res.status(200).json({ message: 'Brand logo updated successfully.', logoUrl });
|
||||
});
|
||||
|
||||
router.get('/unmatched-items', async (req: Request, res: Response, next: NextFunction) => {
|
||||
router.get('/unmatched-items', async (req, res, next) => {
|
||||
const items = await db.getUnmatchedFlyerItems();
|
||||
res.json(items);
|
||||
});
|
||||
|
||||
router.put('/comments/:id/status', async (req: Request, res: Response, next: NextFunction) => {
|
||||
router.put('/comments/:id/status', async (req, res, next) => {
|
||||
const commentId = parseInt(req.params.id, 10);
|
||||
const { status } = req.body;
|
||||
|
||||
@@ -150,19 +151,19 @@ router.put('/comments/:id/status', async (req: Request, res: Response, next: Nex
|
||||
res.status(200).json(updatedComment);
|
||||
});
|
||||
|
||||
router.get('/users', async (req: Request, res: Response, next: NextFunction) => {
|
||||
router.get('/users', async (req, res, next) => {
|
||||
const users = await db.getAllUsers();
|
||||
res.json(users);
|
||||
});
|
||||
|
||||
router.get('/activity-log', async (req: Request, res: Response, next: NextFunction) => {
|
||||
router.get('/activity-log', async (req, res, next) => {
|
||||
const limit = parseInt(req.query.limit as string, 10) || 50;
|
||||
const offset = parseInt(req.query.offset as string, 10) || 0;
|
||||
const logs = await db.getActivityLog(limit, offset);
|
||||
res.json(logs);
|
||||
});
|
||||
|
||||
router.get('/users/:id', async (req: Request, res: Response, next: NextFunction) => {
|
||||
router.get('/users/:id', async (req, res, next) => {
|
||||
const user = await db.findUserProfileById(req.params.id);
|
||||
if (!user) {
|
||||
return res.status(404).json({ message: 'User not found.' });
|
||||
@@ -170,7 +171,7 @@ router.get('/users/:id', async (req: Request, res: Response, next: NextFunction)
|
||||
res.json(user);
|
||||
});
|
||||
|
||||
router.put('/users/:id', async (req: Request, res: Response, next: NextFunction) => {
|
||||
router.put('/users/:id', async (req, res, next) => {
|
||||
const { role } = req.body;
|
||||
if (!role || !['user', 'admin'].includes(role)) {
|
||||
return res.status(400).json({ message: 'A valid role ("user" or "admin") is required.' });
|
||||
@@ -189,7 +190,7 @@ router.put('/users/:id', async (req: Request, res: Response, next: NextFunction)
|
||||
}
|
||||
});
|
||||
|
||||
router.delete('/users/:id', async (req: Request, res: Response, next: NextFunction) => {
|
||||
router.delete('/users/:id', async (req, res, next) => {
|
||||
const adminUser = req.user as UserProfile;
|
||||
if (adminUser.user.user_id === req.params.id) {
|
||||
return res.status(400).json({ message: 'Admins cannot delete their own account.' });
|
||||
@@ -202,7 +203,7 @@ router.delete('/users/:id', async (req: Request, res: Response, next: NextFuncti
|
||||
* POST /api/admin/trigger/daily-deal-check - Manually trigger the daily deal check job.
|
||||
* 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) => {
|
||||
router.post('/trigger/daily-deal-check', async (req, res, next) => {
|
||||
const adminUser = req.user as UserProfile;
|
||||
logger.info(`[Admin] Manual trigger for daily deal check received from user: ${adminUser.user_id}`);
|
||||
|
||||
@@ -221,7 +222,7 @@ router.post('/trigger/daily-deal-check', async (req: Request, res: Response, nex
|
||||
* POST /api/admin/trigger/analytics-report - Manually enqueue a job to generate the daily analytics report.
|
||||
* 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) => {
|
||||
router.post('/trigger/analytics-report', async (req, res, next) => {
|
||||
const adminUser = req.user as UserProfile;
|
||||
logger.info(`[Admin] Manual trigger for analytics report generation received from user: ${adminUser.user_id}`);
|
||||
|
||||
@@ -243,7 +244,7 @@ router.post('/trigger/analytics-report', async (req: Request, res: Response, nex
|
||||
* POST /api/admin/flyers/:flyerId/cleanup - Enqueue a job to clean up a flyer's files.
|
||||
* This is triggered by an admin after they have verified the flyer processing was successful.
|
||||
*/
|
||||
router.post('/flyers/:flyerId/cleanup', async (req: Request, res: Response, next: NextFunction) => {
|
||||
router.post('/flyers/:flyerId/cleanup', async (req, res, next) => {
|
||||
const adminUser = req.user as UserProfile;
|
||||
const flyerId = parseInt(req.params.flyerId, 10);
|
||||
|
||||
@@ -262,7 +263,7 @@ router.post('/flyers/:flyerId/cleanup', async (req: Request, res: Response, next
|
||||
* POST /api/admin/trigger/failing-job - Enqueue a test job designed to fail.
|
||||
* This is for testing the retry mechanism and Bull Board UI.
|
||||
*/
|
||||
router.post('/trigger/failing-job', async (req: Request, res: Response, next: NextFunction) => {
|
||||
router.post('/trigger/failing-job', async (req, res, next) => {
|
||||
const adminUser = req.user as UserProfile;
|
||||
logger.info(`[Admin] Manual trigger for a failing job received from user: ${adminUser.user_id}`);
|
||||
|
||||
@@ -279,7 +280,7 @@ router.post('/trigger/failing-job', async (req: Request, res: Response, next: Ne
|
||||
* POST /api/admin/system/clear-geocode-cache - Clears the Redis cache for geocoded addresses.
|
||||
* Requires admin privileges.
|
||||
*/
|
||||
router.post('/system/clear-geocode-cache', async (req: Request, res: Response, next: NextFunction) => {
|
||||
router.post('/system/clear-geocode-cache', async (req, res, next) => {
|
||||
const adminUser = req.user as UserProfile;
|
||||
logger.info(`[Admin] Manual trigger for geocode cache clear received from user: ${adminUser.user_id}`);
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import * as aiService from '../services/aiService.server'; // Correctly import s
|
||||
import { generateFlyerIcon } from '../utils/imageProcessor';
|
||||
import { logger } from '../services/logger.server';
|
||||
import { UserProfile } from '../types';
|
||||
import { AsyncRequestHandler } from '../types/express';
|
||||
import { flyerQueue } from '../services/queueService.server';
|
||||
|
||||
const router = Router();
|
||||
@@ -68,7 +69,7 @@ router.use((req: Request, res: Response, next: NextFunction) => {
|
||||
* NEW ENDPOINT: Accepts a single flyer file (PDF or image), enqueues it for
|
||||
* background processing, and immediately returns a job ID.
|
||||
*/
|
||||
router.post('/upload-and-process', optionalAuth, uploadToDisk.single('flyerFile'), async (req: Request, res: Response, next: NextFunction) => {
|
||||
router.post('/upload-and-process', optionalAuth, uploadToDisk.single('flyerFile'), async (req, res, next) => {
|
||||
try {
|
||||
if (!req.file) {
|
||||
return res.status(400).json({ message: 'A flyer file (PDF or image) is required.' });
|
||||
@@ -153,7 +154,7 @@ router.get('/jobs/:jobId/status', async (req, res) => {
|
||||
* in the flyer upload workflow after the AI has extracted the data.
|
||||
* It uses `optionalAuth` to handle submissions from both anonymous and authenticated users.
|
||||
*/
|
||||
router.post('/flyers/process', optionalAuth, uploadToDisk.single('flyerImage'), async (req: Request, res: Response, next: NextFunction) => {
|
||||
router.post('/flyers/process', optionalAuth, uploadToDisk.single('flyerImage'), async (req, res, next) => {
|
||||
try {
|
||||
if (!req.file) {
|
||||
return res.status(400).json({ message: 'Flyer image file is required.' });
|
||||
@@ -389,7 +390,7 @@ router.post(
|
||||
'/rescan-area',
|
||||
passport.authenticate('jwt', { session: false }),
|
||||
uploadToDisk.single('image'),
|
||||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
async (req, res, next) => {
|
||||
try {
|
||||
if (!req.file) {
|
||||
return res.status(400).json({ message: 'Image file is required.' });
|
||||
|
||||
@@ -10,6 +10,7 @@ import passport from './passport';
|
||||
import * as db from '../services/db';
|
||||
import { getPool } from '../services/db/connection';
|
||||
import { logger } from '../services/logger.server';
|
||||
import { AsyncRequestHandler } from '../types/express';
|
||||
import { sendPasswordResetEmail } from '../services/emailService.server';
|
||||
|
||||
const router = Router();
|
||||
@@ -41,7 +42,7 @@ const resetPasswordLimiter = rateLimit({
|
||||
// --- Authentication Routes ---
|
||||
|
||||
// Registration Route
|
||||
router.post('/register', async (req: Request, res: Response, next: NextFunction) => {
|
||||
router.post('/register', async (req, res, next) => {
|
||||
const { email, password, full_name, avatar_url } = req.body;
|
||||
|
||||
if (!email || !password) {
|
||||
@@ -146,7 +147,7 @@ router.post('/login', (req: Request, res: Response, next: NextFunction) => {
|
||||
});
|
||||
|
||||
// Route to request a password reset
|
||||
router.post('/forgot-password', forgotPasswordLimiter, async (req: Request, res: Response, next: NextFunction) => {
|
||||
router.post('/forgot-password', forgotPasswordLimiter, async (req, res, next) => {
|
||||
const { email } = req.body;
|
||||
if (!email) {
|
||||
return res.status(400).json({ message: 'Email is required.' });
|
||||
@@ -189,7 +190,7 @@ router.post('/forgot-password', forgotPasswordLimiter, async (req: Request, res:
|
||||
});
|
||||
|
||||
// Route to reset the password using a token
|
||||
router.post('/reset-password', resetPasswordLimiter, async (req: Request, res: Response, next: NextFunction) => {
|
||||
router.post('/reset-password', resetPasswordLimiter, async (req, res, next) => {
|
||||
const { token, newPassword } = req.body;
|
||||
if (!token || !newPassword) {
|
||||
return res.status(400).json({ message: 'Token and new password are required.' });
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
} from '../services/db';
|
||||
import { logger } from '../services/logger.server';
|
||||
import { UserProfile } from '../types';
|
||||
import { AsyncRequestHandler } from '../types/express';
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
@@ -19,7 +20,7 @@ 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) => {
|
||||
router.get('/', async (req, res, next) => {
|
||||
const user = req.user as UserProfile;
|
||||
try {
|
||||
const budgets = await getBudgetsForUser(user.user_id);
|
||||
@@ -33,7 +34,7 @@ router.get('/', async (req: Request, res: Response, next: NextFunction) => {
|
||||
/**
|
||||
* POST /api/budgets - Create a new budget for the authenticated user.
|
||||
*/
|
||||
router.post('/', async (req: Request, res: Response, next: NextFunction) => {
|
||||
router.post('/', async (req, res, next) => {
|
||||
const user = req.user as UserProfile;
|
||||
try {
|
||||
const newBudget = await createBudget(user.user_id, req.body);
|
||||
@@ -47,7 +48,7 @@ router.post('/', async (req: Request, res: Response, next: NextFunction) => {
|
||||
/**
|
||||
* PUT /api/budgets/:id - Update an existing budget.
|
||||
*/
|
||||
router.put('/:id', async (req: Request, res: Response, next: NextFunction) => {
|
||||
router.put('/:id', async (req, res, next) => {
|
||||
const user = req.user as UserProfile;
|
||||
const budgetId = parseInt(req.params.id, 10);
|
||||
try {
|
||||
@@ -62,7 +63,7 @@ router.put('/:id', async (req: Request, res: Response, next: NextFunction) => {
|
||||
/**
|
||||
* DELETE /api/budgets/:id - Delete a budget.
|
||||
*/
|
||||
router.delete('/:id', async (req: Request, res: Response, next: NextFunction) => {
|
||||
router.delete('/:id', async (req, res, next) => {
|
||||
const user = req.user as UserProfile;
|
||||
const budgetId = parseInt(req.params.id, 10);
|
||||
try {
|
||||
@@ -78,7 +79,7 @@ router.delete('/:id', async (req: Request, res: Response, next: NextFunction) =>
|
||||
* 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', async (req: Request, res: Response, next: NextFunction) => {
|
||||
router.get('/spending-analysis', async (req, res, next) => {
|
||||
const user = req.user as UserProfile;
|
||||
const { startDate, endDate } = req.query;
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import passport, { isAdmin } from './passport';
|
||||
import { getAllAchievements, getUserAchievements, awardAchievement, getLeaderboard } from '../services/db';
|
||||
import { logger } from '../services/logger.server';
|
||||
import { UserProfile } from '../types';
|
||||
import { AsyncRequestHandler } from '../types/express';
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
@@ -11,7 +12,7 @@ const router = express.Router();
|
||||
* GET /api/achievements - Get the master list of all available achievements.
|
||||
* This is a public endpoint.
|
||||
*/
|
||||
router.get('/', async (req: Request, res: Response, next: NextFunction) => {
|
||||
router.get('/', async (req, res, next) => {
|
||||
try {
|
||||
const achievements = await getAllAchievements();
|
||||
res.json(achievements);
|
||||
@@ -25,7 +26,7 @@ router.get('/', async (req: Request, res: Response, next: NextFunction) => {
|
||||
* GET /api/achievements/leaderboard - Get the top users by points.
|
||||
* This is a public endpoint.
|
||||
*/
|
||||
router.get('/leaderboard', async (req: Request, res: Response, next: NextFunction) => {
|
||||
router.get('/leaderboard', async (req, res, next) => {
|
||||
// Allow client to specify a limit, but default to 10 and cap it at 50.
|
||||
const limit = Math.min(parseInt(req.query.limit as string, 10) || 10, 50);
|
||||
|
||||
@@ -45,7 +46,7 @@ router.get('/leaderboard', async (req: Request, res: Response, next: NextFunctio
|
||||
router.get(
|
||||
'/me',
|
||||
passport.authenticate('jwt', { session: false }),
|
||||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
async (req, res, next) => {
|
||||
const user = req.user as UserProfile;
|
||||
try {
|
||||
const userAchievements = await getUserAchievements(user.user_id);
|
||||
@@ -65,7 +66,7 @@ router.post(
|
||||
'/award',
|
||||
passport.authenticate('jwt', { session: false }),
|
||||
isAdmin,
|
||||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
async (req, res, next) => {
|
||||
const { userId, achievementName } = req.body;
|
||||
|
||||
if (!userId || !achievementName) {
|
||||
|
||||
@@ -4,6 +4,7 @@ import * as db from '../services/db';
|
||||
import { logger } from '../services/logger.server';
|
||||
import fs from 'fs/promises';
|
||||
import passport from 'passport';
|
||||
import { AsyncRequestHandler } from '../types/express';
|
||||
|
||||
const router = Router();
|
||||
|
||||
@@ -12,7 +13,7 @@ router.get('/health/ping', (req: Request, res: Response) => {
|
||||
res.status(200).send('pong');
|
||||
});
|
||||
|
||||
router.get('/health/db-schema', async (req: Request, res: Response) => {
|
||||
router.get('/health/db-schema', async (req, res) => {
|
||||
try {
|
||||
const requiredTables = ['users', 'profiles', 'flyers', 'flyer_items', 'stores'];
|
||||
const missingTables = await db.checkTablesExist(requiredTables);
|
||||
@@ -27,7 +28,7 @@ router.get('/health/db-schema', async (req: Request, res: Response) => {
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/health/storage', async (req: Request, res: Response) => {
|
||||
router.get('/health/storage', async (req, res) => {
|
||||
const storagePath = process.env.STORAGE_PATH || '/var/www/flyer-crawler.projectium.com/assets';
|
||||
try {
|
||||
await fs.access(storagePath, fs.constants.W_OK);
|
||||
@@ -58,7 +59,7 @@ router.get('/health/db-pool', (req: Request, res: Response) => {
|
||||
|
||||
// --- Public Data Routes ---
|
||||
|
||||
router.get('/flyers', async (req: Request, res: Response, next: NextFunction) => {
|
||||
router.get('/flyers', async (req, res, next) => {
|
||||
try {
|
||||
const flyers = await db.getFlyers();
|
||||
res.json(flyers);
|
||||
@@ -68,7 +69,7 @@ router.get('/flyers', async (req: Request, res: Response, next: NextFunction) =>
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/master-items', async (req: Request, res: Response, next: NextFunction) => {
|
||||
router.get('/master-items', async (req, res, next) => {
|
||||
try {
|
||||
const masterItems = await db.getAllMasterItems();
|
||||
res.json(masterItems);
|
||||
@@ -78,7 +79,7 @@ router.get('/master-items', async (req: Request, res: Response, next: NextFuncti
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/flyers/:id/items', async (req: Request, res: Response, next: NextFunction) => {
|
||||
router.get('/flyers/:id/items', async (req, res, next) => {
|
||||
try {
|
||||
const flyerId = parseInt(req.params.id, 10);
|
||||
const items = await db.getFlyerItems(flyerId);
|
||||
@@ -89,7 +90,7 @@ router.get('/flyers/:id/items', async (req: Request, res: Response, next: NextFu
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/flyer-items/batch-fetch', async (req: Request, res: Response, next: NextFunction) => {
|
||||
router.post('/flyer-items/batch-fetch', async (req, res, next) => {
|
||||
const { flyerIds } = req.body;
|
||||
if (!Array.isArray(flyerIds)) {
|
||||
return res.status(400).json({ message: 'flyerIds must be an array.' });
|
||||
@@ -102,7 +103,7 @@ router.post('/flyer-items/batch-fetch', async (req: Request, res: Response, next
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/flyer-items/batch-count', async (req: Request, res: Response, next: NextFunction) => {
|
||||
router.post('/flyer-items/batch-count', async (req, res, next) => {
|
||||
const { flyerIds } = req.body;
|
||||
if (!Array.isArray(flyerIds)) {
|
||||
return res.status(400).json({ message: 'flyerIds must be an array.' });
|
||||
@@ -115,7 +116,7 @@ router.post('/flyer-items/batch-count', async (req: Request, res: Response, next
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/recipes/by-sale-percentage', async (req: Request, res: Response, next: NextFunction) => {
|
||||
router.get('/recipes/by-sale-percentage', async (req, res, next) => {
|
||||
const minPercentageStr = req.query.minPercentage as string || '50.0';
|
||||
const minPercentage = parseFloat(minPercentageStr);
|
||||
|
||||
@@ -130,7 +131,7 @@ router.get('/recipes/by-sale-percentage', async (req: Request, res: Response, ne
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/recipes/by-sale-ingredients', async (req: Request, res: Response, next: NextFunction) => {
|
||||
router.get('/recipes/by-sale-ingredients', async (req, res, next) => {
|
||||
const minIngredientsStr = req.query.minIngredients as string || '3';
|
||||
const minIngredients = parseInt(minIngredientsStr, 10);
|
||||
|
||||
@@ -141,7 +142,7 @@ router.get('/recipes/by-sale-ingredients', async (req: Request, res: Response, n
|
||||
res.json(recipes);
|
||||
});
|
||||
|
||||
router.get('/recipes/by-ingredient-and-tag', async (req: Request, res: Response, next: NextFunction) => {
|
||||
router.get('/recipes/by-ingredient-and-tag', async (req, res, next) => {
|
||||
const { ingredient, tag } = req.query;
|
||||
if (!ingredient || !tag) {
|
||||
return res.status(400).json({ message: 'Both "ingredient" and "tag" query parameters are required.' });
|
||||
@@ -150,7 +151,7 @@ router.get('/recipes/by-ingredient-and-tag', async (req: Request, res: Response,
|
||||
res.json(recipes);
|
||||
});
|
||||
|
||||
router.get('/stats/most-frequent-sales', async (req: Request, res: Response, next: NextFunction) => {
|
||||
router.get('/stats/most-frequent-sales', async (req, res, next) => {
|
||||
const daysStr = req.query.days as string || '30';
|
||||
const limitStr = req.query.limit as string || '10';
|
||||
|
||||
@@ -168,18 +169,18 @@ router.get('/stats/most-frequent-sales', async (req: Request, res: Response, nex
|
||||
res.json(items);
|
||||
});
|
||||
|
||||
router.get('/recipes/:recipeId/comments', async (req: Request, res: Response, next: NextFunction) => {
|
||||
router.get('/recipes/:recipeId/comments', async (req, res, next) => {
|
||||
const recipeId = parseInt(req.params.recipeId, 10);
|
||||
const comments = await db.getRecipeComments(recipeId);
|
||||
res.json(comments);
|
||||
});
|
||||
|
||||
router.get('/dietary-restrictions', async (req: Request, res: Response, next: NextFunction) => {
|
||||
router.get('/dietary-restrictions', async (req, res, next) => {
|
||||
const restrictions = await db.getDietaryRestrictions();
|
||||
res.json(restrictions);
|
||||
});
|
||||
|
||||
router.get('/appliances', async (req: Request, res: Response, next: NextFunction) => {
|
||||
router.get('/appliances', async (req, res, next) => {
|
||||
const appliances = await db.getAppliances();
|
||||
res.json(appliances);
|
||||
});
|
||||
|
||||
@@ -9,6 +9,7 @@ import zxcvbn from 'zxcvbn';
|
||||
import * as db from '../services/db';
|
||||
import { logger } from '../services/logger.server';
|
||||
import { User, UserProfile, Address } from '../types';
|
||||
import { AsyncRequestHandler } from '../types/express';
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
@@ -27,7 +28,7 @@ fs.mkdir(avatarUploadDir, { recursive: true }).catch(err => {
|
||||
*/
|
||||
router.post(
|
||||
'/profile/avatar',
|
||||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
async (req, res, next) => {
|
||||
// Initialize multer inside the route handler where req.user is available.
|
||||
// This prevents the "Cannot read properties of undefined" error during test setup.
|
||||
const storage = multer.diskStorage({
|
||||
@@ -120,7 +121,7 @@ router.post(
|
||||
/**
|
||||
* GET /api/users/profile - Get the full profile for the authenticated user.
|
||||
*/
|
||||
router.get('/profile', async (req: Request, res: Response, next: NextFunction) => {
|
||||
router.get('/profile', async (req, res, next) => {
|
||||
logger.debug(`[ROUTE] GET /api/users/profile - ENTER`);
|
||||
const user = req.user as UserProfile;
|
||||
try {
|
||||
@@ -140,7 +141,7 @@ router.get('/profile', async (req: Request, res: Response, next: NextFunction) =
|
||||
/**
|
||||
* PUT /api/users/profile - Update the user's profile information.
|
||||
*/
|
||||
router.put('/profile', async (req: Request, res: Response, next: NextFunction) => {
|
||||
router.put('/profile', async (req, res, next) => {
|
||||
logger.debug(`[ROUTE] PUT /api/users/profile - ENTER`);
|
||||
const user = req.user as UserProfile;
|
||||
const { full_name, avatar_url } = req.body;
|
||||
@@ -161,7 +162,7 @@ router.put('/profile', async (req: Request, res: Response, next: NextFunction) =
|
||||
/**
|
||||
* PUT /api/users/profile/password - Update the user's password.
|
||||
*/
|
||||
router.put('/profile/password', async (req: Request, res: Response, next: NextFunction) => {
|
||||
router.put('/profile/password', async (req, res, next) => {
|
||||
logger.debug(`[ROUTE] PUT /api/users/profile/password - ENTER`);
|
||||
const user = req.user as UserProfile;
|
||||
const { newPassword } = req.body;
|
||||
@@ -191,7 +192,7 @@ router.put('/profile/password', async (req: Request, res: Response, next: NextFu
|
||||
/**
|
||||
* DELETE /api/users/account - Delete the user's own account.
|
||||
*/
|
||||
router.delete('/account', async (req: Request, res: Response, next: NextFunction) => {
|
||||
router.delete('/account', async (req, res, next) => {
|
||||
logger.debug(`[ROUTE] DELETE /api/users/account - ENTER`);
|
||||
const user = req.user as UserProfile;
|
||||
const { password } = req.body;
|
||||
@@ -222,7 +223,7 @@ router.delete('/account', async (req: Request, res: Response, next: NextFunction
|
||||
/**
|
||||
* GET /api/users/watched-items - Get all watched items for the authenticated user.
|
||||
*/
|
||||
router.get('/watched-items', async (req: Request, res: Response, next: NextFunction) => {
|
||||
router.get('/watched-items', async (req, res, next) => {
|
||||
logger.debug(`[ROUTE] GET /api/users/watched-items - ENTER`);
|
||||
const user = req.user as UserProfile;
|
||||
try {
|
||||
@@ -237,7 +238,7 @@ router.get('/watched-items', async (req: Request, res: Response, next: NextFunct
|
||||
/**
|
||||
* POST /api/users/watched-items - Add a new item to the user's watchlist.
|
||||
*/
|
||||
router.post('/watched-items', async (req: Request, res: Response, next: NextFunction) => {
|
||||
router.post('/watched-items', async (req, res, next) => {
|
||||
logger.debug(`[ROUTE] POST /api/users/watched-items - ENTER`);
|
||||
const user = req.user as UserProfile;
|
||||
const { itemName, category } = req.body;
|
||||
@@ -253,7 +254,7 @@ router.post('/watched-items', async (req: Request, res: Response, next: NextFunc
|
||||
/**
|
||||
* DELETE /api/users/watched-items/:masterItemId - Remove an item from the watchlist.
|
||||
*/
|
||||
router.delete('/watched-items/:masterItemId', async (req: Request, res: Response, next: NextFunction) => {
|
||||
router.delete('/watched-items/:masterItemId', async (req, res, next) => {
|
||||
logger.debug(`[ROUTE] DELETE /api/users/watched-items/:masterItemId - ENTER`);
|
||||
const user = req.user as UserProfile;
|
||||
const masterItemId = parseInt(req.params.masterItemId, 10);
|
||||
@@ -269,7 +270,7 @@ router.delete('/watched-items/:masterItemId', async (req: Request, res: Response
|
||||
/**
|
||||
* GET /api/users/shopping-lists - Get all shopping lists for the user.
|
||||
*/
|
||||
router.get('/shopping-lists', async (req: Request, res: Response, next: NextFunction) => {
|
||||
router.get('/shopping-lists', async (req, res, next) => {
|
||||
logger.debug(`[ROUTE] GET /api/users/shopping-lists - ENTER`);
|
||||
const user = req.user as UserProfile;
|
||||
try {
|
||||
@@ -284,7 +285,7 @@ router.get('/shopping-lists', async (req: Request, res: Response, next: NextFunc
|
||||
/**
|
||||
* POST /api/users/shopping-lists - Create a new shopping list.
|
||||
*/
|
||||
router.post('/shopping-lists', async (req: Request, res: Response, next: NextFunction) => {
|
||||
router.post('/shopping-lists', async (req, res, next) => {
|
||||
logger.debug(`[ROUTE] POST /api/users/shopping-lists - ENTER`);
|
||||
const user = req.user as UserProfile;
|
||||
const { name } = req.body;
|
||||
@@ -300,7 +301,7 @@ router.post('/shopping-lists', async (req: Request, res: Response, next: NextFun
|
||||
/**
|
||||
* DELETE /api/users/shopping-lists/:listId - Delete a shopping list.
|
||||
*/
|
||||
router.delete('/shopping-lists/:listId', async (req: Request, res: Response, next: NextFunction) => {
|
||||
router.delete('/shopping-lists/:listId', async (req, res, next) => {
|
||||
logger.debug(`[ROUTE] DELETE /api/users/shopping-lists/:listId - ENTER`);
|
||||
const user = req.user as UserProfile;
|
||||
const listId = parseInt(req.params.listId, 10);
|
||||
@@ -316,7 +317,7 @@ router.delete('/shopping-lists/:listId', async (req: Request, res: Response, nex
|
||||
/**
|
||||
* POST /api/users/shopping-lists/:listId/items - Add an item to a shopping list.
|
||||
*/
|
||||
router.post('/shopping-lists/:listId/items', async (req: Request, res: Response, next: NextFunction) => {
|
||||
router.post('/shopping-lists/:listId/items', async (req, res, next) => {
|
||||
logger.debug(`[ROUTE] POST /api/users/shopping-lists/:listId/items - ENTER`);
|
||||
const listId = parseInt(req.params.listId, 10);
|
||||
try {
|
||||
@@ -331,7 +332,7 @@ router.post('/shopping-lists/:listId/items', async (req: Request, res: Response,
|
||||
/**
|
||||
* PUT /api/users/shopping-lists/items/:itemId - Update a shopping list item.
|
||||
*/
|
||||
router.put('/shopping-lists/items/:itemId', async (req: Request, res: Response, next: NextFunction) => {
|
||||
router.put('/shopping-lists/items/:itemId', async (req, res, next) => {
|
||||
logger.debug(`[ROUTE] PUT /api/users/shopping-lists/items/:itemId - ENTER`);
|
||||
const itemId = parseInt(req.params.itemId, 10);
|
||||
try {
|
||||
@@ -346,7 +347,7 @@ router.put('/shopping-lists/items/:itemId', async (req: Request, res: Response,
|
||||
/**
|
||||
* DELETE /api/users/shopping-lists/items/:itemId - Remove an item from a shopping list.
|
||||
*/
|
||||
router.delete('/shopping-lists/items/:itemId', async (req: Request, res: Response, next: NextFunction) => {
|
||||
router.delete('/shopping-lists/items/:itemId', async (req, res, next) => {
|
||||
logger.debug(`[ROUTE] DELETE /api/users/shopping-lists/items/:itemId - ENTER`);
|
||||
const itemId = parseInt(req.params.itemId, 10);
|
||||
try {
|
||||
@@ -361,7 +362,7 @@ router.delete('/shopping-lists/items/:itemId', async (req: Request, res: Respons
|
||||
/**
|
||||
* PUT /api/users/profile/preferences - Update user preferences.
|
||||
*/
|
||||
router.put('/profile/preferences', async (req: Request, res: Response, next: NextFunction) => {
|
||||
router.put('/profile/preferences', async (req, res, next) => {
|
||||
logger.debug(`[ROUTE] PUT /api/users/profile/preferences - ENTER`);
|
||||
const user = req.user as UserProfile;
|
||||
if (typeof req.body !== 'object' || req.body === null || Array.isArray(req.body)) {
|
||||
@@ -376,7 +377,7 @@ router.put('/profile/preferences', async (req: Request, res: Response, next: Nex
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/me/dietary-restrictions', async (req: Request, res: Response, next: NextFunction) => {
|
||||
router.get('/me/dietary-restrictions', async (req, res, next) => {
|
||||
logger.debug(`[ROUTE] GET /api/users/me/dietary-restrictions - ENTER`);
|
||||
const user = req.user as UserProfile;
|
||||
try {
|
||||
@@ -388,7 +389,7 @@ router.get('/me/dietary-restrictions', async (req: Request, res: Response, next:
|
||||
}
|
||||
});
|
||||
|
||||
router.put('/me/dietary-restrictions', async (req: Request, res: Response, next: NextFunction) => {
|
||||
router.put('/me/dietary-restrictions', async (req, res, next) => {
|
||||
logger.debug(`[ROUTE] PUT /api/users/me/dietary-restrictions - ENTER`);
|
||||
const user = req.user as UserProfile;
|
||||
const { restrictionIds } = req.body;
|
||||
@@ -401,7 +402,7 @@ router.put('/me/dietary-restrictions', async (req: Request, res: Response, next:
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/me/appliances', async (req: Request, res: Response, next: NextFunction) => {
|
||||
router.get('/me/appliances', async (req, res, next) => {
|
||||
logger.debug(`[ROUTE] GET /api/users/me/appliances - ENTER`);
|
||||
const user = req.user as UserProfile;
|
||||
try {
|
||||
@@ -413,7 +414,7 @@ router.get('/me/appliances', async (req: Request, res: Response, next: NextFunct
|
||||
}
|
||||
});
|
||||
|
||||
router.put('/me/appliances', async (req: Request, res: Response, next: NextFunction) => {
|
||||
router.put('/me/appliances', async (req, res, next) => {
|
||||
logger.debug(`[ROUTE] PUT /api/users/me/appliances - ENTER`);
|
||||
const user = req.user as UserProfile;
|
||||
const { applianceIds } = req.body;
|
||||
@@ -430,7 +431,7 @@ router.put('/me/appliances', async (req: Request, res: Response, next: NextFunct
|
||||
* GET /api/users/addresses/:addressId - Get a specific address by its ID.
|
||||
* This is protected to ensure a user can only fetch their own address details.
|
||||
*/
|
||||
router.get('/addresses/:addressId', async (req: Request, res: Response, next: NextFunction) => {
|
||||
router.get('/addresses/:addressId', async (req, res, next) => {
|
||||
const user = req.user as UserProfile;
|
||||
const addressId = parseInt(req.params.addressId, 10);
|
||||
|
||||
@@ -453,7 +454,7 @@ router.get('/addresses/:addressId', async (req: Request, res: Response, next: Ne
|
||||
/**
|
||||
* PUT /api/users/profile/address - Create or update the user's primary address.
|
||||
*/
|
||||
router.put('/profile/address', async (req: Request, res: Response, next: NextFunction) => {
|
||||
router.put('/profile/address', async (req, res, next) => {
|
||||
const user = req.user as UserProfile;
|
||||
const addressData = req.body as Partial<Address>;
|
||||
|
||||
|
||||
@@ -25,8 +25,6 @@ describe('DB Connection Service', () => {
|
||||
|
||||
if (typeof pg.Pool === 'function') {
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
const instance = new pg.Pool();
|
||||
console.log('[DEBUG] connection.test.ts: new pg.Pool() SUCCESS. Instance:', Object.keys(instance));
|
||||
} catch (e) {
|
||||
|
||||
20
src/services/express.d.ts
vendored
Normal file
20
src/services/express.d.ts
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
// src/types/express.d.ts
|
||||
import { Request, Response, NextFunction, RequestHandler } from 'express';
|
||||
|
||||
/**
|
||||
* Defines a more accurate type for asynchronous Express route handlers.
|
||||
* Standard `RequestHandler` requires `next` to be present, which is often unused
|
||||
* in modern async/await handlers, leading to linting errors. This type makes `next` optional.
|
||||
*
|
||||
* It also allows for more specific typing of the request body, query, and params.
|
||||
*/
|
||||
export type AsyncRequestHandler<
|
||||
P = Record<string, string>,
|
||||
ResBody = any,
|
||||
ReqBody = any,
|
||||
ReqQuery = qs.ParsedQs
|
||||
> = (
|
||||
req: Request<P, ResBody, ReqBody, ReqQuery>,
|
||||
res: Response<ResBody>,
|
||||
next?: NextFunction
|
||||
) => Promise<void | unknown>;
|
||||
20
src/types/express.d.ts
vendored
Normal file
20
src/types/express.d.ts
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
// src/types/express.d.ts
|
||||
import { Request, Response, NextFunction, RequestHandler } from 'express';
|
||||
|
||||
/**
|
||||
* Defines a more accurate type for asynchronous Express route handlers.
|
||||
* Standard `RequestHandler` requires `next` to be present, which is often unused
|
||||
* in modern async/await handlers, leading to linting errors. This type makes `next` optional.
|
||||
*
|
||||
* It also allows for more specific typing of the request body, query, and params.
|
||||
*/
|
||||
export type AsyncRequestHandler<
|
||||
P = Record<string, string>,
|
||||
ResBody = any,
|
||||
ReqBody = any,
|
||||
ReqQuery = qs.ParsedQs
|
||||
> = (
|
||||
req: Request<P, ResBody, ReqBody, ReqQuery>,
|
||||
res: Response<ResBody>,
|
||||
next?: NextFunction
|
||||
) => Promise<void | unknown>;
|
||||
Reference in New Issue
Block a user