All files / src/routes price.routes.ts

86.27% Statements 88/102
75% Branches 3/4
100% Functions 1/1
86.02% Lines 80/93

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 942x 2x 2x 2x 2x 2x 2x 2x 2x 2x 24x 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 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x                   24x         2x 2x 2x 2x 10x 10x 2x 2x 2x 10x 10x 9x 2x 2x 2x 2x 2x 2x 2x  
// src/routes/price.routes.ts
import { Router, Request, Response, NextFunction } from 'express';
import { z } from 'zod';
import passport from '../config/passport';
import { validateRequest } from '../middleware/validation.middleware';
import { priceRepo } from '../services/db/price.db';
import { optionalNumeric } from '../utils/zodUtils';
import { priceHistoryLimiter } from '../config/rateLimiters';
import { sendSuccess } from '../utils/apiResponse';
 
const router = Router();
 
const priceHistorySchema = z.object({
  body: z.object({
    masterItemIds: z.array(z.number().int().positive('Number must be greater than 0')).nonempty({
      message: 'masterItemIds must be a non-empty array of positive integers.',
    }),
    limit: optionalNumeric({ default: 1000, integer: true, positive: true }),
    offset: optionalNumeric({ default: 0, integer: true, nonnegative: true }),
  }),
});
 
// Infer the type from the schema for local use, as per ADR-003.
type PriceHistoryRequest = z.infer<typeof priceHistorySchema>;
 
/**
 * @openapi
 * /price-history:
 *   post:
 *     tags: [Price]
 *     summary: Get price history
 *     description: Fetches historical price data for a given list of master item IDs.
 *     security:
 *       - bearerAuth: []
 *     requestBody:
 *       required: true
 *       content:
 *         application/json:
 *           schema:
 *             type: object
 *             required:
 *               - masterItemIds
 *             properties:
 *               masterItemIds:
 *                 type: array
 *                 items:
 *                   type: integer
 *                 minItems: 1
 *                 description: Array of master item IDs to get price history for
 *               limit:
 *                 type: integer
 *                 default: 1000
 *                 description: Maximum number of price points to return
 *               offset:
 *                 type: integer
 *                 default: 0
 *                 description: Number of price points to skip
 *     responses:
 *       200:
 *         description: Historical price data for specified items
 *         content:
 *           application/json:
 *             schema:
 *               $ref: '#/components/schemas/SuccessResponse'
 *       400:
 *         description: Validation error - masterItemIds must be a non-empty array
 *       401:
 *         description: Unauthorized - invalid or missing token
 */
router.post(
  '/',
  passport.authenticate('jwt', { session: false }),
  priceHistoryLimiter,
  validateRequest(priceHistorySchema),
  async (req: Request, res: Response, next: NextFunction) => {
    // Cast 'req' to the inferred type for full type safety.
    const {
      body: { masterItemIds, limit, offset },
    } = req as unknown as PriceHistoryRequest;
    req.log.info(
      { itemCount: masterItemIds.length, limit, offset },
      '[API /price-history] Received request for historical price data.',
    );
    try {
      const priceHistory = await priceRepo.getPriceHistory(masterItemIds, req.log, limit, offset);
      sendSuccess(res, priceHistory);
    } catch (error) {
      next(error);
    }
  },
);
 
export default router;