Some checks are pending
Deploy to Test Environment / deploy-to-test (push) Has started running
- Refactored AIService to integrate with the latest GoogleGenAI SDK, updating the generateContent method signature and response handling. - Adjusted error handling and logging for improved clarity and consistency. - Enhanced mock implementations in tests to align with the new SDK structure. refactor: Modify Admin DB service to use Profile type - Updated AdminRepository to replace User type with Profile in relevant methods. - Enhanced test cases to utilize mock factories for creating Profile and AdminUserView objects. fix: Improve error handling in BudgetRepository - Implemented type-safe checks for PostgreSQL error codes to enhance error handling in createBudget method. test: Refactor Deals DB tests for type safety - Updated DealsRepository tests to use Pool type for mock instances, ensuring type safety. chore: Add new mock factories for testing - Introduced mock factories for UserWithPasswordHash, Profile, WatchedItemDeal, LeaderboardUser, and UnmatchedFlyerItem to streamline test data creation. style: Clean up queue service tests - Refactored queue service tests to improve readability and maintainability, including better handling of mock worker instances. docs: Update types to include UserWithPasswordHash - Added UserWithPasswordHash interface to types for better clarity on user authentication data structure. chore: Remove deprecated Google AI SDK references - Updated code and documentation to reflect the migration to the new Google Generative AI SDK, removing references to the deprecated SDK.
110 lines
3.9 KiB
TypeScript
110 lines
3.9 KiB
TypeScript
// src/routes/recipe.routes.ts
|
|
import { Router } from 'express';
|
|
import { z } from 'zod';
|
|
import * as db from '../services/db/index.db';
|
|
import { validateRequest } from '../middleware/validation.middleware';
|
|
|
|
const router = Router();
|
|
|
|
// --- Zod Schemas for Recipe Routes (as per ADR-003) ---
|
|
|
|
const bySalePercentageSchema = z.object({
|
|
query: z.object({
|
|
minPercentage: z.coerce.number().min(0).max(100).optional().default(50),
|
|
}),
|
|
});
|
|
|
|
const bySaleIngredientsSchema = z.object({
|
|
query: z.object({
|
|
minIngredients: z.coerce.number().int().positive().optional().default(3),
|
|
}),
|
|
});
|
|
|
|
const byIngredientAndTagSchema = z.object({
|
|
query: z.object({
|
|
ingredient: z.string().min(1, 'Query parameter "ingredient" is required.'),
|
|
tag: z.string().min(1, 'Query parameter "tag" is required.'),
|
|
}),
|
|
});
|
|
|
|
const recipeIdParamsSchema = z.object({
|
|
params: z.object({
|
|
recipeId: z.coerce.number().int().positive(),
|
|
}),
|
|
});
|
|
|
|
/**
|
|
* GET /api/recipes/by-sale-percentage - Get recipes based on the percentage of their ingredients on sale.
|
|
*/
|
|
type BySalePercentageRequest = z.infer<typeof bySalePercentageSchema>;
|
|
router.get('/by-sale-percentage', validateRequest(bySalePercentageSchema), async (req, res, next) => {
|
|
try {
|
|
const { query } = req as unknown as BySalePercentageRequest;
|
|
const recipes = await db.recipeRepo.getRecipesBySalePercentage(query.minPercentage, req.log);
|
|
res.json(recipes);
|
|
} catch (error) {
|
|
req.log.error({ error }, 'Error fetching recipes in /api/recipes/by-sale-percentage:');
|
|
next(error);
|
|
}
|
|
});
|
|
|
|
/**
|
|
* GET /api/recipes/by-sale-ingredients - Get recipes by the minimum number of sale ingredients.
|
|
*/
|
|
type BySaleIngredientsRequest = z.infer<typeof bySaleIngredientsSchema>;
|
|
router.get('/by-sale-ingredients', validateRequest(bySaleIngredientsSchema), async (req, res, next) => {
|
|
try {
|
|
const { query } = req as unknown as BySaleIngredientsRequest;
|
|
const recipes = await db.recipeRepo.getRecipesByMinSaleIngredients(query.minIngredients, req.log);
|
|
res.json(recipes);
|
|
} catch (error) {
|
|
req.log.error({ error }, 'Error fetching recipes in /api/recipes/by-sale-ingredients:');
|
|
next(error);
|
|
}
|
|
});
|
|
|
|
/**
|
|
* GET /api/recipes/by-ingredient-and-tag - Find recipes by a specific ingredient and tag.
|
|
*/
|
|
type ByIngredientAndTagRequest = z.infer<typeof byIngredientAndTagSchema>;
|
|
router.get('/by-ingredient-and-tag', validateRequest(byIngredientAndTagSchema), async (req, res, next) => {
|
|
try {
|
|
const { query } = req as unknown as ByIngredientAndTagRequest;
|
|
const recipes = await db.recipeRepo.findRecipesByIngredientAndTag(query.ingredient, query.tag, req.log);
|
|
res.json(recipes);
|
|
} catch (error) {
|
|
req.log.error({ error }, 'Error fetching recipes in /api/recipes/by-ingredient-and-tag:');
|
|
next(error);
|
|
}
|
|
});
|
|
|
|
/**
|
|
* GET /api/recipes/:recipeId/comments - Get all comments for a specific recipe.
|
|
*/
|
|
type RecipeIdRequest = z.infer<typeof recipeIdParamsSchema>;
|
|
router.get('/:recipeId/comments', validateRequest(recipeIdParamsSchema), async (req, res, next) => {
|
|
try {
|
|
const { params } = req as unknown as RecipeIdRequest;
|
|
const comments = await db.recipeRepo.getRecipeComments(params.recipeId, req.log);
|
|
res.json(comments);
|
|
} catch (error) {
|
|
req.log.error({ error }, `Error fetching comments for recipe ID ${req.params.recipeId}:`);
|
|
next(error);
|
|
}
|
|
});
|
|
|
|
/**
|
|
* GET /api/recipes/:recipeId - Get a single recipe by its ID, including ingredients and tags.
|
|
*/
|
|
router.get('/:recipeId', validateRequest(recipeIdParamsSchema), async (req, res, next) => {
|
|
try {
|
|
const { params } = req as unknown as RecipeIdRequest;
|
|
const recipe = await db.recipeRepo.getRecipeById(params.recipeId, req.log);
|
|
res.json(recipe);
|
|
} catch (error) {
|
|
req.log.error({ error }, `Error fetching recipe ID ${req.params.recipeId}:`);
|
|
next(error);
|
|
}
|
|
});
|
|
|
|
export default router; |