feat: frontend, sqlite integration
This commit is contained in:
1
backend/.gitignore
vendored
Normal file
1
backend/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
notes.db
|
||||
7
backend/package-lock.json
generated
7
backend/package-lock.json
generated
@@ -11,6 +11,7 @@
|
||||
"dependencies": {
|
||||
"cors": "^2.8.5",
|
||||
"express": "^5.2.1",
|
||||
"sqlite": "^5.1.1",
|
||||
"swagger-jsdoc": "^6.2.8",
|
||||
"swagger-ui-express": "^5.0.1"
|
||||
}
|
||||
@@ -1017,6 +1018,12 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/sqlite": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/sqlite/-/sqlite-5.1.1.tgz",
|
||||
"integrity": "sha512-oBkezXa2hnkfuJwUo44Hl9hS3er+YFtueifoajrgidvqsJRQFpc5fKoAkAor1O5ZnLoa28GBScfHXs8j0K358Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/statuses": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
"dependencies": {
|
||||
"cors": "^2.8.5",
|
||||
"express": "^5.2.1",
|
||||
"sqlite": "^5.1.1",
|
||||
"swagger-jsdoc": "^6.2.8",
|
||||
"swagger-ui-express": "^5.0.1"
|
||||
}
|
||||
|
||||
@@ -1,256 +1,182 @@
|
||||
import express from 'express';
|
||||
import { json } from 'express';
|
||||
import cors from 'cors';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { dirname, join } from 'path';
|
||||
import sqlite3 from 'sqlite3';
|
||||
import { open } from 'sqlite';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
// Создаем экземпляр приложения Express
|
||||
const app = express();
|
||||
|
||||
// Подключаем наши middleware (промежуточные обработчики маршрутов)
|
||||
|
||||
// middlware CORS разрешает запросы с других доменов. Без этого браузер
|
||||
// запретит фронтенду общаться с бекендом!
|
||||
app.use(cors({
|
||||
origin: 'http://localhost:8080', // разрешаем только с этого домена
|
||||
methods: ['GET', 'POST', 'PUT', 'DELETE'], // разрешаем только эти методы
|
||||
allowedHeaders: ['Content-Type'] // разрешаем только эти заголовки
|
||||
origin: 'http://localhost:8080',
|
||||
methods: ['GET', 'POST', 'PUT', 'DELETE'],
|
||||
allowedHeaders: ['Content-Type']
|
||||
}));
|
||||
// JSON парсер позволяет обрабатывать JSON в теле запросов
|
||||
app.use(json());
|
||||
|
||||
// Создаем массив для хранения заметок в памяти
|
||||
// В реальном приложении здесь была бы база данных
|
||||
let notes = [];
|
||||
let db;
|
||||
|
||||
// Переменная для генерации уникальных ID
|
||||
// Каждая новая заметка получит ID на 1 больше предыдущей
|
||||
let noteIdCounter = 1;
|
||||
|
||||
// ==================== РАЗДЕЛ: РОУТЫ API (маршруты) ====================
|
||||
|
||||
// РОУТ 1: Получение всех заметок (READ)
|
||||
// Метод: GET
|
||||
// Адрес: /notes
|
||||
app.get('/notes', function(request, response) {
|
||||
/*
|
||||
Эта функция обрабатывает запрос на получение всех заметок
|
||||
request - объект запроса (содержит данные от клиента)
|
||||
response - объект ответа (используем для отправки данных клиенту)
|
||||
*/
|
||||
console.log('Получен запрос на получение всех заметок');
|
||||
|
||||
// Отправляем клиенту все заметки в формате JSON
|
||||
// status(200) - код 200 означает "Успешно"
|
||||
response.status(200).json({
|
||||
success: true, // флаг успешного выполнения
|
||||
data: notes, // сами заметки
|
||||
count: notes.length // количество заметок
|
||||
async function initializeDatabase() {
|
||||
db = await open({
|
||||
filename: './notes.db',
|
||||
driver: sqlite3.Database
|
||||
});
|
||||
});
|
||||
|
||||
// РОУТ 2: Получение одной заметки по ID (READ)
|
||||
// Метод: GET
|
||||
// Адрес: /notes/:id
|
||||
// :id - это параметр маршрута (динамическая часть URL)
|
||||
app.get('/notes/:id', function(request, response) {
|
||||
/*
|
||||
Эта функция ищет заметку по ID
|
||||
request.params.id - получаем ID из URL
|
||||
*/
|
||||
console.log('Получен запрос на получение заметки с ID:', request.params.id);
|
||||
|
||||
// Преобразуем ID из строки в число
|
||||
const noteId = parseInt(request.params.id);
|
||||
|
||||
// Ищем заметку в массиве по ID.
|
||||
// В качестве аргумента notes.find() используем
|
||||
// функцию function(note) {}, которая определяет,
|
||||
// совпадает ли id некоторой заметки с id нашей
|
||||
const foundNote = notes.find(function(note) {
|
||||
return note.id === noteId;
|
||||
});
|
||||
|
||||
// Если заметка не найдена
|
||||
if (!foundNote) {
|
||||
console.log('Заметка не найдена');
|
||||
return response.status(404).json({
|
||||
await db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS notes (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
title TEXT NOT NULL,
|
||||
text TEXT NOT NULL,
|
||||
author TEXT NOT NULL,
|
||||
createdAt DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updatedAt DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
`);
|
||||
}
|
||||
|
||||
initializeDatabase();
|
||||
|
||||
app.get('/notes', async (req, res) => {
|
||||
try {
|
||||
const notes = await db.all('SELECT * FROM notes ORDER BY createdAt DESC');
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: notes,
|
||||
count: notes.length
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Заметка с таким ID не найдена'
|
||||
message: 'Ошибка при получении заметок'
|
||||
});
|
||||
}
|
||||
|
||||
// Если заметка найдена - отправляем ее клиенту
|
||||
response.status(200).json({
|
||||
success: true,
|
||||
data: foundNote
|
||||
});
|
||||
});
|
||||
|
||||
// РОУТ 3: Создание новой заметки (CREATE)
|
||||
// Метод: POST
|
||||
// Адрес: /notes
|
||||
app.post('/notes', function(request, response) {
|
||||
/*
|
||||
Эта функция создает новую заметку
|
||||
request.body - содержит данные, отправленные клиентом
|
||||
*/
|
||||
console.log('Получен запрос на создание заметки');
|
||||
app.get('/notes/:id', async (req, res) => {
|
||||
try {
|
||||
const note = await db.get('SELECT * FROM notes WHERE id = ?', req.params.id);
|
||||
|
||||
if (!note) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: 'Заметка с таким ID не найдена'
|
||||
});
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: note
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Ошибка при получении заметки'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/notes', async (req, res) => {
|
||||
const { title, text, author } = req.body;
|
||||
|
||||
// Получаем данные из тела запроса
|
||||
const title = request.body.title;
|
||||
const text = request.body.text;
|
||||
const author = request.body.author;
|
||||
|
||||
// Проверяем, все ли обязательные поля заполнены
|
||||
if (!title || !text || !author) {
|
||||
console.log('Ошибка: не все поля заполнены');
|
||||
return response.status(400).json({
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Пожалуйста, заполните все поля: title, text, author'
|
||||
});
|
||||
}
|
||||
|
||||
// Создаем новую заметку
|
||||
const newNote = {
|
||||
id: noteIdCounter, // присваиваем уникальный ID
|
||||
title: title, // заголовок из запроса
|
||||
text: text, // текст заметки из запроса
|
||||
author: author, // автор из запроса
|
||||
createdAt: new Date(), // дата создания (текущее время)
|
||||
updatedAt: new Date() // дата обновления (пока равна дате создания)
|
||||
};
|
||||
|
||||
// Добавляем заметку в массив
|
||||
notes.push(newNote);
|
||||
// Увеличиваем счетчик ID для следующей заметки
|
||||
noteIdCounter++;
|
||||
|
||||
console.log('Создана новая заметка с ID:', newNote.id);
|
||||
|
||||
// Отправляем ответ с созданной заметкой
|
||||
// status(201) - код 201 означает "Создано"
|
||||
response.status(201).json({
|
||||
success: true,
|
||||
message: 'Заметка успешно создана',
|
||||
data: newNote
|
||||
});
|
||||
});
|
||||
|
||||
// РОУТ 4: Обновление существующей заметки (UPDATE)
|
||||
// Метод: PUT
|
||||
// Адрес: /notes/:id
|
||||
app.put('/notes/:id', function(request, response) {
|
||||
/*
|
||||
Эта функция обновляет существующую заметку
|
||||
request.params.id - ID заметки для обновления
|
||||
request.body - новые данные для заметки
|
||||
*/
|
||||
console.log('Получен запрос на обновление заметки с ID:', request.params.id);
|
||||
|
||||
const noteId = parseInt(request.params.id);
|
||||
|
||||
// Ищем индекс заметки в массиве
|
||||
const noteIndex = notes.findIndex(function(note) {
|
||||
return note.id === noteId;
|
||||
});
|
||||
|
||||
// Если заметка не найдена
|
||||
if (noteIndex === -1) {
|
||||
console.log('Заметка для обновления не найдена');
|
||||
return response.status(404).json({
|
||||
try {
|
||||
const result = await db.run(
|
||||
'INSERT INTO notes (title, text, author) VALUES (?, ?, ?)',
|
||||
[title, text, author]
|
||||
);
|
||||
|
||||
const newNote = await db.get('SELECT * FROM notes WHERE id = ?', result.lastID);
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
message: 'Заметка успешно создана',
|
||||
data: newNote
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Заметка с таким ID не найдена'
|
||||
message: 'Ошибка при создании заметки'
|
||||
});
|
||||
}
|
||||
|
||||
// Получаем данные из запроса
|
||||
// Если какое-то поле не передано, используем старое значение
|
||||
const updatedTitle = request.body.title || notes[noteIndex].title;
|
||||
const updatedText = request.body.text || notes[noteIndex].text;
|
||||
const updatedAuthor = request.body.author || notes[noteIndex].author;
|
||||
|
||||
// Обновляем заметку
|
||||
notes[noteIndex] = {
|
||||
...notes[noteIndex], // копируем все старые поля
|
||||
title: updatedTitle, // обновляем заголовок
|
||||
text: updatedText, // обновляем текст
|
||||
author: updatedAuthor, // обновляем автора
|
||||
updatedAt: new Date() // обновляем дату изменения
|
||||
};
|
||||
|
||||
console.log('Заметка с ID', noteId, 'обновлена');
|
||||
|
||||
// Отправляем обновленную заметку
|
||||
response.status(200).json({
|
||||
success: true,
|
||||
message: 'Заметка успешно обновлена',
|
||||
data: notes[noteIndex]
|
||||
});
|
||||
});
|
||||
|
||||
// РОУТ 5: Удаление заметки (DELETE)
|
||||
// Метод: DELETE
|
||||
// Адрес: /notes/:id
|
||||
app.delete('/notes/:id', function(request, response) {
|
||||
/*
|
||||
Эта функция удаляет заметку по ID
|
||||
*/
|
||||
console.log('Получен запрос на удаление заметки с ID:', request.params.id);
|
||||
|
||||
const noteId = parseInt(request.params.id);
|
||||
|
||||
// Ищем индекс заметки в массиве
|
||||
const noteIndex = notes.findIndex(function(note) {
|
||||
return note.id === noteId;
|
||||
});
|
||||
|
||||
// Если заметка не найдена
|
||||
if (noteIndex === -1) {
|
||||
console.log('Заметка для удаления не найдена');
|
||||
return response.status(404).json({
|
||||
app.put('/notes/:id', async (req, res) => {
|
||||
try {
|
||||
const existingNote = await db.get('SELECT * FROM notes WHERE id = ?', req.params.id);
|
||||
|
||||
if (!existingNote) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: 'Заметка с таким ID не найдена'
|
||||
});
|
||||
}
|
||||
|
||||
const updatedTitle = req.body.title || existingNote.title;
|
||||
const updatedText = req.body.text || existingNote.text;
|
||||
const updatedAuthor = req.body.author || existingNote.author;
|
||||
|
||||
await db.run(
|
||||
'UPDATE notes SET title = ?, text = ?, author = ?, updatedAt = CURRENT_TIMESTAMP WHERE id = ?',
|
||||
[updatedTitle, updatedText, updatedAuthor, req.params.id]
|
||||
);
|
||||
|
||||
const updatedNote = await db.get('SELECT * FROM notes WHERE id = ?', req.params.id);
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
message: 'Заметка успешно обновлена',
|
||||
data: updatedNote
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Заметка с таким ID не найдена'
|
||||
message: 'Ошибка при обновлении заметки'
|
||||
});
|
||||
}
|
||||
|
||||
// Удаляем заметку из массива
|
||||
// splice(index, 1) удаляет 1 элемент начиная с позиции index
|
||||
const deletedNote = notes.splice(noteIndex, 1)[0];
|
||||
|
||||
console.log('Заметка с ID', noteId, 'удалена');
|
||||
|
||||
// Отправляем подтверждение удаления
|
||||
response.status(200).json({
|
||||
success: true,
|
||||
message: 'Заметка успешно удалена',
|
||||
data: deletedNote
|
||||
});
|
||||
});
|
||||
|
||||
// РОУТ 7: Корневой маршрут (для проверки работы сервера)
|
||||
// Метод: GET
|
||||
// Адрес: /
|
||||
app.get('/', function(_, response) {
|
||||
/*
|
||||
Простой маршрут для проверки, что сервер работает
|
||||
*/
|
||||
response.json({
|
||||
app.delete('/notes/:id', async (req, res) => {
|
||||
try {
|
||||
const existingNote = await db.get('SELECT * FROM notes WHERE id = ?', req.params.id);
|
||||
|
||||
if (!existingNote) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: 'Заметка с таким ID не найдена'
|
||||
});
|
||||
}
|
||||
|
||||
await db.run('DELETE FROM notes WHERE id = ?', req.params.id);
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
message: 'Заметка успешно удалена',
|
||||
data: existingNote
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Ошибка при удалении заметки'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/', (_, res) => {
|
||||
res.json({
|
||||
message: 'Добро пожаловать в API для заметок!',
|
||||
endpoints: {
|
||||
getAllNotes: 'GET /notes',
|
||||
getOneNote: 'GET /notes/:id',
|
||||
createNote: 'POST /notes',
|
||||
updateNote: 'PUT /notes/:id',
|
||||
deleteNote: 'DELETE /notes/:id',
|
||||
docs: 'GET /docs'
|
||||
},
|
||||
instructions: 'Используйте Postman или curl для тестирования API'
|
||||
deleteNote: 'DELETE /notes/:id'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Экспортируем приложение для использования в server.js
|
||||
export default app;
|
||||
|
||||
Reference in New Issue
Block a user