203 lines
5.6 KiB
JavaScript
203 lines
5.6 KiB
JavaScript
import { readFileSync, writeFileSync, existsSync } from 'fs';
|
|
import { dirname, join } from 'path';
|
|
import { fileURLToPath } from 'url';
|
|
|
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
const DB_FILE = process.env.DB_PATH || join(__dirname, 'data.json');
|
|
|
|
function loadDB() {
|
|
if (existsSync(DB_FILE)) {
|
|
return JSON.parse(readFileSync(DB_FILE, 'utf-8'));
|
|
}
|
|
return { users: [], recipes: [], invite_tokens: [], _counters: { users: 0, recipes: 0, invite_tokens: 0 } };
|
|
}
|
|
|
|
function saveDB(data) {
|
|
writeFileSync(DB_FILE, JSON.stringify(data, null, 2));
|
|
}
|
|
|
|
let db = loadDB();
|
|
|
|
function nextId(table) {
|
|
db._counters[table] = (db._counters[table] || 0) + 1;
|
|
return db._counters[table];
|
|
}
|
|
|
|
function now() {
|
|
return new Date().toISOString().replace('T', ' ').slice(0, 19);
|
|
}
|
|
|
|
export function getDb() {
|
|
return db;
|
|
}
|
|
|
|
export function getAllRecipes(search, category) {
|
|
let results = [...db.recipes];
|
|
|
|
if (search) {
|
|
const term = search.toLowerCase();
|
|
results = results.filter(r =>
|
|
(r.name && r.name.toLowerCase().includes(term)) ||
|
|
(r.description && r.description.toLowerCase().includes(term)) ||
|
|
(r.ingredients && r.ingredients.toLowerCase().includes(term))
|
|
);
|
|
}
|
|
|
|
if (category && category !== 'Alle') {
|
|
results = results.filter(r => r.category === category);
|
|
}
|
|
|
|
results.sort((a, b) => (b.created_at || '').localeCompare(a.created_at || ''));
|
|
return results;
|
|
}
|
|
|
|
export function getRecipeById(id) {
|
|
return db.recipes.find(r => r.id === Number(id)) || undefined;
|
|
}
|
|
|
|
export function createRecipe(recipe) {
|
|
const id = nextId('recipes');
|
|
const nowStr = now();
|
|
const row = {
|
|
id,
|
|
name: recipe.name,
|
|
description: recipe.description || '',
|
|
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,
|
|
thumbnail_url: recipe.thumbnail_url || null,
|
|
user_id: recipe.user_id || null,
|
|
created_at: nowStr,
|
|
updated_at: nowStr,
|
|
};
|
|
db.recipes.push(row);
|
|
saveDB(db);
|
|
return row;
|
|
}
|
|
|
|
export function updateRecipe(id, recipe) {
|
|
const idx = db.recipes.findIndex(r => r.id === Number(id));
|
|
if (idx === -1) return undefined;
|
|
db.recipes[idx] = {
|
|
...db.recipes[idx],
|
|
name: recipe.name,
|
|
description: recipe.description || '',
|
|
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,
|
|
thumbnail_url: recipe.thumbnail_url || null,
|
|
updated_at: now(),
|
|
};
|
|
saveDB(db);
|
|
return db.recipes[idx];
|
|
}
|
|
|
|
export function deleteRecipe(id) {
|
|
const idx = db.recipes.findIndex(r => r.id === Number(id));
|
|
if (idx === -1) return;
|
|
db.recipes.splice(idx, 1);
|
|
saveDB(db);
|
|
}
|
|
|
|
export function getCategories() {
|
|
const counts = {};
|
|
for (const r of db.recipes) {
|
|
counts[r.category] = (counts[r.category] || 0) + 1;
|
|
}
|
|
return Object.entries(counts)
|
|
.map(([category, count]) => ({ category, count }))
|
|
.sort((a, b) => b.count - a.count);
|
|
}
|
|
|
|
export function getUserByUsername(username) {
|
|
return db.users.find(u => u.username === username) || undefined;
|
|
}
|
|
|
|
export function getUserById(id) {
|
|
return db.users.find(u => u.id === Number(id)) || undefined;
|
|
}
|
|
|
|
export function createUser(username, hashedPassword, isAdmin = false) {
|
|
const id = nextId('users');
|
|
const user = {
|
|
id,
|
|
username,
|
|
password: hashedPassword,
|
|
is_admin: isAdmin ? 1 : 0,
|
|
created_at: now(),
|
|
};
|
|
db.users.push(user);
|
|
saveDB(db);
|
|
return { id, username, is_admin: user.is_admin, created_at: user.created_at };
|
|
}
|
|
|
|
export function updateUserCredentials(userId, { username, password }) {
|
|
const user = db.users.find(u => u.id === Number(userId));
|
|
if (!user) return null;
|
|
if (username) user.username = username;
|
|
if (password) user.password = password;
|
|
saveDB(db);
|
|
return { id: user.id, username: user.username, is_admin: user.is_admin, created_at: user.created_at };
|
|
}
|
|
|
|
export function createInviteToken(token, createdBy) {
|
|
const id = nextId('invite_tokens');
|
|
db.invite_tokens.push({
|
|
id,
|
|
token,
|
|
created_by: createdBy,
|
|
used_by: null,
|
|
created_at: now(),
|
|
used_at: null,
|
|
});
|
|
saveDB(db);
|
|
}
|
|
|
|
export function getInviteToken(token) {
|
|
return db.invite_tokens.find(i => i.token === token) || undefined;
|
|
}
|
|
|
|
export function useInviteToken(token, userId) {
|
|
const inv = db.invite_tokens.find(i => i.token === token);
|
|
if (inv) {
|
|
inv.used_by = userId;
|
|
inv.used_at = now();
|
|
saveDB(db);
|
|
}
|
|
}
|
|
|
|
export function getInvitesByUser(userId) {
|
|
return db.invite_tokens
|
|
.filter(i => i.created_by === Number(userId))
|
|
.sort((a, b) => (b.created_at || '').localeCompare(a.created_at || ''))
|
|
.map(i => {
|
|
const usedByUser = i.used_by ? db.users.find(u => u.id === i.used_by) : null;
|
|
return { ...i, used_by_username: usedByUser ? usedByUser.username : null };
|
|
});
|
|
}
|
|
|
|
export function getAllUsers() {
|
|
return db.users
|
|
.map(u => ({ id: u.id, username: u.username, is_admin: u.is_admin, created_at: u.created_at }))
|
|
.sort((a, b) => (b.created_at || '').localeCompare(a.created_at || ''));
|
|
}
|
|
|
|
export function deleteUser(id) {
|
|
const idx = db.users.findIndex(u => u.id === Number(id));
|
|
if (idx === -1) return false;
|
|
db.users.splice(idx, 1);
|
|
db.invite_tokens = db.invite_tokens.filter(i => i.created_by !== Number(id));
|
|
saveDB(db);
|
|
return true;
|
|
}
|
|
|
|
export function getInviteStats() {
|
|
const total = db.invite_tokens.length;
|
|
const used = db.invite_tokens.filter(i => i.used_by !== null).length;
|
|
return { total, used, available: total - used };
|
|
}
|