add Analyse

This commit is contained in:
nadja
2026-08-26 12:18:33 +02:00
parent 28f47c5712
commit 18cf24ff93
12 changed files with 279 additions and 58 deletions

View File

@@ -7,35 +7,13 @@ import {
createInviteToken, getInviteToken, useInviteToken,
getInvitesByUser, getAllUsers, getInviteStats
} from '../db.js';
import { authMiddleware, adminMiddleware } from '../middleware.js';
import { trackEvent } from '../analytics-db.js';
const router = Router();
const JWT_SECRET = process.env.JWT_SECRET || 'tiktok-rezepte-secret-key-change-in-production';
const JWT_EXPIRES_IN = '7d';
function authMiddleware(req, res, next) {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({ error: 'Nicht authentifiziert' });
}
try {
const token = authHeader.split(' ')[1];
const decoded = jwt.verify(token, JWT_SECRET);
const user = getUserByUsername(decoded.username);
if (!user) return res.status(401).json({ error: 'Benutzer nicht gefunden' });
req.user = user;
next();
} catch {
res.status(401).json({ error: 'Ungültiges Token' });
}
}
function adminMiddleware(req, res, next) {
if (!req.user || !req.user.is_admin) {
return res.status(403).json({ error: 'Keine Berechtigung' });
}
next();
}
router.post('/register', async (req, res) => {
try {
const { username, password, inviteToken } = req.body;
@@ -69,6 +47,8 @@ router.post('/register', async (req, res) => {
const user = createUser(username, hashedPassword);
useInviteToken(inviteToken, user.id);
trackEvent('user_register', user.id, { username: user.username });
const token = jwt.sign({ id: user.id, username: user.username }, JWT_SECRET, { expiresIn: JWT_EXPIRES_IN });
res.status(201).json({ user, token });
@@ -97,6 +77,8 @@ router.post('/login', async (req, res) => {
const token = jwt.sign({ id: user.id, username: user.username }, JWT_SECRET, { expiresIn: JWT_EXPIRES_IN });
trackEvent('user_login', user.id, { username: user.username });
res.json({
user: { id: user.id, username: user.username, is_admin: !!user.is_admin, created_at: user.created_at },
token,

View File

@@ -1,16 +1,18 @@
import { Router } from 'express';
import {
getAllRecipes, getRecipeById, createRecipe,
updateRecipe, deleteRecipe, getCategories
updateRecipe, deleteRecipe, getCategories, getDb
} from '../db.js';
import { getTikTokData } from '../services/tiktok.js';
import { getInstagramData } from '../services/instagram.js';
import { isOllamaAvailable, extractRecipe } from '../services/ai.js';
import { trackEvent } from '../analytics-db.js';
const router = Router();
router.get('/', (req, res) => {
const { search, category } = req.query;
const recipes = getAllRecipes(search, category);
const { search, category, user_id } = req.query;
const recipes = getAllRecipes(search, category, user_id);
res.json(recipes.map(r => ({
...r,
ingredients: JSON.parse(r.ingredients),
@@ -22,6 +24,16 @@ router.get('/categories', (req, res) => {
res.json(getCategories());
});
router.get('/authors', (req, res) => {
const db = getDb();
const authorIds = [...new Set(db.recipes.filter(r => r.user_id).map(r => r.user_id))];
const authors = authorIds.map(id => {
const user = db.users.find(u => u.id === id);
return { id, username: user ? user.username : `User #${id}` };
}).sort((a, b) => a.username.localeCompare(b.username));
res.json(authors);
});
router.get('/ai-status', async (req, res) => {
const available = await isOllamaAvailable();
res.json({ ollama: available });
@@ -39,6 +51,8 @@ router.get('/:id', (req, res) => {
router.post('/', (req, res) => {
const recipe = createRecipe(req.body);
const userId = req.body.user_id || null;
trackEvent('recipe_created', userId, { recipe_id: recipe.id, recipe_name: recipe.name });
res.status(201).json(recipe);
});
@@ -46,6 +60,7 @@ router.put('/:id', (req, res) => {
const existing = getRecipeById(req.params.id);
if (!existing) return res.status(404).json({ error: 'Rezept nicht gefunden' });
const updated = updateRecipe(req.params.id, req.body);
trackEvent('recipe_updated', req.body.user_id || null, { recipe_id: Number(req.params.id), recipe_name: updated.name });
res.json(updated);
});
@@ -53,6 +68,7 @@ router.delete('/:id', (req, res) => {
const existing = getRecipeById(req.params.id);
if (!existing) return res.status(404).json({ error: 'Rezept nicht gefunden' });
deleteRecipe(req.params.id);
trackEvent('recipe_deleted', null, { recipe_id: Number(req.params.id) });
res.json({ success: true });
});
@@ -88,4 +104,36 @@ router.post('/from-tiktok', async (req, res) => {
}
});
router.post('/from-instagram', async (req, res) => {
try {
const { url } = req.body;
if (!url) return res.status(400).json({ error: 'URL erforderlich' });
const instagramData = await getInstagramData(url);
const ollamaAvailable = await isOllamaAvailable();
let extracted = null;
let aiStatus = ollamaAvailable ? 'available' : 'unavailable';
if (ollamaAvailable && instagramData.title) {
try {
extracted = await extractRecipe(instagramData.title);
aiStatus = 'success';
} catch (e) {
console.error('Ollama Extraktion fehlgeschlagen:', e.message);
aiStatus = 'error';
}
}
res.json({
instagram: instagramData,
ollama: ollamaAvailable,
aiStatus,
extracted,
});
} catch (error) {
res.status(500).json({ error: error.message });
}
});
export default router;