add Analyse
This commit is contained in:
@@ -31,7 +31,7 @@ export function getDb() {
|
||||
return db;
|
||||
}
|
||||
|
||||
export function getAllRecipes(search, category) {
|
||||
export function getAllRecipes(search, category, userId) {
|
||||
let results = [...db.recipes];
|
||||
|
||||
if (search) {
|
||||
@@ -47,6 +47,10 @@ export function getAllRecipes(search, category) {
|
||||
results = results.filter(r => r.category === category);
|
||||
}
|
||||
|
||||
if (userId) {
|
||||
results = results.filter(r => r.user_id === Number(userId));
|
||||
}
|
||||
|
||||
results.sort((a, b) => (b.created_at || '').localeCompare(a.created_at || ''));
|
||||
return results;
|
||||
}
|
||||
@@ -65,8 +69,11 @@ export function createRecipe(recipe) {
|
||||
ingredients: JSON.stringify(recipe.ingredients || []),
|
||||
steps: JSON.stringify(recipe.steps || []),
|
||||
category: recipe.category || 'Sonstiges',
|
||||
tiktok_url: recipe.tiktok_url || null,
|
||||
tiktok_author: recipe.tiktok_author || null,
|
||||
source: recipe.source || 'tiktok',
|
||||
source_url: recipe.source_url || recipe.tiktok_url || null,
|
||||
source_author: recipe.source_author || recipe.tiktok_author || null,
|
||||
tiktok_url: recipe.tiktok_url || recipe.source_url || null,
|
||||
tiktok_author: recipe.tiktok_author || recipe.source_author || null,
|
||||
thumbnail_url: recipe.thumbnail_url || null,
|
||||
user_id: recipe.user_id || null,
|
||||
created_at: nowStr,
|
||||
@@ -87,8 +94,11 @@ export function updateRecipe(id, recipe) {
|
||||
ingredients: JSON.stringify(recipe.ingredients || []),
|
||||
steps: JSON.stringify(recipe.steps || []),
|
||||
category: recipe.category || 'Sonstiges',
|
||||
tiktok_url: recipe.tiktok_url || null,
|
||||
tiktok_author: recipe.tiktok_author || null,
|
||||
source: recipe.source || db.recipes[idx].source || 'tiktok',
|
||||
source_url: recipe.source_url || recipe.tiktok_url || db.recipes[idx].source_url || null,
|
||||
source_author: recipe.source_author || recipe.tiktok_author || db.recipes[idx].source_author || null,
|
||||
tiktok_url: recipe.tiktok_url || recipe.source_url || db.recipes[idx].tiktok_url || null,
|
||||
tiktok_author: recipe.tiktok_author || recipe.source_author || db.recipes[idx].tiktok_author || null,
|
||||
thumbnail_url: recipe.thumbnail_url || null,
|
||||
updated_at: now(),
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,18 +1,42 @@
|
||||
import express from 'express';
|
||||
import cors from 'cors';
|
||||
import bcrypt from 'bcryptjs';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import recipesRouter from './routes/recipes.js';
|
||||
import authRouter from './routes/auth.js';
|
||||
import analyticsRouter from './routes/analytics.js';
|
||||
import { getUserByUsername, createUser } from './db.js';
|
||||
import { trackEvent } from './analytics-db.js';
|
||||
import { authMiddleware, adminMiddleware } from './middleware.js';
|
||||
|
||||
const app = express();
|
||||
const PORT = process.env.PORT || 3001;
|
||||
const JWT_SECRET = process.env.JWT_SECRET || 'tiktok-rezepte-secret-key-change-in-production';
|
||||
|
||||
app.use(cors());
|
||||
app.use(express.json());
|
||||
|
||||
app.use((req, res, next) => {
|
||||
if (req.method === 'OPTIONS' || !req.path.startsWith('/api/')) return next();
|
||||
if (req.path.startsWith('/api/analytics')) return next();
|
||||
|
||||
let userId = null;
|
||||
const authHeader = req.headers.authorization;
|
||||
if (authHeader && authHeader.startsWith('Bearer ')) {
|
||||
try {
|
||||
const decoded = jwt.verify(authHeader.split(' ')[1], JWT_SECRET);
|
||||
const user = getUserByUsername(decoded.username);
|
||||
if (user) userId = user.id;
|
||||
} catch {}
|
||||
}
|
||||
|
||||
trackEvent('page_view', userId, { path: req.path, method: req.method });
|
||||
next();
|
||||
});
|
||||
|
||||
app.use('/api/auth', authRouter);
|
||||
app.use('/api/recipes', recipesRouter);
|
||||
app.use('/api/analytics', authMiddleware, adminMiddleware, analyticsRouter);
|
||||
|
||||
const adminUser = process.env.ADMIN_USER || 'admin';
|
||||
const adminPass = process.env.ADMIN_PASS || 'admin123';
|
||||
|
||||
Reference in New Issue
Block a user