Переход к серверному варианту приложения
This commit is contained in:
parent
56c0d6e648
commit
54b5608d1a
5
.cursor/rules/style.mdc
Normal file
5
.cursor/rules/style.mdc
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
alwaysApply: true
|
||||||
|
---
|
||||||
|
|
||||||
|
Save the design of my application with notes and try to create as similar a design as possible in new modules.
|
||||||
34
.gitignore
vendored
Normal file
34
.gitignore
vendored
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
# Зависимости
|
||||||
|
node_modules/
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
# База данных (можно исключить если не хотите хранить в репозитории)
|
||||||
|
notes.db
|
||||||
|
*.db
|
||||||
|
|
||||||
|
# Переменные окружения
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
|
||||||
|
# Логи
|
||||||
|
logs/
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Системные файлы
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Редакторские файлы
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
|
||||||
|
# Временные файлы сборки
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
138
README.md
Normal file
138
README.md
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
# NoteJS - Приложение для быстрых заметок
|
||||||
|
|
||||||
|
Простое веб-приложение для создания и управления заметками с поддержкой Markdown форматирования.
|
||||||
|
|
||||||
|
## Особенности
|
||||||
|
|
||||||
|
- 🚀 Создано на Node.js + Express
|
||||||
|
- 🔐 Аутентификация по паролю (без логина)
|
||||||
|
- 💾 Хранение данных в SQLite базе данных
|
||||||
|
- 📝 Поддержка Markdown форматирования
|
||||||
|
- 🎨 Простой и интуитивный интерфейс
|
||||||
|
- 📱 Адаптивный дизайн
|
||||||
|
|
||||||
|
## Установка и запуск
|
||||||
|
|
||||||
|
### Предварительные требования
|
||||||
|
|
||||||
|
- Node.js (версия 14 или выше)
|
||||||
|
- npm
|
||||||
|
|
||||||
|
### Установка
|
||||||
|
|
||||||
|
1. Клонируйте репозиторий:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone <repository-url>
|
||||||
|
cd NoteJS
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Установите зависимости:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Настройте аутентификацию:
|
||||||
|
- Откройте файл `.env`
|
||||||
|
- Установите пароль для входа в переменной `APP_PASSWORD`
|
||||||
|
- Установите секрет сессии в переменной `SESSION_SECRET`
|
||||||
|
- Установите порт в переменной `PORT` (по умолчанию 3000)
|
||||||
|
|
||||||
|
Пример файла `.env`:
|
||||||
|
|
||||||
|
```env
|
||||||
|
APP_PASSWORD=your_secure_password_here
|
||||||
|
SESSION_SECRET=your_session_secret_here
|
||||||
|
PORT=3000
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Запустите сервер:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm start
|
||||||
|
```
|
||||||
|
|
||||||
|
5. Откройте браузер и перейдите по адресу `http://localhost:3000`
|
||||||
|
|
||||||
|
## Использование
|
||||||
|
|
||||||
|
### Вход в систему
|
||||||
|
|
||||||
|
1. При первом запуске введите пароль, указанный в файле `.env`
|
||||||
|
2. После успешного входа вы попадете в интерфейс заметок
|
||||||
|
|
||||||
|
### Создание заметок
|
||||||
|
|
||||||
|
1. Введите текст заметки в поле ввода
|
||||||
|
2. Используйте кнопки форматирования для добавления Markdown элементов:
|
||||||
|
- **B** - жирный текст
|
||||||
|
- _I_ - курсив
|
||||||
|
- H - заголовок
|
||||||
|
- 📋 - элемент списка
|
||||||
|
- " - цитата
|
||||||
|
- `</>` - код
|
||||||
|
- 🔗 - ссылка
|
||||||
|
3. Нажмите кнопку "Сохранить"
|
||||||
|
|
||||||
|
### Редактирование заметок
|
||||||
|
|
||||||
|
1. Нажмите кнопку "Редактировать" рядом с заметкой
|
||||||
|
2. Отредактируйте текст в появившемся поле ввода
|
||||||
|
3. Нажмите кнопку "Сохранить"
|
||||||
|
|
||||||
|
### Удаление заметок
|
||||||
|
|
||||||
|
1. Нажмите кнопку "Удалить" рядом с заметкой
|
||||||
|
2. Подтвердите удаление в появившемся диалоговом окне
|
||||||
|
|
||||||
|
## Структура проекта
|
||||||
|
|
||||||
|
```
|
||||||
|
NoteJS/
|
||||||
|
├── public/ # Статические файлы (клиентская часть)
|
||||||
|
│ ├── index.html # Страница входа
|
||||||
|
│ ├── notes.html # Страница заметок
|
||||||
|
│ ├── style.css # Стили
|
||||||
|
│ └── app.js # Клиентский JavaScript
|
||||||
|
├── server.js # Express сервер
|
||||||
|
├── .env # Конфигурация (не включать в git!)
|
||||||
|
├── package.json # Зависимости проекта
|
||||||
|
├── notes.db # SQLite база данных (создается автоматически)
|
||||||
|
└── README.md # Документация
|
||||||
|
```
|
||||||
|
|
||||||
|
## API Endpoints
|
||||||
|
|
||||||
|
### Аутентификация
|
||||||
|
|
||||||
|
- `POST /login` - вход в систему
|
||||||
|
- `POST /logout` - выход из системы
|
||||||
|
|
||||||
|
### Заметки (требуют аутентификации)
|
||||||
|
|
||||||
|
- `GET /api/notes` - получить все заметки
|
||||||
|
- `POST /api/notes` - создать новую заметку
|
||||||
|
- `PUT /api/notes/:id` - обновить заметку
|
||||||
|
- `DELETE /api/notes/:id` - удалить заметку
|
||||||
|
|
||||||
|
## Безопасность
|
||||||
|
|
||||||
|
- Helmet для защиты от распространенных уязвимостей
|
||||||
|
- Ограничение запросов (rate limiting)
|
||||||
|
- Сессионная аутентификация
|
||||||
|
- Защищенные заголовки
|
||||||
|
|
||||||
|
## Разработка
|
||||||
|
|
||||||
|
Для разработки используйте:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Этот скрипт использует **nodemon** для автоматической перезагрузки сервера при изменении файлов. Больше не нужно вручную перезапускать сервер при каждом изменении кода!
|
||||||
|
|
||||||
|
## Лицензия
|
||||||
|
|
||||||
|
Этот проект создан Fovway.
|
||||||
2436
package-lock.json
generated
Normal file
2436
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
32
package.json
Normal file
32
package.json
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"name": "notejs",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "app.js",
|
||||||
|
"scripts": {
|
||||||
|
"start": "node server.js",
|
||||||
|
"dev": "nodemon server.js"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://git.fovway.ru/Fovway/NoteJS.git"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"bcryptjs": "^3.0.2",
|
||||||
|
"body-parser": "^2.2.0",
|
||||||
|
"cors": "^2.8.5",
|
||||||
|
"dotenv": "^17.2.3",
|
||||||
|
"express": "^5.1.0",
|
||||||
|
"express-rate-limit": "^8.1.0",
|
||||||
|
"express-session": "^1.18.2",
|
||||||
|
"helmet": "^8.1.0",
|
||||||
|
"marked": "^16.4.0",
|
||||||
|
"sqlite3": "^5.1.7"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"nodemon": "^3.1.10"
|
||||||
|
}
|
||||||
|
}
|
||||||
391
public/app.js
Normal file
391
public/app.js
Normal file
@ -0,0 +1,391 @@
|
|||||||
|
// DOM элементы
|
||||||
|
const noteInput = document.getElementById("noteInput");
|
||||||
|
const saveBtn = document.getElementById("saveBtn");
|
||||||
|
const notesList = document.getElementById("notesList");
|
||||||
|
|
||||||
|
// Получаем кнопки markdown
|
||||||
|
const boldBtn = document.getElementById("boldBtn");
|
||||||
|
const italicBtn = document.getElementById("italicBtn");
|
||||||
|
const headerBtn = document.getElementById("headerBtn");
|
||||||
|
const listBtn = document.getElementById("listBtn");
|
||||||
|
const quoteBtn = document.getElementById("quoteBtn");
|
||||||
|
const codeBtn = document.getElementById("codeBtn");
|
||||||
|
const linkBtn = document.getElementById("linkBtn");
|
||||||
|
|
||||||
|
// Функция для получения текущей даты и времени
|
||||||
|
function getFormattedDateTime() {
|
||||||
|
let now = new Date();
|
||||||
|
let day = String(now.getDate()).padStart(2, "0");
|
||||||
|
let month = String(now.getMonth() + 1).padStart(2, "0");
|
||||||
|
let year = now.getFullYear();
|
||||||
|
let hours = String(now.getHours()).padStart(2, "0");
|
||||||
|
let minutes = String(now.getMinutes()).padStart(2, "0");
|
||||||
|
|
||||||
|
return {
|
||||||
|
date: `${day}.${month}.${year}`,
|
||||||
|
time: `${hours}:${minutes}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Функция для авторасширения текстового поля
|
||||||
|
function autoExpandTextarea(textarea) {
|
||||||
|
textarea.style.height = "auto";
|
||||||
|
textarea.style.height = textarea.scrollHeight + "px";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Привязываем авторасширение к текстовому полю для создания заметки
|
||||||
|
noteInput.addEventListener("input", function () {
|
||||||
|
autoExpandTextarea(noteInput);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Изначально запускаем для установки правильной высоты
|
||||||
|
autoExpandTextarea(noteInput);
|
||||||
|
|
||||||
|
// Функция для вставки markdown
|
||||||
|
function insertMarkdown(tag) {
|
||||||
|
const start = noteInput.selectionStart;
|
||||||
|
const end = noteInput.selectionEnd;
|
||||||
|
const text = noteInput.value;
|
||||||
|
|
||||||
|
const before = text.substring(0, start);
|
||||||
|
const selected = text.substring(start, end);
|
||||||
|
const after = text.substring(end);
|
||||||
|
|
||||||
|
if (selected.startsWith(tag) && selected.endsWith(tag)) {
|
||||||
|
// Если теги уже есть, удаляем их
|
||||||
|
noteInput.value = `${before}${selected.slice(
|
||||||
|
tag.length,
|
||||||
|
-tag.length
|
||||||
|
)}${after}`;
|
||||||
|
noteInput.setSelectionRange(start, end - 2 * tag.length);
|
||||||
|
} else if (selected.trim() === "") {
|
||||||
|
// Если текст не выделен
|
||||||
|
if (tag === "[Текст ссылки](URL)") {
|
||||||
|
// Для ссылок создаем шаблон с двумя кавычками
|
||||||
|
noteInput.value = `${before}[Текст ссылки](URL)${after}`;
|
||||||
|
const cursorPosition = start + 1; // Помещаем курсор внутрь текста ссылки
|
||||||
|
noteInput.setSelectionRange(cursorPosition, cursorPosition + 12);
|
||||||
|
} else if (tag === "- " || tag === "> " || tag === "# ") {
|
||||||
|
// Для списка, цитаты и заголовка помещаем курсор после `- `, `> ` или `# `
|
||||||
|
noteInput.value = `${before}${tag}${after}`;
|
||||||
|
const cursorPosition = start + tag.length;
|
||||||
|
noteInput.setSelectionRange(cursorPosition, cursorPosition);
|
||||||
|
} else {
|
||||||
|
// Для остальных типов создаем два тега
|
||||||
|
noteInput.value = `${before}${tag}${tag}${after}`;
|
||||||
|
const cursorPosition = start + tag.length;
|
||||||
|
noteInput.setSelectionRange(cursorPosition, cursorPosition);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Если текст выделен
|
||||||
|
if (tag === "[Текст ссылки](URL)") {
|
||||||
|
// Для ссылок используем выделенный текст вместо "Текст ссылки"
|
||||||
|
noteInput.value = `${before}[${selected}](URL)${after}`;
|
||||||
|
const cursorPosition = start + selected.length + 3; // Помещаем курсор в URL
|
||||||
|
noteInput.setSelectionRange(cursorPosition, cursorPosition + 3);
|
||||||
|
} else if (tag === "- " || tag === "> " || tag === "# ") {
|
||||||
|
// Для списка, цитаты и заголовка добавляем `- `, `> ` или `# ` перед выделенным текстом
|
||||||
|
noteInput.value = `${before}${tag}${selected}${after}`;
|
||||||
|
const cursorPosition = start + tag.length + selected.length;
|
||||||
|
noteInput.setSelectionRange(cursorPosition, cursorPosition);
|
||||||
|
} else {
|
||||||
|
// Для остальных типов оборачиваем выделенный текст
|
||||||
|
noteInput.value = `${before}${tag}${selected}${tag}${after}`;
|
||||||
|
const cursorPosition = start + tag.length + selected.length + tag.length;
|
||||||
|
noteInput.setSelectionRange(cursorPosition, cursorPosition);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
noteInput.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Функция для вставки markdown в режиме редактирования
|
||||||
|
function insertMarkdownForEdit(textarea, tag) {
|
||||||
|
const start = textarea.selectionStart;
|
||||||
|
const end = textarea.selectionEnd;
|
||||||
|
const text = textarea.value;
|
||||||
|
|
||||||
|
const before = text.substring(0, start);
|
||||||
|
const selected = text.substring(start, end);
|
||||||
|
const after = text.substring(end);
|
||||||
|
|
||||||
|
if (selected.startsWith(tag) && selected.endsWith(tag)) {
|
||||||
|
// Если теги уже есть, удаляем их
|
||||||
|
textarea.value = `${before}${selected.slice(
|
||||||
|
tag.length,
|
||||||
|
-tag.length
|
||||||
|
)}${after}`;
|
||||||
|
textarea.setSelectionRange(start, end - 2 * tag.length);
|
||||||
|
} else if (selected.trim() === "") {
|
||||||
|
// Если текст не выделен
|
||||||
|
if (tag === "[Текст ссылки](URL)") {
|
||||||
|
// Для ссылок создаем шаблон с двумя кавычками
|
||||||
|
textarea.value = `${before}[Текст ссылки](URL)${after}`;
|
||||||
|
const cursorPosition = start + 1; // Помещаем курсор внутрь текста ссылки
|
||||||
|
textarea.setSelectionRange(cursorPosition, cursorPosition + 12);
|
||||||
|
} else if (tag === "- " || tag === "> " || tag === "# ") {
|
||||||
|
// Для списка, цитаты и заголовка помещаем курсор после `- `, `> ` или `# `
|
||||||
|
textarea.value = `${before}${tag}${after}`;
|
||||||
|
const cursorPosition = start + tag.length;
|
||||||
|
textarea.setSelectionRange(cursorPosition, cursorPosition);
|
||||||
|
} else {
|
||||||
|
// Для остальных типов создаем два тега
|
||||||
|
textarea.value = `${before}${tag}${tag}${after}`;
|
||||||
|
const cursorPosition = start + tag.length;
|
||||||
|
textarea.setSelectionRange(cursorPosition, cursorPosition);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Если текст выделен
|
||||||
|
if (tag === "[Текст ссылки](URL)") {
|
||||||
|
// Для ссылок используем выделенный текст вместо "Текст ссылки"
|
||||||
|
textarea.value = `${before}[${selected}](URL)${after}`;
|
||||||
|
const cursorPosition = start + selected.length + 3; // Помещаем курсор в URL
|
||||||
|
textarea.setSelectionRange(cursorPosition, cursorPosition + 3);
|
||||||
|
} else if (tag === "- " || tag === "> " || tag === "# ") {
|
||||||
|
// Для списка, цитаты и заголовка добавляем `- `, `> ` или `# ` перед выделенным текстом
|
||||||
|
textarea.value = `${before}${tag}${selected}${after}`;
|
||||||
|
const cursorPosition = start + tag.length + selected.length;
|
||||||
|
textarea.setSelectionRange(cursorPosition, cursorPosition);
|
||||||
|
} else {
|
||||||
|
// Для остальных типов оборачиваем выделенный текст
|
||||||
|
textarea.value = `${before}${tag}${selected}${tag}${after}`;
|
||||||
|
const cursorPosition = start + tag.length + selected.length + tag.length;
|
||||||
|
textarea.setSelectionRange(cursorPosition, cursorPosition);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обработчики для кнопок markdown
|
||||||
|
boldBtn.addEventListener("click", function () {
|
||||||
|
insertMarkdown("**");
|
||||||
|
});
|
||||||
|
|
||||||
|
italicBtn.addEventListener("click", function () {
|
||||||
|
insertMarkdown("*");
|
||||||
|
});
|
||||||
|
|
||||||
|
headerBtn.addEventListener("click", function () {
|
||||||
|
insertMarkdown("# ");
|
||||||
|
});
|
||||||
|
|
||||||
|
listBtn.addEventListener("click", function () {
|
||||||
|
insertMarkdown("- ");
|
||||||
|
});
|
||||||
|
|
||||||
|
quoteBtn.addEventListener("click", function () {
|
||||||
|
insertMarkdown("> ");
|
||||||
|
});
|
||||||
|
|
||||||
|
codeBtn.addEventListener("click", function () {
|
||||||
|
insertMarkdown("`");
|
||||||
|
});
|
||||||
|
|
||||||
|
linkBtn.addEventListener("click", function () {
|
||||||
|
insertMarkdown("[Текст ссылки](URL)");
|
||||||
|
});
|
||||||
|
|
||||||
|
// Функция для загрузки заметок с сервера
|
||||||
|
async function loadNotes() {
|
||||||
|
try {
|
||||||
|
const response = await fetch("/api/notes");
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error("Ошибка загрузки заметок");
|
||||||
|
}
|
||||||
|
const notes = await response.json();
|
||||||
|
renderNotes(notes);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Ошибка:", error);
|
||||||
|
notesList.innerHTML = "<p>Ошибка загрузки заметок</p>";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Функция для отображения заметок
|
||||||
|
function renderNotes(notes) {
|
||||||
|
notesList.innerHTML = "";
|
||||||
|
|
||||||
|
// Итерируемся по заметкам в обычном порядке, чтобы новые были сверху
|
||||||
|
notes.forEach(function (note) {
|
||||||
|
const noteHtml = `
|
||||||
|
<div id="note" class="container">
|
||||||
|
<div class="date">
|
||||||
|
${note.date} ${note.time}
|
||||||
|
<div id="editBtn" class="notesHeaderBtn" data-id="${
|
||||||
|
note.id
|
||||||
|
}">Редактировать</div>
|
||||||
|
<div id="deleteBtn" class="notesHeaderBtn" data-id="${
|
||||||
|
note.id
|
||||||
|
}">Удалить</div>
|
||||||
|
</div>
|
||||||
|
<div class="textNote" data-original-content="${note.content.replace(
|
||||||
|
/"/g,
|
||||||
|
"""
|
||||||
|
)}">${marked.parse(note.content)}</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
notesList.insertAdjacentHTML("afterbegin", noteHtml);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Добавляем обработчики событий для кнопок редактирования и удаления
|
||||||
|
addNoteEventListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Функция для добавления обработчиков событий к заметкам
|
||||||
|
function addNoteEventListeners() {
|
||||||
|
// Обработчик удаления
|
||||||
|
document.querySelectorAll("#deleteBtn").forEach((btn) => {
|
||||||
|
btn.addEventListener("click", async function (event) {
|
||||||
|
const noteId = event.target.dataset.id;
|
||||||
|
if (confirm("Вы уверены, что хотите удалить эту заметку?")) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/notes/${noteId}`, {
|
||||||
|
method: "DELETE",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error("Ошибка удаления заметки");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Перезагружаем заметки
|
||||||
|
loadNotes();
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Ошибка:", error);
|
||||||
|
alert("Ошибка удаления заметки");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Обработчик редактирования
|
||||||
|
document.querySelectorAll("#editBtn").forEach((btn) => {
|
||||||
|
btn.addEventListener("click", function (event) {
|
||||||
|
const noteId = event.target.dataset.id;
|
||||||
|
const noteContainer = event.target.closest("#note");
|
||||||
|
const noteContent = noteContainer.querySelector(".textNote");
|
||||||
|
|
||||||
|
// Создаем контейнер для markdown кнопок
|
||||||
|
const markdownButtonsContainer = document.createElement("div");
|
||||||
|
markdownButtonsContainer.classList.add("markdown-buttons");
|
||||||
|
|
||||||
|
// Создаем markdown кнопки
|
||||||
|
const markdownButtons = [
|
||||||
|
{ id: "editBoldBtn", icon: "fas fa-bold", tag: "**" },
|
||||||
|
{ id: "editItalicBtn", icon: "fas fa-italic", tag: "*" },
|
||||||
|
{ id: "editHeaderBtn", icon: "fas fa-heading", tag: "# " },
|
||||||
|
{ id: "editListBtn", icon: "fas fa-list-ul", tag: "- " },
|
||||||
|
{ id: "editQuoteBtn", icon: "fas fa-quote-right", tag: "> " },
|
||||||
|
{ id: "editCodeBtn", icon: "fas fa-code", tag: "`" },
|
||||||
|
{ id: "editLinkBtn", icon: "fas fa-link", tag: "[Текст ссылки](URL)" },
|
||||||
|
];
|
||||||
|
|
||||||
|
markdownButtons.forEach((button) => {
|
||||||
|
const btn = document.createElement("button");
|
||||||
|
btn.classList.add("btnMarkdown");
|
||||||
|
btn.id = button.id;
|
||||||
|
btn.innerHTML = `<i class="${button.icon}"></i>`;
|
||||||
|
markdownButtonsContainer.appendChild(btn);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Создаем textarea с уже существующим классом textInput
|
||||||
|
const textarea = document.createElement("textarea");
|
||||||
|
textarea.classList.add("textInput");
|
||||||
|
// Получаем исходный markdown контент из data-атрибута или используем textContent как fallback
|
||||||
|
textarea.value =
|
||||||
|
noteContent.dataset.originalContent || noteContent.textContent;
|
||||||
|
|
||||||
|
// Привязываем авторасширение к textarea для редактирования
|
||||||
|
textarea.addEventListener("input", function () {
|
||||||
|
autoExpandTextarea(textarea);
|
||||||
|
});
|
||||||
|
autoExpandTextarea(textarea);
|
||||||
|
|
||||||
|
// Кнопка сохранить
|
||||||
|
const saveEditBtn = document.createElement("button");
|
||||||
|
saveEditBtn.textContent = "Сохранить";
|
||||||
|
saveEditBtn.classList.add("btnSave");
|
||||||
|
|
||||||
|
// Очищаем текущий контент и вставляем markdown кнопки, textarea и кнопку сохранить
|
||||||
|
noteContent.innerHTML = "";
|
||||||
|
noteContent.appendChild(markdownButtonsContainer);
|
||||||
|
noteContent.appendChild(textarea);
|
||||||
|
noteContent.appendChild(saveEditBtn);
|
||||||
|
|
||||||
|
// Добавляем обработчики для markdown кнопок редактирования
|
||||||
|
markdownButtons.forEach((button) => {
|
||||||
|
const btn = document.getElementById(button.id);
|
||||||
|
btn.addEventListener("click", function () {
|
||||||
|
insertMarkdownForEdit(textarea, button.tag);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Обработчик сохранения редактирования
|
||||||
|
saveEditBtn.addEventListener("click", async function () {
|
||||||
|
if (textarea.value.trim() !== "") {
|
||||||
|
try {
|
||||||
|
const { date, time } = getFormattedDateTime();
|
||||||
|
const response = await fetch(`/api/notes/${noteId}`, {
|
||||||
|
method: "PUT",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
content: textarea.value,
|
||||||
|
date: date,
|
||||||
|
time: time,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error("Ошибка сохранения заметки");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Перезагружаем заметки
|
||||||
|
loadNotes();
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Ошибка:", error);
|
||||||
|
alert("Ошибка сохранения заметки");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обработчик сохранения новой заметки
|
||||||
|
saveBtn.addEventListener("click", async function () {
|
||||||
|
if (noteInput.value.trim() !== "") {
|
||||||
|
try {
|
||||||
|
const { date, time } = getFormattedDateTime();
|
||||||
|
|
||||||
|
const response = await fetch("/api/notes", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
content: noteInput.value,
|
||||||
|
date: date,
|
||||||
|
time: time,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error("Ошибка сохранения заметки");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Очищаем поле ввода и перезагружаем заметки
|
||||||
|
noteInput.value = "";
|
||||||
|
noteInput.style.height = "auto";
|
||||||
|
loadNotes();
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Ошибка:", error);
|
||||||
|
alert("Ошибка сохранения заметки");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Загружаем заметки при загрузке страницы
|
||||||
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
|
loadNotes();
|
||||||
|
});
|
||||||
40
public/index.html
Normal file
40
public/index.html
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Вход в систему заметок</title>
|
||||||
|
<link rel="stylesheet" href="/style.css" />
|
||||||
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css"
|
||||||
|
/>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<header>Вход в систему заметок</header>
|
||||||
|
<div class="login-form">
|
||||||
|
<form action="/login" method="POST">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="password">Пароль:</label>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
id="password"
|
||||||
|
name="password"
|
||||||
|
required
|
||||||
|
placeholder="Введите пароль"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btnSave">Войти</button>
|
||||||
|
</form>
|
||||||
|
<div id="errorMessage" class="error-message" style="display: none">
|
||||||
|
Неверный пароль!
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="footer">
|
||||||
|
<p>Создатель: <span>Fovway</span></p>
|
||||||
|
</div>
|
||||||
|
<script src="/login.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
5
public/login.js
Normal file
5
public/login.js
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
// Проверяем наличие ошибки в URL
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
if (urlParams.get("error") === "invalid_password") {
|
||||||
|
document.getElementById("errorMessage").style.display = "block";
|
||||||
|
}
|
||||||
65
public/notes.html
Normal file
65
public/notes.html
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Заметки</title>
|
||||||
|
<link rel="stylesheet" href="/style.css" />
|
||||||
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css"
|
||||||
|
/>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<header class="notes-header">
|
||||||
|
<span>Заметки</span>
|
||||||
|
<form action="/logout" method="POST" style="display: inline">
|
||||||
|
<button type="submit" class="logout-btn">Выйти</button>
|
||||||
|
</form>
|
||||||
|
</header>
|
||||||
|
<div class="main">
|
||||||
|
<div class="markdown-buttons">
|
||||||
|
<button class="btnMarkdown" id="boldBtn">
|
||||||
|
<i class="fas fa-bold"></i>
|
||||||
|
</button>
|
||||||
|
<button class="btnMarkdown" id="italicBtn">
|
||||||
|
<i class="fas fa-italic"></i>
|
||||||
|
</button>
|
||||||
|
<button class="btnMarkdown" id="headerBtn">
|
||||||
|
<i class="fas fa-heading"></i>
|
||||||
|
</button>
|
||||||
|
<button class="btnMarkdown" id="listBtn">
|
||||||
|
<i class="fas fa-list-ul"></i>
|
||||||
|
</button>
|
||||||
|
<button class="btnMarkdown" id="quoteBtn">
|
||||||
|
<i class="fas fa-quote-right"></i>
|
||||||
|
</button>
|
||||||
|
<button class="btnMarkdown" id="codeBtn">
|
||||||
|
<i class="fas fa-code"></i>
|
||||||
|
</button>
|
||||||
|
<button class="btnMarkdown" id="linkBtn">
|
||||||
|
<i class="fas fa-link"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<textarea
|
||||||
|
class="textInput"
|
||||||
|
id="noteInput"
|
||||||
|
placeholder="Ваша заметка..."
|
||||||
|
></textarea>
|
||||||
|
<button class="btnSave" id="saveBtn">Сохранить</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="notes-container">
|
||||||
|
<div id="notesList">
|
||||||
|
<!-- Заметки будут загружаться здесь -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="footer">
|
||||||
|
<p>Создатель: <span>Fovway</span></p>
|
||||||
|
</div>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/marked/11.1.0/marked.min.js"></script>
|
||||||
|
<script src="/app.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
237
public/style.css
Normal file
237
public/style.css
Normal file
@ -0,0 +1,237 @@
|
|||||||
|
body {
|
||||||
|
font-family: "Open Sans", sans-serif;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
background: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notes-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logout-btn {
|
||||||
|
padding: 8px 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
border-radius: 5px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #dc3545;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logout-btn:hover {
|
||||||
|
background-color: #dc3545;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
width: 90%;
|
||||||
|
max-width: 600px;
|
||||||
|
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 15px;
|
||||||
|
margin-top: 20px;
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-form {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 5px;
|
||||||
|
font-size: 16px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group input:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #007bff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-message {
|
||||||
|
color: #dc3545;
|
||||||
|
margin-top: 10px;
|
||||||
|
padding: 10px;
|
||||||
|
background-color: #f8d7da;
|
||||||
|
border: 1px solid #f5c6cb;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 50px;
|
||||||
|
resize: none;
|
||||||
|
border: none;
|
||||||
|
background: white;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
overflow-y: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btnSave {
|
||||||
|
padding: 10px 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
border-width: 1px;
|
||||||
|
background: white;
|
||||||
|
border-radius: 5px;
|
||||||
|
font-family: "Open Sans", sans-serif;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btnSave:hover {
|
||||||
|
background-color: #007bff;
|
||||||
|
color: white;
|
||||||
|
border-color: #007bff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date {
|
||||||
|
font-size: 11px;
|
||||||
|
color: grey;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notesHeaderBtn {
|
||||||
|
display: inline-block;
|
||||||
|
cursor: pointer;
|
||||||
|
color: black;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.textNote {
|
||||||
|
margin-top: 10px;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Убираем стандартные отступы для абзацев */
|
||||||
|
.textNote p {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Убираем маргины у заголовков */
|
||||||
|
.textNote h1,
|
||||||
|
.textNote h2,
|
||||||
|
.textNote h3,
|
||||||
|
.textNote h4,
|
||||||
|
.textNote h5,
|
||||||
|
.textNote h6 {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Убираем отступы у списков */
|
||||||
|
.textNote ul,
|
||||||
|
.textNote ol {
|
||||||
|
margin: 0;
|
||||||
|
padding-left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Убираем маргины у элементов списка */
|
||||||
|
.textNote li {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Стили для ссылок */
|
||||||
|
.textNote a {
|
||||||
|
color: #007bff;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.textNote a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Стили для кода */
|
||||||
|
.textNote pre {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 5px;
|
||||||
|
font-size: 14px;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.textNote code {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
padding: 2px 4px;
|
||||||
|
border-radius: 5px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notes-container {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
padding-bottom: 60px; /* Отступ снизу для footer */
|
||||||
|
}
|
||||||
|
|
||||||
|
#notesList {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-buttons {
|
||||||
|
margin-top: 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-buttons .btnMarkdown {
|
||||||
|
padding: 5px 10px;
|
||||||
|
margin-right: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
border-radius: 5px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-buttons .btnMarkdown:hover {
|
||||||
|
background-color: #e0e0e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer span {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
214
server.js
Normal file
214
server.js
Normal file
@ -0,0 +1,214 @@
|
|||||||
|
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(`📝 Приложение для заметок готово к работе!`);
|
||||||
|
});
|
||||||
Loading…
x
Reference in New Issue
Block a user