Files
scoffer/backend/routes/recipes.js

194 lines
4.7 KiB
JavaScript

const express = require('express');
const router = express.Router();
const jwt = require('jsonwebtoken');
const Recipe = require('../models/Recipe');
// Middleware to authenticate JWT token
function authenticateToken(req, res, next) {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1];
if (!token) {
return res.status(401).json({ error: 'Access token required' });
}
jwt.verify(token, process.env.JWT_SECRET, (err, decoded) => {
if (err) {
return res.status(403).json({ error: 'Invalid or expired token' });
}
req.userId = decoded.userId;
next();
});
}
// Get ingredient suggestions for autocomplete
router.get('/ingredients/suggestions', async (req, res) => {
try {
const { q } = req.query; // Search query
if (!q || q.length < 2) {
return res.json([]);
}
// Aggregate unique ingredient names from all recipes
const suggestions = await Recipe.aggregate([
{ $unwind: '$ingredients' },
{
$match: {
'ingredients.name': {
$regex: q,
$options: 'i'
}
}
},
{
$group: {
_id: { $toLower: '$ingredients.name' },
name: { $first: '$ingredients.name' },
count: { $sum: 1 }
}
},
{ $sort: { count: -1, name: 1 } },
{ $limit: 10 },
{
$project: {
_id: 0,
name: 1,
count: 1
}
}
]);
res.json(suggestions);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// Get unit suggestions for a specific ingredient
router.get('/ingredients/units', async (req, res) => {
try {
const { ingredient } = req.query;
if (!ingredient) {
return res.json([]);
}
// Find units used with this specific ingredient
const unitSuggestions = await Recipe.aggregate([
{ $unwind: '$ingredients' },
{
$match: {
'ingredients.name': {
$regex: ingredient,
$options: 'i'
}
}
},
{
$group: {
_id: { $toLower: '$ingredients.unit' },
unit: { $first: '$ingredients.unit' },
count: { $sum: 1 },
avgAmount: { $avg: '$ingredients.amount' }
}
},
{ $sort: { count: -1, unit: 1 } },
{ $limit: 8 },
{
$project: {
_id: 0,
unit: 1,
count: 1,
avgAmount: { $round: ['$avgAmount', 2] }
}
}
]);
res.json(unitSuggestions);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// Get all recipes
router.get('/', async (req, res) => {
try {
const { category, difficulty, search } = req.query;
let filter = {};
if (category) filter.category = category;
if (difficulty) filter.difficulty = difficulty;
if (search) {
filter.$or = [
{ title: { $regex: search, $options: 'i' } },
{ description: { $regex: search, $options: 'i' } }
];
}
const recipes = await Recipe.find(filter).sort({ createdAt: -1 });
res.json(recipes);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// Get recipe by ID
router.get('/:id', async (req, res) => {
try {
const recipe = await Recipe.findById(req.params.id);
if (!recipe) {
return res.status(404).json({ error: 'Recipe not found' });
}
res.json(recipe);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// Create new recipe (protected route)
router.post('/', authenticateToken, async (req, res) => {
try {
const recipe = new Recipe({
...req.body,
createdBy: req.userId // Add user ID to track who created the recipe
});
await recipe.save();
res.status(201).json(recipe);
} catch (error) {
res.status(400).json({ error: error.message });
}
});
// Update recipe
router.put('/:id', async (req, res) => {
try {
const recipe = await Recipe.findByIdAndUpdate(
req.params.id,
req.body,
{ new: true, runValidators: true }
);
if (!recipe) {
return res.status(404).json({ error: 'Recipe not found' });
}
res.json(recipe);
} catch (error) {
res.status(400).json({ error: error.message });
}
});
// Delete recipe
router.delete('/:id', async (req, res) => {
try {
const recipe = await Recipe.findByIdAndDelete(req.params.id);
if (!recipe) {
return res.status(404).json({ error: 'Recipe not found' });
}
res.json({ message: 'Recipe deleted successfully' });
} catch (error) {
res.status(500).json({ error: error.message });
}
});
module.exports = router;