Files
flyer-crawler.projectium.com/src/routes/recipe.routes.ts
Torben Sorensen f73b1422ab
Some checks are pending
Deploy to Test Environment / deploy-to-test (push) Has started running
feat: Update AI service to use new Google Generative AI SDK
- 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.
2025-12-14 17:14:44 -08:00

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;