Datei-Struktur ordentlich bereinigt
This commit is contained in:
		
							
								
								
									
										3
									
								
								backend/dist/routes/health.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								backend/dist/routes/health.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| declare const router: import("express-serve-static-core").Router; | ||||
| export default router; | ||||
| //# sourceMappingURL=health.d.ts.map | ||||
							
								
								
									
										1
									
								
								backend/dist/routes/health.d.ts.map
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								backend/dist/routes/health.d.ts.map
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| {"version":3,"file":"health.d.ts","sourceRoot":"","sources":["../../src/routes/health.ts"],"names":[],"mappings":"AAEA,QAAA,MAAM,MAAM,4CAAW,CAAC;AA8BxB,eAAe,MAAM,CAAC"} | ||||
							
								
								
									
										30
									
								
								backend/dist/routes/health.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								backend/dist/routes/health.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | ||||
| "use strict"; | ||||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||||
| const express_1 = require("express"); | ||||
| const router = (0, express_1.Router)(); | ||||
| router.get('/', (req, res) => { | ||||
|     res.json({ | ||||
|         success: true, | ||||
|         message: 'Rezepte API is running!', | ||||
|         timestamp: new Date().toISOString(), | ||||
|         environment: process.env.NODE_ENV, | ||||
|     }); | ||||
| }); | ||||
| router.get('/db', async (req, res) => { | ||||
|     try { | ||||
|         res.json({ | ||||
|             success: true, | ||||
|             message: 'Database connection is healthy', | ||||
|             timestamp: new Date().toISOString(), | ||||
|         }); | ||||
|     } | ||||
|     catch (error) { | ||||
|         res.status(500).json({ | ||||
|             success: false, | ||||
|             message: 'Database connection failed', | ||||
|             error: error instanceof Error ? error.message : 'Unknown error', | ||||
|         }); | ||||
|     } | ||||
| }); | ||||
| exports.default = router; | ||||
| //# sourceMappingURL=health.js.map | ||||
							
								
								
									
										1
									
								
								backend/dist/routes/health.js.map
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								backend/dist/routes/health.js.map
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| {"version":3,"file":"health.js","sourceRoot":"","sources":["../../src/routes/health.ts"],"names":[],"mappings":";;AAAA,qCAAoD;AAEpD,MAAM,MAAM,GAAG,IAAA,gBAAM,GAAE,CAAC;AAGxB,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,GAAY,EAAE,GAAa,EAAE,EAAE;IAC9C,GAAG,CAAC,IAAI,CAAC;QACP,OAAO,EAAE,IAAI;QACb,OAAO,EAAE,+BAA+B;QACxC,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QACnC,WAAW,EAAE,OAAO,CAAC,GAAG,CAAC,QAAQ;KAClC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAGH,MAAM,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,GAAY,EAAE,GAAa,EAAE,EAAE;IACtD,IAAI,CAAC;QAEH,GAAG,CAAC,IAAI,CAAC;YACP,OAAO,EAAE,IAAI;YACb,OAAO,EAAE,gCAAgC;YACzC,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;SACpC,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;YACnB,OAAO,EAAE,KAAK;YACd,OAAO,EAAE,4BAA4B;YACrC,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe;SAChE,CAAC,CAAC;IACL,CAAC;AACH,CAAC,CAAC,CAAC;AAEH,kBAAe,MAAM,CAAC"} | ||||
							
								
								
									
										3
									
								
								backend/dist/routes/images.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								backend/dist/routes/images.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| declare const router: import("express-serve-static-core").Router; | ||||
| export default router; | ||||
| //# sourceMappingURL=images.d.ts.map | ||||
							
								
								
									
										1
									
								
								backend/dist/routes/images.d.ts.map
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								backend/dist/routes/images.d.ts.map
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| {"version":3,"file":"images.d.ts","sourceRoot":"","sources":["../../src/routes/images.ts"],"names":[],"mappings":"AAOA,QAAA,MAAM,MAAM,4CAAW,CAAC;AAqQxB,eAAe,MAAM,CAAC"} | ||||
							
								
								
									
										225
									
								
								backend/dist/routes/images.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										225
									
								
								backend/dist/routes/images.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,225 @@ | ||||
| "use strict"; | ||||
| var __importDefault = (this && this.__importDefault) || function (mod) { | ||||
|     return (mod && mod.__esModule) ? mod : { "default": mod }; | ||||
| }; | ||||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||||
| const express_1 = require("express"); | ||||
| const client_1 = require("@prisma/client"); | ||||
| const multer_1 = __importDefault(require("multer")); | ||||
| const path_1 = __importDefault(require("path")); | ||||
| const fs_1 = __importDefault(require("fs")); | ||||
| const config_1 = require("../config/config"); | ||||
| const router = (0, express_1.Router)(); | ||||
| const prisma = new client_1.PrismaClient(); | ||||
| const storage = multer_1.default.diskStorage({ | ||||
|     destination: (req, file, cb) => { | ||||
|         const recipeNumber = req.body.recipeNumber || req.params.recipeNumber; | ||||
|         if (!recipeNumber) { | ||||
|             return cb(new Error('Recipe number is required'), ''); | ||||
|         } | ||||
|         const uploadDir = path_1.default.join(process.cwd(), '../../uploads', recipeNumber); | ||||
|         if (!fs_1.default.existsSync(uploadDir)) { | ||||
|             fs_1.default.mkdirSync(uploadDir, { recursive: true }); | ||||
|         } | ||||
|         cb(null, uploadDir); | ||||
|     }, | ||||
|     filename: (req, file, cb) => { | ||||
|         const recipeNumber = req.body.recipeNumber || req.params.recipeNumber; | ||||
|         if (!recipeNumber) { | ||||
|             return cb(new Error('Recipe number is required'), ''); | ||||
|         } | ||||
|         const uploadDir = path_1.default.join(process.cwd(), '../../uploads', recipeNumber); | ||||
|         const existingFiles = fs_1.default.existsSync(uploadDir) | ||||
|             ? fs_1.default.readdirSync(uploadDir).filter(f => f.match(new RegExp(`^${recipeNumber}_\\d+\\.jpg$`))) | ||||
|             : []; | ||||
|         const nextIndex = existingFiles.length; | ||||
|         const filename = `${recipeNumber}_${nextIndex}.jpg`; | ||||
|         cb(null, filename); | ||||
|     } | ||||
| }); | ||||
| const upload = (0, multer_1.default)({ | ||||
|     storage, | ||||
|     limits: { | ||||
|         fileSize: config_1.config.upload.maxFileSize, | ||||
|     }, | ||||
|     fileFilter: (req, file, cb) => { | ||||
|         const allowedTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/webp']; | ||||
|         if (allowedTypes.includes(file.mimetype)) { | ||||
|             cb(null, true); | ||||
|         } | ||||
|         else { | ||||
|             cb(new Error('Invalid file type. Only JPEG, PNG and WebP are allowed.')); | ||||
|         } | ||||
|     }, | ||||
| }); | ||||
| router.post('/upload/:recipeId', upload.array('images', 10), async (req, res, next) => { | ||||
|     try { | ||||
|         const { recipeId } = req.params; | ||||
|         const files = req.files; | ||||
|         if (!recipeId) { | ||||
|             return res.status(400).json({ | ||||
|                 success: false, | ||||
|                 message: 'Recipe ID is required', | ||||
|             }); | ||||
|         } | ||||
|         if (!files || files.length === 0) { | ||||
|             return res.status(400).json({ | ||||
|                 success: false, | ||||
|                 message: 'No files uploaded', | ||||
|             }); | ||||
|         } | ||||
|         const recipe = await prisma.recipe.findUnique({ | ||||
|             where: { id: parseInt(recipeId) } | ||||
|         }); | ||||
|         if (!recipe) { | ||||
|             return res.status(404).json({ | ||||
|                 success: false, | ||||
|                 message: 'Recipe not found', | ||||
|             }); | ||||
|         } | ||||
|         const imagePromises = files.map(file => { | ||||
|             const relativePath = `uploads/${recipe.recipeNumber}/${file.filename}`; | ||||
|             return prisma.recipeImage.create({ | ||||
|                 data: { | ||||
|                     recipeId: parseInt(recipeId), | ||||
|                     filePath: relativePath, | ||||
|                 } | ||||
|             }); | ||||
|         }); | ||||
|         const images = await Promise.all(imagePromises); | ||||
|         return res.status(201).json({ | ||||
|             success: true, | ||||
|             data: images, | ||||
|             message: `${files.length} images uploaded successfully`, | ||||
|         }); | ||||
|     } | ||||
|     catch (error) { | ||||
|         if (req.files) { | ||||
|             const files = req.files; | ||||
|             files.forEach(file => { | ||||
|                 if (fs_1.default.existsSync(file.path)) { | ||||
|                     fs_1.default.unlinkSync(file.path); | ||||
|                 } | ||||
|             }); | ||||
|         } | ||||
|         next(error); | ||||
|     } | ||||
| }); | ||||
| router.delete('/:id', async (req, res, next) => { | ||||
|     try { | ||||
|         const { id } = req.params; | ||||
|         if (!id) { | ||||
|             return res.status(400).json({ | ||||
|                 success: false, | ||||
|                 message: 'Image ID is required', | ||||
|             }); | ||||
|         } | ||||
|         const image = await prisma.recipeImage.findUnique({ | ||||
|             where: { id: parseInt(id) } | ||||
|         }); | ||||
|         if (!image) { | ||||
|             return res.status(404).json({ | ||||
|                 success: false, | ||||
|                 message: 'Image not found', | ||||
|             }); | ||||
|         } | ||||
|         const fullPath = path_1.default.join(process.cwd(), '../..', image.filePath); | ||||
|         if (fs_1.default.existsSync(fullPath)) { | ||||
|             fs_1.default.unlinkSync(fullPath); | ||||
|         } | ||||
|         await prisma.recipeImage.delete({ | ||||
|             where: { id: parseInt(id) } | ||||
|         }); | ||||
|         return res.json({ | ||||
|             success: true, | ||||
|             message: 'Image deleted successfully', | ||||
|         }); | ||||
|     } | ||||
|     catch (error) { | ||||
|         next(error); | ||||
|     } | ||||
| }); | ||||
| router.get('/recipe/:recipeId', async (req, res, next) => { | ||||
|     try { | ||||
|         const { recipeId } = req.params; | ||||
|         if (!recipeId) { | ||||
|             return res.status(400).json({ | ||||
|                 success: false, | ||||
|                 message: 'Recipe ID is required', | ||||
|             }); | ||||
|         } | ||||
|         const images = await prisma.recipeImage.findMany({ | ||||
|             where: { recipeId: parseInt(recipeId) }, | ||||
|             orderBy: { id: 'asc' } | ||||
|         }); | ||||
|         return res.json({ | ||||
|             success: true, | ||||
|             data: images, | ||||
|         }); | ||||
|     } | ||||
|     catch (error) { | ||||
|         next(error); | ||||
|     } | ||||
| }); | ||||
| router.get('/serve/:imagePath(*)', (req, res, next) => { | ||||
|     try { | ||||
|         const imagePath = req.params.imagePath; | ||||
|         if (!imagePath) { | ||||
|             return res.status(400).json({ | ||||
|                 success: false, | ||||
|                 message: 'Image path is required', | ||||
|             }); | ||||
|         } | ||||
|         const cleanPath = imagePath.replace(/^uploads\//, ''); | ||||
|         const fullPath = path_1.default.join(process.cwd(), '../../uploads', cleanPath); | ||||
|         console.log(`Serving image: ${imagePath} -> ${fullPath}`); | ||||
|         if (!fs_1.default.existsSync(fullPath)) { | ||||
|             console.log(`Image not found: ${fullPath}`); | ||||
|             return res.status(404).json({ | ||||
|                 success: false, | ||||
|                 message: 'Image not found', | ||||
|                 requestedPath: imagePath, | ||||
|                 resolvedPath: fullPath | ||||
|             }); | ||||
|         } | ||||
|         res.set({ | ||||
|             'Access-Control-Allow-Origin': 'http://localhost:5173', | ||||
|             'Access-Control-Allow-Credentials': 'true', | ||||
|             'Cache-Control': 'public, max-age=31536000', | ||||
|         }); | ||||
|         return res.sendFile(path_1.default.resolve(fullPath)); | ||||
|     } | ||||
|     catch (error) { | ||||
|         console.error('Error serving image:', error); | ||||
|         next(error); | ||||
|     } | ||||
| }); | ||||
| router.get('/:id', async (req, res, next) => { | ||||
|     try { | ||||
|         const { id } = req.params; | ||||
|         if (!id) { | ||||
|             return res.status(400).json({ | ||||
|                 success: false, | ||||
|                 message: 'Image ID is required', | ||||
|             }); | ||||
|         } | ||||
|         const image = await prisma.recipeImage.findUnique({ | ||||
|             where: { id: parseInt(id) } | ||||
|         }); | ||||
|         if (!image) { | ||||
|             return res.status(404).json({ | ||||
|                 success: false, | ||||
|                 message: 'Image not found', | ||||
|             }); | ||||
|         } | ||||
|         return res.json({ | ||||
|             success: true, | ||||
|             data: image, | ||||
|         }); | ||||
|     } | ||||
|     catch (error) { | ||||
|         next(error); | ||||
|     } | ||||
| }); | ||||
| exports.default = router; | ||||
| //# sourceMappingURL=images.js.map | ||||
							
								
								
									
										1
									
								
								backend/dist/routes/images.js.map
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								backend/dist/routes/images.js.map
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										3
									
								
								backend/dist/routes/ingredients.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								backend/dist/routes/ingredients.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| declare const router: import("express-serve-static-core").Router; | ||||
| export default router; | ||||
| //# sourceMappingURL=ingredients.d.ts.map | ||||
							
								
								
									
										1
									
								
								backend/dist/routes/ingredients.d.ts.map
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								backend/dist/routes/ingredients.d.ts.map
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| {"version":3,"file":"ingredients.d.ts","sourceRoot":"","sources":["../../src/routes/ingredients.ts"],"names":[],"mappings":"AAIA,QAAA,MAAM,MAAM,4CAAW,CAAC;AA0LxB,eAAe,MAAM,CAAC"} | ||||
							
								
								
									
										159
									
								
								backend/dist/routes/ingredients.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										159
									
								
								backend/dist/routes/ingredients.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,159 @@ | ||||
| "use strict"; | ||||
| var __importDefault = (this && this.__importDefault) || function (mod) { | ||||
|     return (mod && mod.__esModule) ? mod : { "default": mod }; | ||||
| }; | ||||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||||
| const express_1 = require("express"); | ||||
| const client_1 = require("@prisma/client"); | ||||
| const joi_1 = __importDefault(require("joi")); | ||||
| const router = (0, express_1.Router)(); | ||||
| const prisma = new client_1.PrismaClient(); | ||||
| const ingredientSchema = joi_1.default.object({ | ||||
|     recipeNumber: joi_1.default.string().required().max(20), | ||||
|     ingredients: joi_1.default.string().required(), | ||||
| }); | ||||
| const updateIngredientSchema = ingredientSchema.fork(['recipeNumber'], (schema) => schema.optional()); | ||||
| router.get('/', async (req, res, next) => { | ||||
|     try { | ||||
|         const { search = '', category = '', page = '1', limit = '10', sortBy = 'recipeNumber', sortOrder = 'asc' } = req.query; | ||||
|         const pageNum = parseInt(page); | ||||
|         const limitNum = parseInt(limit); | ||||
|         const skip = (pageNum - 1) * limitNum; | ||||
|         const where = {}; | ||||
|         if (search) { | ||||
|             where.OR = [ | ||||
|                 { recipeNumber: { contains: search } }, | ||||
|                 { ingredients: { contains: search } }, | ||||
|             ]; | ||||
|         } | ||||
|         if (category) { | ||||
|             where.recipeNumber = { contains: category }; | ||||
|         } | ||||
|         const [ingredients, total] = await Promise.all([ | ||||
|             prisma.ingredient.findMany({ | ||||
|                 where, | ||||
|                 orderBy: { [sortBy]: sortOrder }, | ||||
|                 skip, | ||||
|                 take: limitNum, | ||||
|             }), | ||||
|             prisma.ingredient.count({ where }) | ||||
|         ]); | ||||
|         return res.json({ | ||||
|             success: true, | ||||
|             data: ingredients, | ||||
|             pagination: { | ||||
|                 page: pageNum, | ||||
|                 limit: limitNum, | ||||
|                 total, | ||||
|                 pages: Math.ceil(total / limitNum), | ||||
|             }, | ||||
|         }); | ||||
|     } | ||||
|     catch (error) { | ||||
|         next(error); | ||||
|     } | ||||
| }); | ||||
| router.get('/:id', async (req, res, next) => { | ||||
|     try { | ||||
|         const { id } = req.params; | ||||
|         if (!id) { | ||||
|             return res.status(400).json({ | ||||
|                 success: false, | ||||
|                 message: 'Ingredient ID is required', | ||||
|             }); | ||||
|         } | ||||
|         const ingredient = await prisma.ingredient.findUnique({ | ||||
|             where: { id: parseInt(id) } | ||||
|         }); | ||||
|         if (!ingredient) { | ||||
|             return res.status(404).json({ | ||||
|                 success: false, | ||||
|                 message: 'Ingredient not found', | ||||
|             }); | ||||
|         } | ||||
|         return res.json({ | ||||
|             success: true, | ||||
|             data: ingredient, | ||||
|         }); | ||||
|     } | ||||
|     catch (error) { | ||||
|         next(error); | ||||
|     } | ||||
| }); | ||||
| router.post('/', async (req, res, next) => { | ||||
|     try { | ||||
|         const { error, value } = ingredientSchema.validate(req.body); | ||||
|         if (error) { | ||||
|             return res.status(400).json({ | ||||
|                 success: false, | ||||
|                 message: 'Validation error', | ||||
|                 details: error.details, | ||||
|             }); | ||||
|         } | ||||
|         const ingredient = await prisma.ingredient.create({ | ||||
|             data: value | ||||
|         }); | ||||
|         return res.status(201).json({ | ||||
|             success: true, | ||||
|             data: ingredient, | ||||
|             message: 'Ingredient created successfully', | ||||
|         }); | ||||
|     } | ||||
|     catch (error) { | ||||
|         next(error); | ||||
|     } | ||||
| }); | ||||
| router.put('/:id', async (req, res, next) => { | ||||
|     try { | ||||
|         const { id } = req.params; | ||||
|         if (!id) { | ||||
|             return res.status(400).json({ | ||||
|                 success: false, | ||||
|                 message: 'Ingredient ID is required', | ||||
|             }); | ||||
|         } | ||||
|         const { error, value } = updateIngredientSchema.validate(req.body); | ||||
|         if (error) { | ||||
|             return res.status(400).json({ | ||||
|                 success: false, | ||||
|                 message: 'Validation error', | ||||
|                 details: error.details, | ||||
|             }); | ||||
|         } | ||||
|         const ingredient = await prisma.ingredient.update({ | ||||
|             where: { id: parseInt(id) }, | ||||
|             data: value | ||||
|         }); | ||||
|         return res.json({ | ||||
|             success: true, | ||||
|             data: ingredient, | ||||
|             message: 'Ingredient updated successfully', | ||||
|         }); | ||||
|     } | ||||
|     catch (error) { | ||||
|         next(error); | ||||
|     } | ||||
| }); | ||||
| router.delete('/:id', async (req, res, next) => { | ||||
|     try { | ||||
|         const { id } = req.params; | ||||
|         if (!id) { | ||||
|             return res.status(400).json({ | ||||
|                 success: false, | ||||
|                 message: 'Ingredient ID is required', | ||||
|             }); | ||||
|         } | ||||
|         await prisma.ingredient.delete({ | ||||
|             where: { id: parseInt(id) } | ||||
|         }); | ||||
|         return res.json({ | ||||
|             success: true, | ||||
|             message: 'Ingredient deleted successfully', | ||||
|         }); | ||||
|     } | ||||
|     catch (error) { | ||||
|         next(error); | ||||
|     } | ||||
| }); | ||||
| exports.default = router; | ||||
| //# sourceMappingURL=ingredients.js.map | ||||
							
								
								
									
										1
									
								
								backend/dist/routes/ingredients.js.map
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								backend/dist/routes/ingredients.js.map
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| {"version":3,"file":"ingredients.js","sourceRoot":"","sources":["../../src/routes/ingredients.ts"],"names":[],"mappings":"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"} | ||||
							
								
								
									
										3
									
								
								backend/dist/routes/recipes.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								backend/dist/routes/recipes.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| declare const router: import("express-serve-static-core").Router; | ||||
| export default router; | ||||
| //# sourceMappingURL=recipes.d.ts.map | ||||
							
								
								
									
										1
									
								
								backend/dist/routes/recipes.d.ts.map
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								backend/dist/routes/recipes.d.ts.map
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| {"version":3,"file":"recipes.d.ts","sourceRoot":"","sources":["../../src/routes/recipes.ts"],"names":[],"mappings":"AAIA,QAAA,MAAM,MAAM,4CAAW,CAAC;AAoQxB,eAAe,MAAM,CAAC"} | ||||
							
								
								
									
										219
									
								
								backend/dist/routes/recipes.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										219
									
								
								backend/dist/routes/recipes.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,219 @@ | ||||
| "use strict"; | ||||
| var __importDefault = (this && this.__importDefault) || function (mod) { | ||||
|     return (mod && mod.__esModule) ? mod : { "default": mod }; | ||||
| }; | ||||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||||
| const express_1 = require("express"); | ||||
| const client_1 = require("@prisma/client"); | ||||
| const joi_1 = __importDefault(require("joi")); | ||||
| const router = (0, express_1.Router)(); | ||||
| const prisma = new client_1.PrismaClient(); | ||||
| const recipeSchema = joi_1.default.object({ | ||||
|     recipeNumber: joi_1.default.string().optional().allow(''), | ||||
|     title: joi_1.default.string().required().min(1).max(255), | ||||
|     description: joi_1.default.string().optional().allow(''), | ||||
|     category: joi_1.default.string().optional().allow(''), | ||||
|     preparation: joi_1.default.string().optional().allow(''), | ||||
|     servings: joi_1.default.number().integer().min(1).default(1), | ||||
|     ingredients: joi_1.default.string().optional().allow(''), | ||||
|     instructions: joi_1.default.string().optional().allow(''), | ||||
|     comment: joi_1.default.string().optional().allow(''), | ||||
| }); | ||||
| const updateRecipeSchema = recipeSchema; | ||||
| router.get('/', async (req, res, next) => { | ||||
|     try { | ||||
|         const { search = '', category = '', page = '1', limit = '10', sortBy = 'title', sortOrder = 'asc' } = req.query; | ||||
|         const pageNum = parseInt(page); | ||||
|         const limitNum = parseInt(limit); | ||||
|         const skip = (pageNum - 1) * limitNum; | ||||
|         const where = {}; | ||||
|         if (search) { | ||||
|             where.OR = [ | ||||
|                 { title: { contains: search } }, | ||||
|                 { description: { contains: search } }, | ||||
|                 { ingredients: { contains: search } }, | ||||
|             ]; | ||||
|         } | ||||
|         if (category) { | ||||
|             where.category = { contains: category }; | ||||
|         } | ||||
|         const [recipes, total] = await Promise.all([ | ||||
|             prisma.recipe.findMany({ | ||||
|                 where, | ||||
|                 include: { | ||||
|                     images: true, | ||||
|                     ingredientsList: true, | ||||
|                 }, | ||||
|                 orderBy: { [sortBy]: sortOrder }, | ||||
|                 skip, | ||||
|                 take: limitNum, | ||||
|             }), | ||||
|             prisma.recipe.count({ where }) | ||||
|         ]); | ||||
|         return res.json({ | ||||
|             success: true, | ||||
|             data: recipes, | ||||
|             pagination: { | ||||
|                 page: pageNum, | ||||
|                 limit: limitNum, | ||||
|                 total, | ||||
|                 pages: Math.ceil(total / limitNum), | ||||
|             }, | ||||
|         }); | ||||
|     } | ||||
|     catch (error) { | ||||
|         next(error); | ||||
|     } | ||||
| }); | ||||
| router.get('/:id', async (req, res, next) => { | ||||
|     try { | ||||
|         const { id } = req.params; | ||||
|         if (!id) { | ||||
|             return res.status(400).json({ | ||||
|                 success: false, | ||||
|                 message: 'Recipe ID is required', | ||||
|             }); | ||||
|         } | ||||
|         const recipeId = parseInt(id); | ||||
|         if (isNaN(recipeId)) { | ||||
|             return res.status(400).json({ | ||||
|                 success: false, | ||||
|                 message: 'Invalid recipe ID format', | ||||
|             }); | ||||
|         } | ||||
|         const recipe = await prisma.recipe.findUnique({ | ||||
|             where: { id: recipeId }, | ||||
|             include: { | ||||
|                 images: true, | ||||
|                 ingredientsList: true, | ||||
|             } | ||||
|         }); | ||||
|         if (!recipe) { | ||||
|             return res.status(404).json({ | ||||
|                 success: false, | ||||
|                 message: 'Recipe not found', | ||||
|             }); | ||||
|         } | ||||
|         if ((!recipe.ingredients || recipe.ingredients.trim() === '' || recipe.ingredients.length < 10) && recipe.recipeNumber) { | ||||
|             try { | ||||
|                 const paddedNumber = recipe.recipeNumber.padStart(3, '0'); | ||||
|                 const recipeNumberWithR = `R${paddedNumber}`; | ||||
|                 const separateIngredients = await prisma.ingredient.findFirst({ | ||||
|                     where: { recipeNumber: recipeNumberWithR } | ||||
|                 }); | ||||
|                 if (separateIngredients && separateIngredients.ingredients) { | ||||
|                     recipe.ingredients = separateIngredients.ingredients; | ||||
|                 } | ||||
|             } | ||||
|             catch (ingredientError) { | ||||
|                 console.log(`Could not load separate ingredients for recipe ${recipe.recipeNumber}:`, ingredientError); | ||||
|             } | ||||
|         } | ||||
|         return res.json({ | ||||
|             success: true, | ||||
|             data: recipe, | ||||
|         }); | ||||
|     } | ||||
|     catch (error) { | ||||
|         next(error); | ||||
|     } | ||||
| }); | ||||
| router.post('/', async (req, res, next) => { | ||||
|     try { | ||||
|         const { error, value } = recipeSchema.validate(req.body); | ||||
|         if (error) { | ||||
|             return res.status(400).json({ | ||||
|                 success: false, | ||||
|                 message: 'Validation error', | ||||
|                 details: error.details, | ||||
|             }); | ||||
|         } | ||||
|         if (!value.recipeNumber || value.recipeNumber.trim() === '') { | ||||
|             const lastRecipe = await prisma.recipe.findFirst({ | ||||
|                 orderBy: { id: 'desc' }, | ||||
|                 select: { recipeNumber: true } | ||||
|             }); | ||||
|             let nextNumber = 1; | ||||
|             if (lastRecipe?.recipeNumber) { | ||||
|                 const match = lastRecipe.recipeNumber.match(/\d+/); | ||||
|                 if (match) { | ||||
|                     nextNumber = parseInt(match[0]) + 1; | ||||
|                 } | ||||
|             } | ||||
|             value.recipeNumber = `R${nextNumber.toString().padStart(3, '0')}`; | ||||
|         } | ||||
|         const recipe = await prisma.recipe.create({ | ||||
|             data: value, | ||||
|             include: { | ||||
|                 images: true, | ||||
|                 ingredientsList: true, | ||||
|             } | ||||
|         }); | ||||
|         return res.status(201).json({ | ||||
|             success: true, | ||||
|             data: recipe, | ||||
|             message: 'Recipe created successfully', | ||||
|         }); | ||||
|     } | ||||
|     catch (error) { | ||||
|         next(error); | ||||
|     } | ||||
| }); | ||||
| router.put('/:id', async (req, res, next) => { | ||||
|     try { | ||||
|         const { id } = req.params; | ||||
|         if (!id) { | ||||
|             return res.status(400).json({ | ||||
|                 success: false, | ||||
|                 message: 'Recipe ID is required', | ||||
|             }); | ||||
|         } | ||||
|         const { error, value } = updateRecipeSchema.validate(req.body); | ||||
|         if (error) { | ||||
|             return res.status(400).json({ | ||||
|                 success: false, | ||||
|                 message: 'Validation error', | ||||
|                 details: error.details, | ||||
|             }); | ||||
|         } | ||||
|         const recipe = await prisma.recipe.update({ | ||||
|             where: { id: parseInt(id) }, | ||||
|             data: value, | ||||
|             include: { | ||||
|                 images: true, | ||||
|                 ingredientsList: true, | ||||
|             } | ||||
|         }); | ||||
|         return res.json({ | ||||
|             success: true, | ||||
|             data: recipe, | ||||
|             message: 'Recipe updated successfully', | ||||
|         }); | ||||
|     } | ||||
|     catch (error) { | ||||
|         next(error); | ||||
|     } | ||||
| }); | ||||
| router.delete('/:id', async (req, res, next) => { | ||||
|     try { | ||||
|         const { id } = req.params; | ||||
|         if (!id) { | ||||
|             return res.status(400).json({ | ||||
|                 success: false, | ||||
|                 message: 'Recipe ID is required', | ||||
|             }); | ||||
|         } | ||||
|         await prisma.recipe.delete({ | ||||
|             where: { id: parseInt(id) } | ||||
|         }); | ||||
|         return res.json({ | ||||
|             success: true, | ||||
|             message: 'Recipe deleted successfully', | ||||
|         }); | ||||
|     } | ||||
|     catch (error) { | ||||
|         next(error); | ||||
|     } | ||||
| }); | ||||
| exports.default = router; | ||||
| //# sourceMappingURL=recipes.js.map | ||||
							
								
								
									
										1
									
								
								backend/dist/routes/recipes.js.map
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								backend/dist/routes/recipes.js.map
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
		Reference in New Issue
	
	Block a user