refactor: simplify async route handlers by making 'next' optional
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
// src/pages/admin/components/SystemCheck.tsx
|
// src/pages/admin/components/SystemCheck.tsx
|
||||||
|
/// <reference types="vite/client" />
|
||||||
import React, { useState, useEffect, useCallback } from 'react';
|
import React, { useState, useEffect, useCallback } from 'react';
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
import { loginUser, pingBackend, checkDbSchema, checkStorage, checkDbPoolHealth, checkPm2Status, checkRedisHealth, triggerFailingJob, clearGeocodeCache } from '../../../services/apiClient';
|
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 * as db from '../services/db';
|
||||||
import { logger } from '../services/logger.server';
|
import { logger } from '../services/logger.server';
|
||||||
import { UserProfile } from '../types';
|
import { UserProfile } from '../types';
|
||||||
|
import { AsyncRequestHandler } from '../types/express';
|
||||||
import { clearGeocodeCache } from '../services/geocodingService.server';
|
import { clearGeocodeCache } from '../services/geocodingService.server';
|
||||||
|
|
||||||
// --- Bull Board (Job Queue UI) Imports ---
|
// --- Bull Board (Job Queue UI) Imports ---
|
||||||
@@ -55,27 +56,27 @@ router.use(passport.authenticate('jwt', { session: false }), isAdmin);
|
|||||||
|
|
||||||
// --- Admin Routes ---
|
// --- Admin Routes ---
|
||||||
|
|
||||||
router.get('/corrections', async (req: Request, res: Response, next: NextFunction) => {
|
router.get('/corrections', async (req, res, next) => {
|
||||||
const corrections = await db.getSuggestedCorrections();
|
const corrections = await db.getSuggestedCorrections();
|
||||||
res.json(corrections);
|
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();
|
const brands = await db.getAllBrands();
|
||||||
res.json(brands);
|
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();
|
const stats = await db.getApplicationStats();
|
||||||
res.json(stats);
|
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();
|
const dailyStats = await db.getDailyStatsForLast30Days();
|
||||||
res.json(dailyStats);
|
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);
|
const correctionId = parseInt(req.params.id, 10);
|
||||||
// Add validation to ensure the ID is a valid number.
|
// Add validation to ensure the ID is a valid number.
|
||||||
if (isNaN(correctionId)) {
|
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.' });
|
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);
|
const correctionId = parseInt(req.params.id, 10);
|
||||||
await db.rejectCorrection(correctionId);
|
await db.rejectCorrection(correctionId);
|
||||||
res.status(200).json({ message: 'Correction rejected successfully.' });
|
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 correctionId = parseInt(req.params.id, 10);
|
||||||
const { suggested_value } = req.body;
|
const { suggested_value } = req.body;
|
||||||
if (!suggested_value) {
|
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 recipeId = parseInt(req.params.id, 10);
|
||||||
const { status } = req.body;
|
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);
|
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);
|
const brandId = parseInt(req.params.id, 10);
|
||||||
if (!req.file) {
|
if (!req.file) {
|
||||||
return res.status(400).json({ message: 'Logo image file is required.' });
|
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 });
|
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();
|
const items = await db.getUnmatchedFlyerItems();
|
||||||
res.json(items);
|
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 commentId = parseInt(req.params.id, 10);
|
||||||
const { status } = req.body;
|
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);
|
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();
|
const users = await db.getAllUsers();
|
||||||
res.json(users);
|
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 limit = parseInt(req.query.limit as string, 10) || 50;
|
||||||
const offset = parseInt(req.query.offset as string, 10) || 0;
|
const offset = parseInt(req.query.offset as string, 10) || 0;
|
||||||
const logs = await db.getActivityLog(limit, offset);
|
const logs = await db.getActivityLog(limit, offset);
|
||||||
res.json(logs);
|
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);
|
const user = await db.findUserProfileById(req.params.id);
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return res.status(404).json({ message: 'User not found.' });
|
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);
|
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;
|
const { role } = req.body;
|
||||||
if (!role || !['user', 'admin'].includes(role)) {
|
if (!role || !['user', 'admin'].includes(role)) {
|
||||||
return res.status(400).json({ message: 'A valid role ("user" or "admin") is required.' });
|
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;
|
const adminUser = req.user as UserProfile;
|
||||||
if (adminUser.user.user_id === req.params.id) {
|
if (adminUser.user.user_id === req.params.id) {
|
||||||
return res.status(400).json({ message: 'Admins cannot delete their own account.' });
|
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.
|
* 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.
|
* 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;
|
const adminUser = req.user as UserProfile;
|
||||||
logger.info(`[Admin] Manual trigger for daily deal check received from user: ${adminUser.user_id}`);
|
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.
|
* 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.
|
* 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;
|
const adminUser = req.user as UserProfile;
|
||||||
logger.info(`[Admin] Manual trigger for analytics report generation received from user: ${adminUser.user_id}`);
|
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.
|
* 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.
|
* 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 adminUser = req.user as UserProfile;
|
||||||
const flyerId = parseInt(req.params.flyerId, 10);
|
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.
|
* POST /api/admin/trigger/failing-job - Enqueue a test job designed to fail.
|
||||||
* This is for testing the retry mechanism and Bull Board UI.
|
* 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;
|
const adminUser = req.user as UserProfile;
|
||||||
logger.info(`[Admin] Manual trigger for a failing job received from user: ${adminUser.user_id}`);
|
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.
|
* POST /api/admin/system/clear-geocode-cache - Clears the Redis cache for geocoded addresses.
|
||||||
* Requires admin privileges.
|
* 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;
|
const adminUser = req.user as UserProfile;
|
||||||
logger.info(`[Admin] Manual trigger for geocode cache clear received from user: ${adminUser.user_id}`);
|
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 { generateFlyerIcon } from '../utils/imageProcessor';
|
||||||
import { logger } from '../services/logger.server';
|
import { logger } from '../services/logger.server';
|
||||||
import { UserProfile } from '../types';
|
import { UserProfile } from '../types';
|
||||||
|
import { AsyncRequestHandler } from '../types/express';
|
||||||
import { flyerQueue } from '../services/queueService.server';
|
import { flyerQueue } from '../services/queueService.server';
|
||||||
|
|
||||||
const router = Router();
|
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
|
* NEW ENDPOINT: Accepts a single flyer file (PDF or image), enqueues it for
|
||||||
* background processing, and immediately returns a job ID.
|
* 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 {
|
try {
|
||||||
if (!req.file) {
|
if (!req.file) {
|
||||||
return res.status(400).json({ message: 'A flyer file (PDF or image) is required.' });
|
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.
|
* in the flyer upload workflow after the AI has extracted the data.
|
||||||
* It uses `optionalAuth` to handle submissions from both anonymous and authenticated users.
|
* 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 {
|
try {
|
||||||
if (!req.file) {
|
if (!req.file) {
|
||||||
return res.status(400).json({ message: 'Flyer image file is required.' });
|
return res.status(400).json({ message: 'Flyer image file is required.' });
|
||||||
@@ -389,7 +390,7 @@ router.post(
|
|||||||
'/rescan-area',
|
'/rescan-area',
|
||||||
passport.authenticate('jwt', { session: false }),
|
passport.authenticate('jwt', { session: false }),
|
||||||
uploadToDisk.single('image'),
|
uploadToDisk.single('image'),
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
if (!req.file) {
|
if (!req.file) {
|
||||||
return res.status(400).json({ message: 'Image file is required.' });
|
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 * as db from '../services/db';
|
||||||
import { getPool } from '../services/db/connection';
|
import { getPool } from '../services/db/connection';
|
||||||
import { logger } from '../services/logger.server';
|
import { logger } from '../services/logger.server';
|
||||||
|
import { AsyncRequestHandler } from '../types/express';
|
||||||
import { sendPasswordResetEmail } from '../services/emailService.server';
|
import { sendPasswordResetEmail } from '../services/emailService.server';
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
@@ -41,7 +42,7 @@ const resetPasswordLimiter = rateLimit({
|
|||||||
// --- Authentication Routes ---
|
// --- Authentication Routes ---
|
||||||
|
|
||||||
// Registration Route
|
// 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;
|
const { email, password, full_name, avatar_url } = req.body;
|
||||||
|
|
||||||
if (!email || !password) {
|
if (!email || !password) {
|
||||||
@@ -146,7 +147,7 @@ router.post('/login', (req: Request, res: Response, next: NextFunction) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Route to request a password reset
|
// 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;
|
const { email } = req.body;
|
||||||
if (!email) {
|
if (!email) {
|
||||||
return res.status(400).json({ message: 'Email is required.' });
|
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
|
// 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;
|
const { token, newPassword } = req.body;
|
||||||
if (!token || !newPassword) {
|
if (!token || !newPassword) {
|
||||||
return res.status(400).json({ message: 'Token and new password are required.' });
|
return res.status(400).json({ message: 'Token and new password are required.' });
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
} from '../services/db';
|
} from '../services/db';
|
||||||
import { logger } from '../services/logger.server';
|
import { logger } from '../services/logger.server';
|
||||||
import { UserProfile } from '../types';
|
import { UserProfile } from '../types';
|
||||||
|
import { AsyncRequestHandler } from '../types/express';
|
||||||
|
|
||||||
const router = express.Router();
|
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.
|
* 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;
|
const user = req.user as UserProfile;
|
||||||
try {
|
try {
|
||||||
const budgets = await getBudgetsForUser(user.user_id);
|
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.
|
* 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;
|
const user = req.user as UserProfile;
|
||||||
try {
|
try {
|
||||||
const newBudget = await createBudget(user.user_id, req.body);
|
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.
|
* 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 user = req.user as UserProfile;
|
||||||
const budgetId = parseInt(req.params.id, 10);
|
const budgetId = parseInt(req.params.id, 10);
|
||||||
try {
|
try {
|
||||||
@@ -62,7 +63,7 @@ router.put('/:id', async (req: Request, res: Response, next: NextFunction) => {
|
|||||||
/**
|
/**
|
||||||
* DELETE /api/budgets/:id - Delete a budget.
|
* 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 user = req.user as UserProfile;
|
||||||
const budgetId = parseInt(req.params.id, 10);
|
const budgetId = parseInt(req.params.id, 10);
|
||||||
try {
|
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.
|
* GET /api/spending-analysis - Get spending breakdown by category for a date range.
|
||||||
* Query params: startDate (YYYY-MM-DD), endDate (YYYY-MM-DD)
|
* 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 user = req.user as UserProfile;
|
||||||
const { startDate, endDate } = req.query;
|
const { startDate, endDate } = req.query;
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import passport, { isAdmin } from './passport';
|
|||||||
import { getAllAchievements, getUserAchievements, awardAchievement, getLeaderboard } from '../services/db';
|
import { getAllAchievements, getUserAchievements, awardAchievement, getLeaderboard } from '../services/db';
|
||||||
import { logger } from '../services/logger.server';
|
import { logger } from '../services/logger.server';
|
||||||
import { UserProfile } from '../types';
|
import { UserProfile } from '../types';
|
||||||
|
import { AsyncRequestHandler } from '../types/express';
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
@@ -11,7 +12,7 @@ const router = express.Router();
|
|||||||
* GET /api/achievements - Get the master list of all available achievements.
|
* GET /api/achievements - Get the master list of all available achievements.
|
||||||
* This is a public endpoint.
|
* This is a public endpoint.
|
||||||
*/
|
*/
|
||||||
router.get('/', async (req: Request, res: Response, next: NextFunction) => {
|
router.get('/', async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
const achievements = await getAllAchievements();
|
const achievements = await getAllAchievements();
|
||||||
res.json(achievements);
|
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.
|
* GET /api/achievements/leaderboard - Get the top users by points.
|
||||||
* This is a public endpoint.
|
* 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.
|
// 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);
|
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(
|
router.get(
|
||||||
'/me',
|
'/me',
|
||||||
passport.authenticate('jwt', { session: false }),
|
passport.authenticate('jwt', { session: false }),
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
async (req, res, next) => {
|
||||||
const user = req.user as UserProfile;
|
const user = req.user as UserProfile;
|
||||||
try {
|
try {
|
||||||
const userAchievements = await getUserAchievements(user.user_id);
|
const userAchievements = await getUserAchievements(user.user_id);
|
||||||
@@ -65,7 +66,7 @@ router.post(
|
|||||||
'/award',
|
'/award',
|
||||||
passport.authenticate('jwt', { session: false }),
|
passport.authenticate('jwt', { session: false }),
|
||||||
isAdmin,
|
isAdmin,
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
async (req, res, next) => {
|
||||||
const { userId, achievementName } = req.body;
|
const { userId, achievementName } = req.body;
|
||||||
|
|
||||||
if (!userId || !achievementName) {
|
if (!userId || !achievementName) {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import * as db from '../services/db';
|
|||||||
import { logger } from '../services/logger.server';
|
import { logger } from '../services/logger.server';
|
||||||
import fs from 'fs/promises';
|
import fs from 'fs/promises';
|
||||||
import passport from 'passport';
|
import passport from 'passport';
|
||||||
|
import { AsyncRequestHandler } from '../types/express';
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
@@ -12,7 +13,7 @@ router.get('/health/ping', (req: Request, res: Response) => {
|
|||||||
res.status(200).send('pong');
|
res.status(200).send('pong');
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get('/health/db-schema', async (req: Request, res: Response) => {
|
router.get('/health/db-schema', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const requiredTables = ['users', 'profiles', 'flyers', 'flyer_items', 'stores'];
|
const requiredTables = ['users', 'profiles', 'flyers', 'flyer_items', 'stores'];
|
||||||
const missingTables = await db.checkTablesExist(requiredTables);
|
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';
|
const storagePath = process.env.STORAGE_PATH || '/var/www/flyer-crawler.projectium.com/assets';
|
||||||
try {
|
try {
|
||||||
await fs.access(storagePath, fs.constants.W_OK);
|
await fs.access(storagePath, fs.constants.W_OK);
|
||||||
@@ -58,7 +59,7 @@ router.get('/health/db-pool', (req: Request, res: Response) => {
|
|||||||
|
|
||||||
// --- Public Data Routes ---
|
// --- Public Data Routes ---
|
||||||
|
|
||||||
router.get('/flyers', async (req: Request, res: Response, next: NextFunction) => {
|
router.get('/flyers', async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
const flyers = await db.getFlyers();
|
const flyers = await db.getFlyers();
|
||||||
res.json(flyers);
|
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 {
|
try {
|
||||||
const masterItems = await db.getAllMasterItems();
|
const masterItems = await db.getAllMasterItems();
|
||||||
res.json(masterItems);
|
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 {
|
try {
|
||||||
const flyerId = parseInt(req.params.id, 10);
|
const flyerId = parseInt(req.params.id, 10);
|
||||||
const items = await db.getFlyerItems(flyerId);
|
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;
|
const { flyerIds } = req.body;
|
||||||
if (!Array.isArray(flyerIds)) {
|
if (!Array.isArray(flyerIds)) {
|
||||||
return res.status(400).json({ message: 'flyerIds must be an array.' });
|
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;
|
const { flyerIds } = req.body;
|
||||||
if (!Array.isArray(flyerIds)) {
|
if (!Array.isArray(flyerIds)) {
|
||||||
return res.status(400).json({ message: 'flyerIds must be an array.' });
|
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 minPercentageStr = req.query.minPercentage as string || '50.0';
|
||||||
const minPercentage = parseFloat(minPercentageStr);
|
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 minIngredientsStr = req.query.minIngredients as string || '3';
|
||||||
const minIngredients = parseInt(minIngredientsStr, 10);
|
const minIngredients = parseInt(minIngredientsStr, 10);
|
||||||
|
|
||||||
@@ -141,7 +142,7 @@ router.get('/recipes/by-sale-ingredients', async (req: Request, res: Response, n
|
|||||||
res.json(recipes);
|
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;
|
const { ingredient, tag } = req.query;
|
||||||
if (!ingredient || !tag) {
|
if (!ingredient || !tag) {
|
||||||
return res.status(400).json({ message: 'Both "ingredient" and "tag" query parameters are required.' });
|
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);
|
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 daysStr = req.query.days as string || '30';
|
||||||
const limitStr = req.query.limit as string || '10';
|
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);
|
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 recipeId = parseInt(req.params.recipeId, 10);
|
||||||
const comments = await db.getRecipeComments(recipeId);
|
const comments = await db.getRecipeComments(recipeId);
|
||||||
res.json(comments);
|
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();
|
const restrictions = await db.getDietaryRestrictions();
|
||||||
res.json(restrictions);
|
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();
|
const appliances = await db.getAppliances();
|
||||||
res.json(appliances);
|
res.json(appliances);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import zxcvbn from 'zxcvbn';
|
|||||||
import * as db from '../services/db';
|
import * as db from '../services/db';
|
||||||
import { logger } from '../services/logger.server';
|
import { logger } from '../services/logger.server';
|
||||||
import { User, UserProfile, Address } from '../types';
|
import { User, UserProfile, Address } from '../types';
|
||||||
|
import { AsyncRequestHandler } from '../types/express';
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
@@ -27,7 +28,7 @@ fs.mkdir(avatarUploadDir, { recursive: true }).catch(err => {
|
|||||||
*/
|
*/
|
||||||
router.post(
|
router.post(
|
||||||
'/profile/avatar',
|
'/profile/avatar',
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
async (req, res, next) => {
|
||||||
// Initialize multer inside the route handler where req.user is available.
|
// Initialize multer inside the route handler where req.user is available.
|
||||||
// This prevents the "Cannot read properties of undefined" error during test setup.
|
// This prevents the "Cannot read properties of undefined" error during test setup.
|
||||||
const storage = multer.diskStorage({
|
const storage = multer.diskStorage({
|
||||||
@@ -120,7 +121,7 @@ router.post(
|
|||||||
/**
|
/**
|
||||||
* GET /api/users/profile - Get the full profile for the authenticated user.
|
* 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`);
|
logger.debug(`[ROUTE] GET /api/users/profile - ENTER`);
|
||||||
const user = req.user as UserProfile;
|
const user = req.user as UserProfile;
|
||||||
try {
|
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.
|
* 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`);
|
logger.debug(`[ROUTE] PUT /api/users/profile - ENTER`);
|
||||||
const user = req.user as UserProfile;
|
const user = req.user as UserProfile;
|
||||||
const { full_name, avatar_url } = req.body;
|
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.
|
* 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`);
|
logger.debug(`[ROUTE] PUT /api/users/profile/password - ENTER`);
|
||||||
const user = req.user as UserProfile;
|
const user = req.user as UserProfile;
|
||||||
const { newPassword } = req.body;
|
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.
|
* 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`);
|
logger.debug(`[ROUTE] DELETE /api/users/account - ENTER`);
|
||||||
const user = req.user as UserProfile;
|
const user = req.user as UserProfile;
|
||||||
const { password } = req.body;
|
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.
|
* 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`);
|
logger.debug(`[ROUTE] GET /api/users/watched-items - ENTER`);
|
||||||
const user = req.user as UserProfile;
|
const user = req.user as UserProfile;
|
||||||
try {
|
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.
|
* 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`);
|
logger.debug(`[ROUTE] POST /api/users/watched-items - ENTER`);
|
||||||
const user = req.user as UserProfile;
|
const user = req.user as UserProfile;
|
||||||
const { itemName, category } = req.body;
|
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.
|
* 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`);
|
logger.debug(`[ROUTE] DELETE /api/users/watched-items/:masterItemId - ENTER`);
|
||||||
const user = req.user as UserProfile;
|
const user = req.user as UserProfile;
|
||||||
const masterItemId = parseInt(req.params.masterItemId, 10);
|
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.
|
* 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`);
|
logger.debug(`[ROUTE] GET /api/users/shopping-lists - ENTER`);
|
||||||
const user = req.user as UserProfile;
|
const user = req.user as UserProfile;
|
||||||
try {
|
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.
|
* 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`);
|
logger.debug(`[ROUTE] POST /api/users/shopping-lists - ENTER`);
|
||||||
const user = req.user as UserProfile;
|
const user = req.user as UserProfile;
|
||||||
const { name } = req.body;
|
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.
|
* 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`);
|
logger.debug(`[ROUTE] DELETE /api/users/shopping-lists/:listId - ENTER`);
|
||||||
const user = req.user as UserProfile;
|
const user = req.user as UserProfile;
|
||||||
const listId = parseInt(req.params.listId, 10);
|
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.
|
* 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`);
|
logger.debug(`[ROUTE] POST /api/users/shopping-lists/:listId/items - ENTER`);
|
||||||
const listId = parseInt(req.params.listId, 10);
|
const listId = parseInt(req.params.listId, 10);
|
||||||
try {
|
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.
|
* 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`);
|
logger.debug(`[ROUTE] PUT /api/users/shopping-lists/items/:itemId - ENTER`);
|
||||||
const itemId = parseInt(req.params.itemId, 10);
|
const itemId = parseInt(req.params.itemId, 10);
|
||||||
try {
|
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.
|
* 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`);
|
logger.debug(`[ROUTE] DELETE /api/users/shopping-lists/items/:itemId - ENTER`);
|
||||||
const itemId = parseInt(req.params.itemId, 10);
|
const itemId = parseInt(req.params.itemId, 10);
|
||||||
try {
|
try {
|
||||||
@@ -361,7 +362,7 @@ router.delete('/shopping-lists/items/:itemId', async (req: Request, res: Respons
|
|||||||
/**
|
/**
|
||||||
* PUT /api/users/profile/preferences - Update user preferences.
|
* 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`);
|
logger.debug(`[ROUTE] PUT /api/users/profile/preferences - ENTER`);
|
||||||
const user = req.user as UserProfile;
|
const user = req.user as UserProfile;
|
||||||
if (typeof req.body !== 'object' || req.body === null || Array.isArray(req.body)) {
|
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`);
|
logger.debug(`[ROUTE] GET /api/users/me/dietary-restrictions - ENTER`);
|
||||||
const user = req.user as UserProfile;
|
const user = req.user as UserProfile;
|
||||||
try {
|
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`);
|
logger.debug(`[ROUTE] PUT /api/users/me/dietary-restrictions - ENTER`);
|
||||||
const user = req.user as UserProfile;
|
const user = req.user as UserProfile;
|
||||||
const { restrictionIds } = req.body;
|
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`);
|
logger.debug(`[ROUTE] GET /api/users/me/appliances - ENTER`);
|
||||||
const user = req.user as UserProfile;
|
const user = req.user as UserProfile;
|
||||||
try {
|
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`);
|
logger.debug(`[ROUTE] PUT /api/users/me/appliances - ENTER`);
|
||||||
const user = req.user as UserProfile;
|
const user = req.user as UserProfile;
|
||||||
const { applianceIds } = req.body;
|
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.
|
* 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.
|
* 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 user = req.user as UserProfile;
|
||||||
const addressId = parseInt(req.params.addressId, 10);
|
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.
|
* 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 user = req.user as UserProfile;
|
||||||
const addressData = req.body as Partial<Address>;
|
const addressData = req.body as Partial<Address>;
|
||||||
|
|
||||||
|
|||||||
@@ -25,8 +25,6 @@ describe('DB Connection Service', () => {
|
|||||||
|
|
||||||
if (typeof pg.Pool === 'function') {
|
if (typeof pg.Pool === 'function') {
|
||||||
try {
|
try {
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore
|
|
||||||
const instance = new pg.Pool();
|
const instance = new pg.Pool();
|
||||||
console.log('[DEBUG] connection.test.ts: new pg.Pool() SUCCESS. Instance:', Object.keys(instance));
|
console.log('[DEBUG] connection.test.ts: new pg.Pool() SUCCESS. Instance:', Object.keys(instance));
|
||||||
} catch (e) {
|
} 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>;
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "ES2022",
|
"target": "ES2022",
|
||||||
|
"typeRoots": ["./node_modules/@types", "./src/types"],
|
||||||
"experimentalDecorators": true,
|
"experimentalDecorators": true,
|
||||||
"useDefineForClassFields": false,
|
"useDefineForClassFields": false,
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
|
|||||||
Reference in New Issue
Block a user