All files / src/routes personalization.routes.ts

76.21% Statements 125/164
75% Branches 3/4
100% Functions 3/3
77.85% Lines 109/140

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 1412x 2x 2x 2x 2x 2x 2x 2x 2x 24x 2x 2x 2x 2x 24x 2x 2x 24x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x         24x         114x   114x     114x 2x 2x 114x   114x 113x   1x 1x                   2x 2x 2x 2x             24x       2x 3x 3x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 24x 2x 2x 2x 2x 3x 3x 2x 2x 2x 2x 2x 2x 2x 2x 2x  
// src/routes/personalization.routes.ts
import { Router, Request, Response, NextFunction } from 'express';
import { z } from 'zod';
import * as db from '../services/db/index.db';
import { validateRequest } from '../middleware/validation.middleware';
import { publicReadLimiter } from '../config/rateLimiters';
import { sendSuccess } from '../utils/apiResponse';
import { optionalNumeric } from '../utils/zodUtils';
 
const router = Router();
 
// --- Zod Schemas for Personalization Routes (as per ADR-003) ---
// These routes do not expect any input, so we define an empty schema
// to maintain a consistent validation pattern across the application.
const emptySchema = z.object({});
 
// Schema for master-items with optional pagination
const masterItemsSchema = z.object({
  query: z.object({
    limit: optionalNumeric({ integer: true, positive: true, max: 500 }),
    offset: optionalNumeric({ default: 0, integer: true, nonnegative: true }),
  }),
});
 
/**
 * @openapi
 * /personalization/master-items:
 *   get:
 *     tags: [Personalization]
 *     summary: Get master items list
 *     description: Get the master list of all grocery items with optional pagination. Response is cached for 1 hour.
 *     parameters:
 *       - in: query
 *         name: limit
 *         schema:
 *           type: integer
 *           maximum: 500
 *         description: Maximum number of items to return. If omitted, returns all items.
 *       - in: query
 *         name: offset
 *         schema:
 *           type: integer
 *           default: 0
 *         description: Number of items to skip
 *     responses:
 *       200:
 *         description: List of master grocery items with total count
 *         content:
 *           application/json:
 *             schema:
 *               $ref: '#/components/schemas/SuccessResponse'
 */
router.get(
  '/master-items',
  publicReadLimiter,
  validateRequest(masterItemsSchema),
  async (req: Request, res: Response, next: NextFunction) => {
    try {
      // Parse and apply defaults from schema
      const { limit, offset } = masterItemsSchema.shape.query.parse(req.query);

      // LOGGING: Track how often this heavy DB call is actually made vs served from cache
      req.log.info({ limit, offset }, 'Fetching master items list from database...');
 
      // Optimization: This list changes rarely. Instruct clients to cache it for 1 hour (3600s).
      res.set('Cache-Control', 'public, max-age=3600');

      const result = await db.personalizationRepo.getAllMasterItems(req.log, limit, offset);
      sendSuccess(res, result);
    } catch (error) {
      req.log.error({ error }, 'Error fetching master items in /api/personalization/master-items:');
      next(error);
    }
  },
);

/**
 * @openapi
 * /personalization/dietary-restrictions:
 *   get:
 *     tags: [Personalization]
 *     summary: Get dietary restrictions
 *     description: Get the master list of all available dietary restrictions.
 *     responses:
 *       200:
 *         description: List of all dietary restrictions
 *         content:
 *           application/json:
 *             schema:
 *               $ref: '#/components/schemas/SuccessResponse'
 */
router.get(
  '/dietary-restrictions',
  publicReadLimiter,
  validateRequest(emptySchema),
  async (req: Request, res: Response, next: NextFunction) => {
    try {
      const restrictions = await db.personalizationRepo.getDietaryRestrictions(req.log);
      sendSuccess(res, restrictions);
    } catch (error) {
      req.log.error(
        { error },
        'Error fetching dietary restrictions in /api/personalization/dietary-restrictions:',
      );
      next(error);
    }
  },
);
 
/**
 * @openapi
 * /personalization/appliances:
 *   get:
 *     tags: [Personalization]
 *     summary: Get kitchen appliances
 *     description: Get the master list of all available kitchen appliances.
 *     responses:
 *       200:
 *         description: List of all kitchen appliances
 *         content:
 *           application/json:
 *             schema:
 *               $ref: '#/components/schemas/SuccessResponse'
 */
router.get(
  '/appliances',
  publicReadLimiter,
  validateRequest(emptySchema),
  async (req: Request, res: Response, next: NextFunction) => {
    try {
      const appliances = await db.personalizationRepo.getAppliances(req.log);
      sendSuccess(res, appliances);
    } catch (error) {
      req.log.error({ error }, 'Error fetching appliances in /api/personalization/appliances:');
      next(error);
    }
  },
);
 
export default router;