const express = require("express"); const sqlite3 = require("sqlite3").verbose(); const bcrypt = require("bcryptjs"); const session = require("express-session"); const path = require("path"); const helmet = require("helmet"); const rateLimit = require("express-rate-limit"); const bodyParser = require("body-parser"); require("dotenv").config(); const app = express(); const PORT = process.env.PORT || 3000; // Middleware для безопасности app.use( helmet({ contentSecurityPolicy: { directives: { defaultSrc: ["'self'"], scriptSrc: [ "'self'", "'unsafe-inline'", "https://cdnjs.cloudflare.com", ], styleSrc: ["'self'", "'unsafe-inline'", "https://cdnjs.cloudflare.com"], fontSrc: ["'self'", "https://cdnjs.cloudflare.com", "data:"], imgSrc: ["'self'", "data:"], }, }, }) ); // Ограничение запросов const limiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 минут max: 100, // максимум 100 запросов с одного IP }); app.use(limiter); // Статические файлы app.use(express.static(path.join(__dirname, "public"))); // Парсинг тела запроса app.use(bodyParser.urlencoded({ extended: true })); app.use(bodyParser.json()); // Настройка сессий app.use( session({ secret: process.env.SESSION_SECRET || "default-secret", resave: false, saveUninitialized: false, cookie: { secure: false }, // в продакшене установить true с HTTPS }) ); // Подключение к базе данных const db = new sqlite3.Database("./notes.db", (err) => { if (err) { console.error("Ошибка подключения к базе данных:", err.message); } else { console.log("Подключено к SQLite базе данных"); createTables(); } }); // Создание таблиц function createTables() { const createNotesTable = ` CREATE TABLE IF NOT EXISTS notes ( id INTEGER PRIMARY KEY AUTOINCREMENT, content TEXT NOT NULL, date TEXT NOT NULL, time TEXT NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ) `; db.run(createNotesTable, (err) => { if (err) { console.error("Ошибка создания таблицы заметок:", err.message); } else { console.log("Таблица заметок готова"); } }); } // Middleware для аутентификации function requireAuth(req, res, next) { if (req.session.authenticated) { return next(); } else { return res.redirect("/"); } } // Маршруты // Главная страница с формой входа app.get("/", (req, res) => { if (req.session.authenticated) { return res.redirect("/notes"); } res.sendFile(path.join(__dirname, "public", "index.html")); }); // Обработка входа app.post("/login", async (req, res) => { const { password } = req.body; const correctPassword = process.env.APP_PASSWORD; if (password === correctPassword) { req.session.authenticated = true; res.redirect("/notes"); } else { res.redirect("/?error=invalid_password"); } }); // Страница с заметками (требует аутентификации) app.get("/notes", requireAuth, (req, res) => { res.sendFile(path.join(__dirname, "public", "notes.html")); }); // API для получения всех заметок app.get("/api/notes", requireAuth, (req, res) => { const sql = "SELECT * FROM notes ORDER BY created_at ASC"; db.all(sql, [], (err, rows) => { if (err) { console.error("Ошибка получения заметок:", err.message); return res.status(500).json({ error: "Ошибка сервера" }); } res.json(rows); }); }); // API для создания новой заметки app.post("/api/notes", requireAuth, (req, res) => { const { content, date, time } = req.body; if (!content || !date || !time) { return res.status(400).json({ error: "Не все поля заполнены" }); } const sql = "INSERT INTO notes (content, date, time) VALUES (?, ?, ?)"; const params = [content, date, time]; db.run(sql, params, function (err) { if (err) { console.error("Ошибка создания заметки:", err.message); return res.status(500).json({ error: "Ошибка сервера" }); } res.json({ id: this.lastID, content, date, time }); }); }); // API для обновления заметки app.put("/api/notes/:id", requireAuth, (req, res) => { const { content, date, time } = req.body; const { id } = req.params; if (!content || !date || !time) { return res.status(400).json({ error: "Не все поля заполнены" }); } const sql = "UPDATE notes SET content = ?, date = ?, time = ? WHERE id = ?"; const params = [content, date, time, id]; db.run(sql, params, function (err) { if (err) { console.error("Ошибка обновления заметки:", err.message); return res.status(500).json({ error: "Ошибка сервера" }); } if (this.changes === 0) { return res.status(404).json({ error: "Заметка не найдена" }); } res.json({ id, content, date, time }); }); }); // API для удаления заметки app.delete("/api/notes/:id", requireAuth, (req, res) => { const { id } = req.params; const sql = "DELETE FROM notes WHERE id = ?"; db.run(sql, id, function (err) { if (err) { console.error("Ошибка удаления заметки:", err.message); return res.status(500).json({ error: "Ошибка сервера" }); } if (this.changes === 0) { return res.status(404).json({ error: "Заметка не найдена" }); } res.json({ message: "Заметка удалена" }); }); }); // Выход app.post("/logout", (req, res) => { req.session.destroy((err) => { if (err) { return res.status(500).json({ error: "Ошибка выхода" }); } res.redirect("/"); }); }); // Запуск сервера app.listen(PORT, () => { console.log(`🚀 Сервер запущен на порту ${PORT}`); console.log(`📝 Приложение для заметок готово к работе!`); });