Last Used implementiert

This commit is contained in:
2025-09-25 19:52:55 +00:00
parent 0bfb8b2074
commit db431553b9
11 changed files with 270 additions and 7 deletions

View File

@@ -1 +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"}
{"version":3,"file":"recipes.d.ts","sourceRoot":"","sources":["../../src/routes/recipes.ts"],"names":[],"mappings":"AAIA,QAAA,MAAM,MAAM,4CAAW,CAAC;AA6QxB,eAAe,MAAM,CAAC"}

View File

@@ -109,6 +109,13 @@ router.get('/:id', async (req, res, next) => {
console.log(`Could not load separate ingredients for recipe ${recipe.recipeNumber}:`, ingredientError);
}
}
const now = new Date();
prisma.recipe.update({
where: { id: recipeId },
data: { lastUsed: now },
select: { id: true }
}).catch(err => console.warn('lastUsed update failed for recipe', recipeId, err.message));
recipe.lastUsed = now;
return res.json({
success: true,
data: recipe,

File diff suppressed because one or more lines are too long

View File

@@ -22,12 +22,14 @@ model Recipe {
ingredients String? @map("Zutaten") @db.Text
instructions String? @map("Zubereitung") @db.Text
comment String? @map("Kommentar") @db.Text
lastUsed DateTime? @map("last_used") @db.DateTime(0)
// Relations
images RecipeImage[]
ingredientsList Ingredient[]
@@map("Rezepte")
@@index([lastUsed])
}
model Ingredient {

View File

@@ -50,6 +50,10 @@ router.get('/', async (req: Request, res: Response, next: NextFunction) => {
where.category = { contains: category as string };
}
// Whitelist allowed sort fields to prevent SQL injection vectors via Prisma
const allowedSortFields = new Set(['title','category','servings','recipeNumber','lastUsed','id']);
const chosenSortField = allowedSortFields.has(String(sortBy)) ? String(sortBy) : 'title';
const [recipes, total] = await Promise.all([
prisma.recipe.findMany({
where,
@@ -57,7 +61,8 @@ router.get('/', async (req: Request, res: Response, next: NextFunction) => {
images: true,
ingredientsList: true,
},
orderBy: { [sortBy as string]: sortOrder as 'asc' | 'desc' },
// @ts-ignore lastUsed exists in DB schema; Prisma typing cache issue workaround
orderBy: { [chosenSortField]: sortOrder === 'desc' ? 'desc' : 'asc' },
skip,
take: limitNum,
}),
@@ -79,8 +84,24 @@ router.get('/', async (req: Request, res: Response, next: NextFunction) => {
}
});
// Neueste zubereitete Rezepte (nach lastUsed sortiert) vor :id Route platzieren
router.get('/recent', async (req: Request, res: Response, next: NextFunction) => {
try {
const { limit = '10' } = req.query;
const take = Math.min(Math.max(parseInt(limit as string) || 10, 1), 50);
const recipes = await prisma.recipe.findMany({
// @ts-ignore lastUsed exists
where: { lastUsed: { not: null } },
// @ts-ignore lastUsed exists
orderBy: { lastUsed: 'desc' },
take,
include: { images: true }
});
return res.json({ success: true, data: recipes });
} catch (error) { next(error); }
});
// Get single recipe by ID
// Get recipe by ID
router.get('/:id', async (req: Request, res: Response, next: NextFunction) => {
try {
const { id } = req.params;
@@ -135,6 +156,8 @@ router.get('/:id', async (req: Request, res: Response, next: NextFunction) => {
}
}
// lastUsed wird nicht mehr beim Ansehen aktualisiert, sondern explizit über einen "cooked" Endpoint
return res.json({
success: true,
data: recipe,
@@ -262,4 +285,45 @@ router.delete('/:id', async (req: Request, res: Response, next: NextFunction) =>
}
});
// Endpoint um ein Rezept als "zubereitet" zu markieren -> aktualisiert lastUsed
router.post('/:id/cooked', async (req: Request, res: Response, next: NextFunction) => {
try {
const { id } = req.params;
if (!id) {
return res.status(400).json({ success: false, message: 'Recipe ID is required' });
}
const recipeId = parseInt(id, 10);
if (isNaN(recipeId)) {
return res.status(400).json({ success: false, message: 'Invalid recipe ID format' });
}
let customDate: Date | null = null;
if (req.body && req.body.lastUsed) {
const parsed = new Date(req.body.lastUsed);
if (isNaN(parsed.getTime())) {
return res.status(400).json({ success: false, message: 'Ungültiges Datum (lastUsed)' });
}
customDate = parsed;
}
const targetDate = customDate || new Date();
if (targetDate.getTime() > Date.now() + 60_000) { // 1 Minute Puffer
return res.status(400).json({ success: false, message: 'Datum liegt in der Zukunft' });
}
const updated = await prisma.recipe.update({
where: { id: recipeId },
// @ts-ignore lastUsed exists
data: { lastUsed: targetDate }
});
// @ts-ignore lastUsed field is present in database
return res.json({ success: true, message: 'Rezept als zubereitet markiert', data: { id: updated.id, lastUsed: (updated as any).lastUsed } });
} catch (error) {
if ((error as any).code === 'P2025') {
return res.status(404).json({ success: false, message: 'Recipe not found' });
}
next(error);
}
});
export default router;