sozday-veb-prilozhenie-dlya-3/server.js

275 lines
9.3 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const http = require("http");
const fs = require("fs");
const path = require("path");
const PORT = process.env.PORT || 3000;
const DATA = path.join(__dirname, "data.json");
function readData() {
try { return JSON.parse(fs.readFileSync(DATA, "utf8")); } catch (_) { return { meals: [], products: [], weekly_plan: [], shopping_list: [], timeline: [] }; }
}
function writeData(data) {
fs.writeFileSync(DATA, JSON.stringify(data, null, 2));
}
function body(req) {
return new Promise((resolve) => {
let s = "";
req.on("data", (c) => (s += c));
req.on("end", () => { try { resolve(JSON.parse(s || "{}")); } catch (_) { resolve({}); } });
});
}
function generateWeeklyPlan() {
const data = readData();
const days = ["Понедельник", "Вторник", "Среда", "Четверг", "Пятница", "Суббота", "Воскресенье"];
const breakfasts = data.meals.filter(m => m.type === "завтрак");
const lunches = data.meals.filter(m => m.type === "обед");
const dinners = data.meals.filter(m => m.type === "ужин");
const plan = [];
let totalCalories = 0, totalProtein = 0, totalFat = 0, totalCarbs = 0, totalCost = 0;
days.forEach((day, i) => {
const b = breakfasts[i % breakfasts.length];
const l = lunches[i % lunches.length];
const d = dinners[i % dinners.length];
const dayCalories = b.calories + l.calories + d.calories;
const dayProtein = b.protein + l.protein + d.protein;
const dayFat = b.fat + l.fat + d.fat;
const dayCarbs = b.carbs + l.carbs + d.carbs;
const dayCost = calculateDayCost([b, l, d], data.products);
plan.push({
day,
date: new Date(Date.now() + i * 86400000).toISOString().slice(0, 10),
breakfast: { id: b.id, name: b.name, calories: b.calories, protein: b.protein, fat: b.fat, carbs: b.carbs, time_min: b.time_min },
lunch: { id: l.id, name: l.name, calories: l.calories, protein: l.protein, fat: l.fat, carbs: l.carbs, time_min: l.time_min },
dinner: { id: d.id, name: d.name, calories: d.calories, protein: d.protein, fat: d.fat, carbs: d.carbs, time_min: d.time_min },
daily_total: { calories: dayCalories, protein: dayProtein, fat: dayFat, carbs: dayCarbs, cost: dayCost }
});
totalCalories += dayCalories;
totalProtein += dayProtein;
totalFat += dayFat;
totalCarbs += dayCarbs;
totalCost += dayCost;
});
const weekly_totals = {
calories: Math.round(totalCalories),
protein: Math.round(totalProtein),
fat: Math.round(totalFat),
carbs: Math.round(totalCarbs),
cost: Math.round(totalCost),
avg_daily_calories: Math.round(totalCalories / 7)
};
return { plan, weekly_totals };
}
function calculateDayCost(meals, products) {
const productMap = {};
products.forEach(p => { productMap[p.id] = { ...p }; });
meals.forEach(m => {
m.ingredients.forEach(ing => {
if (!productMap[ing.product_id]) return;
const existing = productMap[ing.product_id];
const amountKg = ing.amount / 1000;
if (!existing.total_amount) existing.total_amount = 0;
existing.total_amount += amountKg;
});
});
let total = 0;
Object.values(productMap).forEach(p => {
if (p.total_amount) {
total += p.total_amount * p.price_per_kg;
}
});
return total;
}
function generateShoppingList(weeklyPlan) {
const data = readData();
const productMap = {};
data.products.forEach(p => { productMap[p.id] = { ...p, total_amount: 0, total_cost: 0 }; });
weeklyPlan.forEach(day => {
[day.breakfast, day.lunch, day.dinner].forEach(meal => {
const mealData = data.meals.find(m => m.id === meal.id);
if (!mealData) return;
mealData.ingredients.forEach(ing => {
if (productMap[ing.product_id]) {
productMap[ing.product_id].total_amount += ing.amount;
}
});
});
});
const shoppingList = [];
let totalCost = 0;
Object.values(productMap).forEach(p => {
if (p.total_amount > 0) {
const cost = (p.total_amount / 1000) * p.price_per_kg;
shoppingList.push({
id: p.id,
name: p.name,
category: p.category,
amount: Math.round(p.total_amount),
unit: p.total_amount >= 1000 ? "кг" : "г",
price_per_kg: p.price_per_kg,
cost: Math.round(cost)
});
totalCost += cost;
}
});
shoppingList.sort((a, b) => a.category.localeCompare(b.category) || a.name.localeCompare(b.name));
return { items: shoppingList, total_cost: Math.round(totalCost), budget: 100000, remaining: 100000 - Math.round(totalCost) };
}
function generateTimeline(weeklyPlan) {
const data = readData();
const timeline = [];
weeklyPlan.forEach((day, dayIndex) => {
const prepTime = [];
[day.breakfast, day.lunch, day.dinner].forEach((meal, mealIdx) => {
const mealData = data.meals.find(m => m.id === meal.id);
if (!mealData) return;
const mealTypes = ["завтрак", "обед", "ужин"];
prepTime.push({
day: day.day,
date: day.date,
meal_type: mealTypes[mealIdx],
dish: mealData.name,
prep_time_min: mealData.time_min,
ingredients: mealData.ingredients.map(i => i.name)
});
});
if (dayIndex === 0) {
timeline.push({
day: "Воскресенье (перед неделей)",
date: new Date(Date.now() - 86400000).toISOString().slice(0, 10),
action: "Закупка продуктов",
details: "Купить все продукты по списку на неделю",
time_min: 90
});
}
prepTime.forEach(p => timeline.push({
day: p.day,
date: p.date,
action: `Приготовление: ${p.meal_type}`,
details: p.dish,
time_min: p.prep_time_min,
ingredients: p.ingredients
}));
});
return timeline;
}
http.createServer(async (req, res) => {
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
if (req.method === "OPTIONS") {
res.writeHead(200);
return res.end();
}
const urlPath = req.url.split("?")[0];
if (req.method === "GET" && urlPath === "/api/meals") {
const data = readData();
res.writeHead(200, { "Content-Type": "application/json" });
return res.end(JSON.stringify(data.meals));
}
if (req.method === "GET" && urlPath === "/api/products") {
const data = readData();
res.writeHead(200, { "Content-Type": "application/json" });
return res.end(JSON.stringify(data.products));
}
if (req.method === "GET" && urlPath.startsWith("/api/meal/")) {
const id = urlPath.split("/")[3];
const data = readData();
const meal = data.meals.find(m => m.id === id);
res.writeHead(200, { "Content-Type": "application/json" });
return res.end(JSON.stringify(meal || {}));
}
if (req.method === "POST" && urlPath === "/api/generate-plan") {
const result = generateWeeklyPlan();
const data = readData();
data.weekly_plan = result.plan;
writeData(data);
res.writeHead(200, { "Content-Type": "application/json" });
return res.end(JSON.stringify(result));
}
if (req.method === "GET" && urlPath === "/api/plan") {
const data = readData();
res.writeHead(200, { "Content-Type": "application/json" });
return res.end(JSON.stringify(data.weekly_plan || []));
}
if (req.method === "POST" && urlPath === "/api/shopping-list") {
const bodyData = await body(req);
const shoppingList = generateShoppingList(bodyData.plan || []);
const data = readData();
data.shopping_list = shoppingList;
writeData(data);
res.writeHead(200, { "Content-Type": "application/json" });
return res.end(JSON.stringify(shoppingList));
}
if (req.method === "GET" && urlPath === "/api/shopping-list") {
const data = readData();
res.writeHead(200, { "Content-Type": "application/json" });
return res.end(JSON.stringify(data.shopping_list || { items: [], total_cost: 0 }));
}
if (req.method === "POST" && urlPath === "/api/timeline") {
const bodyData = await body(req);
const timeline = generateTimeline(bodyData.plan || []);
const data = readData();
data.timeline = timeline;
writeData(data);
res.writeHead(200, { "Content-Type": "application/json" });
return res.end(JSON.stringify(timeline));
}
if (req.method === "GET" && urlPath === "/api/timeline") {
const data = readData();
res.writeHead(200, { "Content-Type": "application/json" });
return res.end(JSON.stringify(data.timeline || []));
}
if (urlPath.startsWith("/api/")) {
res.writeHead(404, { "Content-Type": "application/json" });
return res.end(JSON.stringify({ error: "API endpoint not found: " + req.url }));
}
let file = req.url === "/" ? "/index.html" : urlPath;
const full = path.join(__dirname, file);
if (full.startsWith(__dirname) && fs.existsSync(full) && fs.statSync(full).isFile()) {
const ext = path.extname(full);
const type = ext === ".css" ? "text/css" : ext === ".js" ? "application/javascript" : "text/html";
res.writeHead(200, { "Content-Type": type + "; charset=utf-8" });
return res.end(fs.readFileSync(full));
}
res.writeHead(404);
res.end("Not found");
}).listen(PORT, () => console.log("Сервер запущен на порту " + PORT));