Datei-Struktur ordentlich bereinigt

This commit is contained in:
2025-09-24 19:29:16 +00:00
parent fbed816204
commit ef4ab9e800
98 changed files with 247 additions and 1024 deletions

3
backend/dist/routes/health.d.ts vendored Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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

File diff suppressed because one or more lines are too long

3
backend/dist/routes/ingredients.d.ts vendored Normal file
View File

@@ -0,0 +1,3 @@
declare const router: import("express-serve-static-core").Router;
export default router;
//# sourceMappingURL=ingredients.d.ts.map

View 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
View 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

View 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
View 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
View 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
View 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

File diff suppressed because one or more lines are too long