From 0164c5fb1a58f76117b21ba1d569431ba15542c7 Mon Sep 17 00:00:00 2001 From: Egor Deev <67710823+IGlek@users.noreply.github.com> Date: Fri, 23 May 2025 15:44:05 +0300 Subject: [PATCH 1/2] Table Users --- server/func2serv.cpp | 142 +++++++++++++++++++++++++++++------------ server/mytcpserver.cpp | 59 +++++++++++++++-- 2 files changed, 155 insertions(+), 46 deletions(-) diff --git a/server/func2serv.cpp b/server/func2serv.cpp index c367c10..c2c676f 100644 --- a/server/func2serv.cpp +++ b/server/func2serv.cpp @@ -15,64 +15,90 @@ QMap> mockDatabase = { }; using namespace std; +// КРИТИЧЕСКИ ВАЖНОЕ исправление в func2serv.cpp +// Полная замена функции parsing с корректной обработкой admin команд: + QByteArray parsing(QString input, int socdes) { - - QStringList container = input.remove("\r\n").split("//"); //пример входящих данных reg//login_user//password_user + // Архитектурное решение: нормализация входных данных + QStringList container = input.remove("\r\n").split("//"); if (container.isEmpty()) { - return "server error: empty command\\n"; + qDebug() << "ERROR: Empty command received"; + return "server error: empty command\r\n"; } + // Диагностика для отладки маршрутизации + qDebug() << "=== PARSING REQUEST ==="; + qDebug() << "Socket:" << socdes; + qDebug() << "Raw input:" << input; + qDebug() << "Parsed command:" << container; + qDebug() << "Command[0]:" << (container.size() > 0 ? container[0] : "NONE"); + qDebug() << "Command[1]:" << (container.size() > 1 ? container[1] : "NONE"); - qDebug() << socdes << " user command: " << container[0]; QString var = container[0]; - if (var == "check_task") - { + + // КРИТИЧЕСКОЕ ИСПРАВЛЕНИЕ: правильная обработка admin команд + if (var == "admin") { + qDebug() << "Admin command detected"; + + if (container.size() < 2) { + qDebug() << "ERROR: Admin command without subcommand"; + return "server error: admin command requires subcommand\r\n"; + } + + QString adminCmd = container[1]; + qDebug() << "Admin subcommand:" << adminCmd; + + if (adminCmd == "get_all_users") { + qDebug() << "Executing get_all_users..."; + QByteArray result = get_all_users(); + qDebug() << "get_all_users returned:" << result.size() << "bytes"; + return result; + } + else if (adminCmd == "dynamic_stat") { + return get_dynamic_stat(); + } + else if (adminCmd == "stable_stat") { + return get_stable_stat(); + } + else { + qDebug() << "Unknown admin command:" << adminCmd; + return "server error: unknown admin command\r\n"; + } + } + else if (var == "check_task") { return check_task(); } - else if (var =="auth") - { + else if (var == "auth") { return auth(container); } - else if (var == "add_product") - { + else if (var == "add_product") { return add_product(container); } - else if (var == "user" && container[2] == "get_products") { - return get_products(container[1]); + else if (var == "user" && container.size() >= 3) { + if (container[2] == "get_products") { + return get_products(container[1]); + } + else if (container[2] == "add_favorite_ration") { + return add_favorite_ration(container); + } } - else if (var =="reg") - { + else if (var == "reg") { return reg(container); } - else if (var == "get_stat") - { - return(get_stat()); - } - else if (var == "admin" && container[1] == "dynamic_stat") { - return get_dynamic_stat(); + else if (var == "get_stat") { + return get_stat(); } - else if (var == "menu_export") - { + else if (var == "menu_export") { return menu_export(); } - else if (var == "user" && container[2] == "add_favorite_ration") { - return add_favorite_ration(container); - } - else if (var == "admin" && container[1] == "get_all_users") { - return get_all_users(); - } - else if (var == "admin" && container[1] == "stable_stat") { - return get_stable_stat(); - } - else - { - return "server error: unknow command\r\n"; + else { + qDebug() << "ERROR: Unknown command:" << var; + return "server error: unknown command\r\n"; } } - // дарова Руслан, когда будешь писать тут функцию эту, при успешной авторизации просто пропиши return "true", при неуспешной return "false", на клиенте я так принимаю ответ QByteArray auth(QStringList log) { // Проверяем количество параметров @@ -233,17 +259,51 @@ QByteArray get_products(QString userId) { } QByteArray get_all_users() { - QStringList users; + qDebug() << "\n=== EXECUTING GET_ALL_USERS ==="; + + // Техническое решение: early return паттерн для минимизации задержек + DataBaseSingleton* db = DataBaseSingleton::getInstance(); + if (!db) { + qDebug() << "CRITICAL: Database not initialized"; + return "[]"; + } - // fetch_users_from_db(users); + // Архитектурное решение: единственный SQL запрос без дополнительных проверок + QSqlQuery query = db->executeQuery( + "SELECT id, name, email, is_admin FROM users ORDER BY id" + ); - QString response; - for (const QString& user : users) { - response += user + "\r\n"; + // Быстрая проверка на ошибки SQL + if (query.lastError().isValid()) { + qDebug() << "SQL Error:" << query.lastError().text(); + return "[]"; } - return response.toUtf8(); + // Оптимизация: резервирование памяти для JSON + QJsonArray usersArray; + + while (query.next()) { + QJsonObject userObj; + userObj["id"] = query.value(0).toInt(); + userObj["name"] = query.value(1).toString(); + userObj["email"] = query.value(2).toString(); + userObj["is_admin"] = query.value(3).toBool(); + + usersArray.append(userObj); + } + + qDebug() << "Users found:" << usersArray.size(); + + // Критически важно: компактная сериализация для минимизации размера + QJsonDocument doc(usersArray); + QByteArray result = doc.toJson(QJsonDocument::Compact); + + qDebug() << "JSON generated, size:" << result.size(); + qDebug() << "=== GET_ALL_USERS COMPLETE ===\n"; + + return result; } + int get_user_count() { // Здесь будет SQL-запрос, пока заглушка return 152; // Примерное значение diff --git a/server/mytcpserver.cpp b/server/mytcpserver.cpp index 463ae2c..cb74f94 100644 --- a/server/mytcpserver.cpp +++ b/server/mytcpserver.cpp @@ -48,14 +48,63 @@ void MyTcpServer::slotNewConnection() void MyTcpServer::slotServerRead() { QTcpSocket *clientSocket = qobject_cast(sender()); - if (!clientSocket) return; + if (!clientSocket) { + qDebug() << "CRITICAL ERROR: Invalid socket in slotServerRead"; + return; + } QByteArray data = clientSocket->readAll(); - qDebug() << "Received data:" << data; - // Обрабатываем данные сразу, без накопления - QByteArray response = parsing(QString(data).trimmed(), clientSocket->socketDescriptor()); - clientSocket->write(response); + // Архитектурное решение: детальное логирование для диагностики + qDebug() << "\n=== SERVER REQUEST PROCESSING ==="; + qDebug() << "Socket descriptor:" << clientSocket->socketDescriptor(); + qDebug() << "Received bytes:" << data.size(); + qDebug() << "Raw data:" << data; + qDebug() << "As string:" << QString(data); + + // Техническое решение: валидация данных перед обработкой + if (data.isEmpty()) { + qDebug() << "WARNING: Empty data received"; + return; + } + + // Обработка запроса + QString trimmedInput = QString(data).trimmed(); + qDebug() << "Processing command:" << trimmedInput; + + QByteArray response = parsing(trimmedInput, clientSocket->socketDescriptor()); + + qDebug() << "Response size:" << response.size(); + qDebug() << "Response preview:" << response.left(100); + + // КРИТИЧЕСКИ ВАЖНО: правильная отправка данных + if (!response.isEmpty()) { + qint64 bytesWritten = clientSocket->write(response); + qDebug() << "Bytes written:" << bytesWritten; + + // Архитектурное решение: гарантированная отправка данных + if (bytesWritten > 0) { + // КРИТИЧЕСКИ ВАЖНО: форсируем отправку + clientSocket->flush(); + qDebug() << "Data flushed successfully"; + + // Дополнительная проверка состояния сокета + if (clientSocket->state() == QTcpSocket::ConnectedState) { + qDebug() << "Socket still connected after write"; + } else { + qDebug() << "WARNING: Socket state changed:" << clientSocket->state(); + } + } else { + qDebug() << "ERROR: Failed to write response"; + } + } else { + qDebug() << "WARNING: Empty response from parsing"; + // Отправляем пустой JSON массив как fallback + clientSocket->write("[]"); + clientSocket->flush(); + } + + qDebug() << "=== SERVER REQUEST COMPLETE ===\n"; } void MyTcpServer::slotClientDisconnected() From d807d5b2d9113e88abceb07411d254b80daa4cb8 Mon Sep 17 00:00:00 2001 From: IGlek Date: Sat, 24 May 2025 17:14:34 +0300 Subject: [PATCH 2/2] Refactor Server 1.0 --- server/Easyweek.db | Bin 28672 -> 28672 bytes server/{ => database}/databasesingleton.cpp | 376 +++++++++----------- server/database/databasesingleton.h | 140 ++++++++ server/database/migrations.cpp | 81 +++++ server/database/migrations.h | 37 ++ server/databasesingleton.h | 62 ---- server/echoServer.pro | 22 +- server/func2serv.cpp | 376 -------------------- server/func2serv.h | 27 -- server/main.cpp | 13 +- server/mytcpserver.h | 27 -- server/{ => network}/mytcpserver.cpp | 269 +++++++------- server/network/mytcpserver.h | 53 +++ server/network/requesthandler.cpp | 98 +++++ server/network/requesthandler.h | 48 +++ server/services/authservice.cpp | 144 ++++++++ server/services/authservice.h | 42 +++ server/services/productservice.cpp | 55 +++ server/services/productservice.h | 36 ++ server/services/rationservice.cpp | 30 ++ server/services/rationservice.h | 38 ++ server/services/statsservice.cpp | 66 ++++ server/services/statsservice.h | 64 ++++ 23 files changed, 1253 insertions(+), 851 deletions(-) rename server/{ => database}/databasesingleton.cpp (73%) create mode 100644 server/database/databasesingleton.h create mode 100644 server/database/migrations.cpp create mode 100644 server/database/migrations.h delete mode 100644 server/databasesingleton.h delete mode 100644 server/func2serv.cpp delete mode 100644 server/func2serv.h delete mode 100644 server/mytcpserver.h rename server/{ => network}/mytcpserver.cpp (90%) create mode 100644 server/network/mytcpserver.h create mode 100644 server/network/requesthandler.cpp create mode 100644 server/network/requesthandler.h create mode 100644 server/services/authservice.cpp create mode 100644 server/services/authservice.h create mode 100644 server/services/productservice.cpp create mode 100644 server/services/productservice.h create mode 100644 server/services/rationservice.cpp create mode 100644 server/services/rationservice.h create mode 100644 server/services/statsservice.cpp create mode 100644 server/services/statsservice.h diff --git a/server/Easyweek.db b/server/Easyweek.db index 1a46a3d7dbd3bc03f495c21d200f9597162031f9..82d60a64dd7b9c53ae9a4621f9f6c5d00006a12d 100644 GIT binary patch delta 498 zcmZ8dIYv)i92fsb1}6S14E$I4Ujmh%;GcZO-hf$_M`|*=gY{-Xfg9YL zrF@{~iAe{zv?Gz{*uv*jP9?I9OQ0Bny~iX5j>iFo8)%5D5Ti ClqhKc diff --git a/server/databasesingleton.cpp b/server/database/databasesingleton.cpp similarity index 73% rename from server/databasesingleton.cpp rename to server/database/databasesingleton.cpp index d7e405a..b9bb60e 100644 --- a/server/databasesingleton.cpp +++ b/server/database/databasesingleton.cpp @@ -1,212 +1,164 @@ -#include "databasesingleton.h" - -DataBaseSingleton* DataBaseSingleton::p_instance = nullptr; -SingletonDestroyer DataBaseSingleton::destroyer; - -DataBaseSingleton::DataBaseSingleton() { - db = QSqlDatabase::addDatabase("QSQLITE"); -} - -DataBaseSingleton* DataBaseSingleton::getInstance() { - if (!p_instance) { - p_instance = new DataBaseSingleton(); - destroyer.initialize(p_instance); - } - return p_instance; -} - -bool DataBaseSingleton::initialize(const QString& databaseName) { - db.setDatabaseName(databaseName); - if (!db.open()) { - qDebug() << "Ошибка подключения:" << db.lastError().text(); - return false; - } - - // Создание таблицы users - QSqlQuery query(db); - bool success = query.exec( - "CREATE TABLE IF NOT EXISTS users (" - "id INTEGER PRIMARY KEY AUTOINCREMENT, " - "name VARCHAR(20) NOT NULL, " - "email VARCHAR(50) NOT NULL , " - "pass VARCHAR(20) NOT NULL, " - "is_admin BOOLEAN DEFAULT FALSE)" - ); - - success &= query.exec("CREATE UNIQUE INDEX IF NOT EXISTS idx_users_email ON users(email)"); - - // Создание таблицы products - success &= query.exec( - "CREATE TABLE IF NOT EXISTS products (" - "id INTEGER PRIMARY KEY AUTOINCREMENT, " - "id_user INTEGER NOT NULL, " - "name VARCHAR(50) NOT NULL, " - "proteins INTEGER NOT NULL, " - "fatness INTEGER NOT NULL, " - "carbs INTEGER NOT NULL, " - "weight INTEGER NOT NULL, " - "cost INTEGER NOT NULL, " - "type INTEGER NOT NULL, " - "FOREIGN KEY(id_user) REFERENCES users(id))" - ); - - // Создание таблицы favorites - success &= query.exec( - "CREATE TABLE IF NOT EXISTS favorites (" - "id_user INTEGER NOT NULL, " - "products TEXT NOT NULL, " // Хранение массива ID продуктов в виде строки - "calories INTEGER NOT NULL, " - "all_cost INTEGER NOT NULL, " - "all_weight INTEGER NOT NULL, " - "FOREIGN KEY(id_user) REFERENCES users(id))" - ); - - // Создание таблицы statistics - success &= query.exec( - "CREATE TABLE IF NOT EXISTS statistics (" - "count_registrations INTEGER DEFAULT 0, " - "count_visits INTEGER DEFAULT 0, " - "count_generations INTEGER DEFAULT 0)" - ); - - // Инициализация статистики, если таблица пуста - query.exec("INSERT OR IGNORE INTO statistics (count_registrations, count_visits, count_generations) VALUES (0, 0, 0)"); - query.exec("INSERT OR IGNORE INTO users(name, email, pass, is_admin) VALUES ('NewDev','admin@new-devs.ru','admin',true)"); - return success; -} - -QSqlQuery DataBaseSingleton::executeQuery(const QString& queryStr, const QVariantMap& params) { - QSqlQuery query(db); - query.prepare(queryStr); - for (auto it = params.begin(); it != params.end(); ++it) { - query.bindValue(it.key(), it.value()); - } - if (!query.exec()) { - qDebug() << "Ошибка запроса:" << query.lastError().text(); - qDebug() << "Текст запроса:" << queryStr; - } - return query; -} - -// Методы для работы с таблицей users -bool DataBaseSingleton::checkUserCredentials(const QString& email, const QString& password) { - QSqlQuery query = executeQuery( - "SELECT * FROM users WHERE email = :email AND pass = :pass", - {{":email", email}, {":pass", password}} - ); - return query.next(); -} - -bool DataBaseSingleton::addUser(const QString& name, const QString& email, const QString& password, bool isAdmin) { - QSqlQuery query = executeQuery( - "INSERT INTO users (name, email, pass, is_admin) VALUES (:name, :email, :pass, :is_admin)", - {{":name", name}, {":email", email}, {":pass", password}, {":is_admin", isAdmin}} - ); - - if (query.lastError().isValid()) { - qDebug() << "Ошибка SQL:" << query.lastError().text(); - return false; - } - return true; -} - -// Методы для работы с таблицей products -bool DataBaseSingleton::addProduct(int userId, const QString& name, int proteins, int fatness, int carbs, int weight, int cost, int type) { - QSqlQuery query = executeQuery( - "INSERT INTO products (id_user, name, proteins, fatness, carbs, weight, cost, type) " - "VALUES (:id_user, :name, :proteins, :fatness, :carbs, :weight, :cost, :type)", - { - {":id_user", userId}, - {":name", name}, - {":proteins", proteins}, - {":fatness", fatness}, - {":carbs", carbs}, - {":weight", weight}, - {":cost", cost}, - {":type", type} - } - ); - return !query.lastError().isValid(); // Возвращаем true, если ошибок нет -} - -QVector DataBaseSingleton::getProductsByUser(int userId) { - QSqlQuery query = executeQuery( - "SELECT * FROM products WHERE id_user = :id_user", - {{":id_user", userId}} - ); - QVector products; - while (query.next()) { - QVariantMap product; - product["id"] = query.value("id").toInt(); - product["name"] = query.value("name").toString(); - product["proteins"] = query.value("proteins").toInt(); - product["fatness"] = query.value("fatness").toInt(); - product["carbs"] = query.value("carbs").toInt(); - product["weight"] = query.value("weight").toInt(); - product["cost"] = query.value("cost").toInt(); - product["type"] = query.value("type").toInt(); - products.append(product); - } - return products; -} - -// Методы для работы с таблицей favorites -bool DataBaseSingleton::addFavoriteRation(int userId, const QVector& productIds, int calories, int allCost, int allWeight) { - // Преобразуем QVector в QVector - QVector productIdsStr; - for (int id : productIds) { - productIdsStr.append(QString::number(id)); // Преобразуем int в QString - } - - // Преобразуем QVector в QStringList и объединяем в строку через запятую - QString productsStr = QStringList::fromVector(productIdsStr).join(","); - - // Выполняем SQL-запрос - return executeQuery( - "INSERT INTO favorites (id_user, products, calories, all_cost, all_weight) " - "VALUES (:id_user, :products, :calories, :all_cost, :all_weight)", - {{":id_user", userId}, {":products", productsStr}, {":calories", calories}, - {":all_cost", allCost}, {":all_weight", allWeight}} - ).exec(); -} - -QVector DataBaseSingleton::getFavoritesByUser(int userId) { - QSqlQuery query = executeQuery( - "SELECT * FROM favorites WHERE id_user = :id_user", - {{":id_user", userId}} - ); - QVector favorites; - while (query.next()) { - QVariantMap favorite; - favorite["products"] = query.value("products").toString(); - favorite["calories"] = query.value("calories").toInt(); - favorite["all_cost"] = query.value("all_cost").toInt(); - favorite["all_weight"] = query.value("all_weight").toInt(); - favorites.append(favorite); - } - return favorites; -} - -// Методы для работы с таблицей statistics -bool DataBaseSingleton::updateStatistics(int registrations, int visits, int generations) { - return executeQuery( - "UPDATE statistics SET count_registrations = :registrations, " - "count_visits = :visits, count_generations = :generations", - {{":registrations", registrations}, {":visits", visits}, {":generations", generations}} - ).exec(); -} - -QVariantMap DataBaseSingleton::getStatistics() { - QSqlQuery query = executeQuery("SELECT * FROM statistics"); - QVariantMap stats; - if (query.next()) { - stats["registrations"] = query.value("count_registrations").toInt(); - stats["visits"] = query.value("count_visits").toInt(); - stats["generations"] = query.value("count_generations").toInt(); - } - return stats; -} - -// Реализация разрушителя -SingletonDestroyer::~SingletonDestroyer() { delete p_instance; } -void SingletonDestroyer::initialize(DataBaseSingleton* p) { p_instance = p; } +#include "databasesingleton.h" +#include "migrations.h" + +DataBaseSingleton* DataBaseSingleton::p_instance = nullptr; +SingletonDestroyer DataBaseSingleton::destroyer; + +DataBaseSingleton::DataBaseSingleton() { + db = QSqlDatabase::addDatabase("QSQLITE"); +} + +DataBaseSingleton* DataBaseSingleton::getInstance() { + if (!p_instance) { + p_instance = new DataBaseSingleton(); + destroyer.initialize(p_instance); + } + return p_instance; +} + +bool DataBaseSingleton::initialize(const QString& databaseName) { + db.setDatabaseName(databaseName); + if (!db.open()) { + qDebug() << "Ошибка подключения:" << db.lastError().text(); + return false; + } + + // Выполнение миграций для создания необходимых таблиц + return Migrations::runMigrations(db); +} + +QSqlQuery DataBaseSingleton::executeQuery(const QString& queryStr, const QVariantMap& params) { + QSqlQuery query(db); + query.prepare(queryStr); + for (auto it = params.begin(); it != params.end(); ++it) { + query.bindValue(it.key(), it.value()); + } + if (!query.exec()) { + qDebug() << "Ошибка запроса:" << query.lastError().text(); + qDebug() << "Текст запроса:" << queryStr; + } + return query; +} + +// Методы для работы с таблицей users +bool DataBaseSingleton::checkUserCredentials(const QString& email, const QString& password) { + QSqlQuery query = executeQuery( + "SELECT * FROM users WHERE email = :email AND pass = :pass", + {{":email", email}, {":pass", password}} + ); + return query.next(); +} + +bool DataBaseSingleton::addUser(const QString& name, const QString& email, const QString& password, bool isAdmin) { + QSqlQuery query = executeQuery( + "INSERT INTO users (name, email, pass, is_admin) VALUES (:name, :email, :pass, :is_admin)", + {{":name", name}, {":email", email}, {":pass", password}, {":is_admin", isAdmin}} + ); + + if (query.lastError().isValid()) { + qDebug() << "Ошибка SQL:" << query.lastError().text(); + return false; + } + return true; +} + +// Методы для работы с таблицей products +bool DataBaseSingleton::addProduct(int userId, const QString& name, int proteins, int fatness, int carbs, int weight, int cost, int type) { + QSqlQuery query = executeQuery( + "INSERT INTO products (id_user, name, proteins, fatness, carbs, weight, cost, type) " + "VALUES (:id_user, :name, :proteins, :fatness, :carbs, :weight, :cost, :type)", + { + {":id_user", userId}, + {":name", name}, + {":proteins", proteins}, + {":fatness", fatness}, + {":carbs", carbs}, + {":weight", weight}, + {":cost", cost}, + {":type", type} + } + ); + return !query.lastError().isValid(); // Возвращаем true, если ошибок нет +} + +QVector DataBaseSingleton::getProductsByUser(int userId) { + QSqlQuery query = executeQuery( + "SELECT * FROM products WHERE id_user = :id_user", + {{":id_user", userId}} + ); + QVector products; + while (query.next()) { + QVariantMap product; + product["id"] = query.value("id").toInt(); + product["name"] = query.value("name").toString(); + product["proteins"] = query.value("proteins").toInt(); + product["fatness"] = query.value("fatness").toInt(); + product["carbs"] = query.value("carbs").toInt(); + product["weight"] = query.value("weight").toInt(); + product["cost"] = query.value("cost").toInt(); + product["type"] = query.value("type").toInt(); + products.append(product); + } + return products; +} + +// Методы для работы с таблицей favorites +bool DataBaseSingleton::addFavoriteRation(int userId, const QVector& productIds, int calories, int allCost, int allWeight) { + // Преобразуем QVector в QVector + QVector productIdsStr; + for (int id : productIds) { + productIdsStr.append(QString::number(id)); // Преобразуем int в QString + } + + // Преобразуем QVector в QStringList и объединяем в строку через запятую + QString productsStr = QStringList::fromVector(productIdsStr).join(","); + + // Выполняем SQL-запрос + return executeQuery( + "INSERT INTO favorites (id_user, products, calories, all_cost, all_weight) " + "VALUES (:id_user, :products, :calories, :all_cost, :all_weight)", + {{":id_user", userId}, {":products", productsStr}, {":calories", calories}, + {":all_cost", allCost}, {":all_weight", allWeight}} + ).exec(); +} + +QVector DataBaseSingleton::getFavoritesByUser(int userId) { + QSqlQuery query = executeQuery( + "SELECT * FROM favorites WHERE id_user = :id_user", + {{":id_user", userId}} + ); + QVector favorites; + while (query.next()) { + QVariantMap favorite; + favorite["products"] = query.value("products").toString(); + favorite["calories"] = query.value("calories").toInt(); + favorite["all_cost"] = query.value("all_cost").toInt(); + favorite["all_weight"] = query.value("all_weight").toInt(); + favorites.append(favorite); + } + return favorites; +} + +// Методы для работы с таблицей statistics +bool DataBaseSingleton::updateStatistics(int registrations, int visits, int generations) { + return executeQuery( + "UPDATE statistics SET count_registrations = :registrations, " + "count_visits = :visits, count_generations = :generations", + {{":registrations", registrations}, {":visits", visits}, {":generations", generations}} + ).exec(); +} + +QVariantMap DataBaseSingleton::getStatistics() { + QSqlQuery query = executeQuery("SELECT * FROM statistics"); + QVariantMap stats; + if (query.next()) { + stats["registrations"] = query.value("count_registrations").toInt(); + stats["visits"] = query.value("count_visits").toInt(); + stats["generations"] = query.value("count_generations").toInt(); + } + return stats; +} + +// Реализация разрушителя +SingletonDestroyer::~SingletonDestroyer() { delete p_instance; } +void SingletonDestroyer::initialize(DataBaseSingleton* p) { p_instance = p; } \ No newline at end of file diff --git a/server/database/databasesingleton.h b/server/database/databasesingleton.h new file mode 100644 index 0000000..8b3a2af --- /dev/null +++ b/server/database/databasesingleton.h @@ -0,0 +1,140 @@ +#ifndef DATABASESINGLETON_H +#define DATABASESINGLETON_H + +#include +#include +#include +#include +#include +#include + +class DataBaseSingleton; + +/** + * @brief Класс-разрушитель для корректного удаления Singleton + */ +class SingletonDestroyer { +private: + DataBaseSingleton* p_instance; ///< Указатель на экземпляр Singleton +public: + ~SingletonDestroyer(); ///< Деструктор для удаления Singleton + void initialize(DataBaseSingleton* p); ///< Инициализация указателя +}; + +/** + * @brief Основной класс для работы с базой данных + * + * Реализует паттерн Singleton для централизованного доступа к БД + */ +class DataBaseSingleton { +private: + static DataBaseSingleton* p_instance; ///< Единственный экземпляр класса + static SingletonDestroyer destroyer; ///< Объект-разрушитель + QSqlDatabase db; ///< Объект базы данных + + DataBaseSingleton(); ///< Приватный конструктор + DataBaseSingleton(const DataBaseSingleton&) = delete; ///< Запрет копирования + DataBaseSingleton& operator=(const DataBaseSingleton&) = delete; + ~DataBaseSingleton() = default; ///< Приватный деструктор + friend class SingletonDestroyer; ///< Дружественный класс для доступа к деструктору + +public: + /** + * @brief Получение экземпляра Singleton + * @return Указатель на экземпляр DataBaseSingleton + */ + static DataBaseSingleton* getInstance(); + + /** + * @brief Инициализация БД + * @param databaseName Имя файла базы данных + * @return true в случае успеха, false при ошибке + */ + bool initialize(const QString& databaseName); + + /** + * @brief Выполнение параметризованного SQL-запроса + * @param query SQL-запрос + * @param params Параметры запроса + * @return Объект QSqlQuery с результатом запроса + */ + QSqlQuery executeQuery(const QString& query, const QVariantMap& params = QVariantMap()); + + // Методы для работы с таблицей users + /** + * @brief Проверка учетных данных пользователя + * @param email Email пользователя + * @param password Пароль пользователя + * @return true если данные верны, false в противном случае + */ + bool checkUserCredentials(const QString& email, const QString& password); + + /** + * @brief Добавление нового пользователя + * @param name Имя пользователя + * @param email Email пользователя + * @param password Пароль пользователя + * @param isAdmin Флаг администратора + * @return true в случае успеха, false при ошибке + */ + bool addUser(const QString& name, const QString& email, const QString& password, bool isAdmin = false); + + // Методы для работы с таблицей products + /** + * @brief Добавление нового продукта + * @param userId ID пользователя + * @param name Название продукта + * @param proteins Содержание белков + * @param fatness Содержание жиров + * @param carbs Содержание углеводов + * @param weight Вес порции + * @param cost Стоимость + * @param type Тип продукта + * @return true в случае успеха, false при ошибке + */ + bool addProduct(int userId, const QString& name, int proteins, int fatness, int carbs, int weight, int cost, int type); + + /** + * @brief Получение списка продуктов пользователя + * @param userId ID пользователя + * @return Вектор с данными продуктов + */ + QVector getProductsByUser(int userId); + + // Методы для работы с таблицей favorites + /** + * @brief Добавление избранного рациона + * @param userId ID пользователя + * @param productIds Список ID продуктов + * @param calories Калорийность + * @param allCost Общая стоимость + * @param allWeight Общий вес + * @return true в случае успеха, false при ошибке + */ + bool addFavoriteRation(int userId, const QVector& productIds, int calories, int allCost, int allWeight); + + /** + * @brief Получение списка избранных рационов пользователя + * @param userId ID пользователя + * @return Вектор с данными рационов + */ + QVector getFavoritesByUser(int userId); + + // Методы для работы с таблицей statistics + /** + * @brief Обновление статистики + * @param registrations Количество регистраций + * @param visits Количество посещений + * @param generations Количество генераций + * @return true в случае успеха, false при ошибке + */ + bool updateStatistics(int registrations, int visits, int generations); + + /** + * @brief Получение статистики + * @return Объект с данными статистики + */ + QVariantMap getStatistics(); +}; + +#endif // DATABASESINGLETON_H \ No newline at end of file diff --git a/server/database/migrations.cpp b/server/database/migrations.cpp new file mode 100644 index 0000000..5fb04de --- /dev/null +++ b/server/database/migrations.cpp @@ -0,0 +1,81 @@ +#include "migrations.h" + +bool Migrations::runMigrations(QSqlDatabase& db) { + if (!createTables(db)) { + qDebug() << "Ошибка создания таблиц"; + return false; + } + + if (!seedInitialData(db)) { + qDebug() << "Ошибка инициализации данных"; + return false; + } + + return true; +} + +bool Migrations::createTables(QSqlDatabase& db) { + QSqlQuery query(db); + bool success = true; + + // Создание таблицы users + success &= query.exec( + "CREATE TABLE IF NOT EXISTS users (" + "id INTEGER PRIMARY KEY AUTOINCREMENT, " + "name VARCHAR(20) NOT NULL, " + "email VARCHAR(50) NOT NULL , " + "pass VARCHAR(20) NOT NULL, " + "is_admin BOOLEAN DEFAULT FALSE)" + ); + + success &= query.exec("CREATE UNIQUE INDEX IF NOT EXISTS idx_users_email ON users(email)"); + + // Создание таблицы products + success &= query.exec( + "CREATE TABLE IF NOT EXISTS products (" + "id INTEGER PRIMARY KEY AUTOINCREMENT, " + "id_user INTEGER NOT NULL, " + "name VARCHAR(50) NOT NULL, " + "proteins INTEGER NOT NULL, " + "fatness INTEGER NOT NULL, " + "carbs INTEGER NOT NULL, " + "weight INTEGER NOT NULL, " + "cost INTEGER NOT NULL, " + "type INTEGER NOT NULL, " + "FOREIGN KEY(id_user) REFERENCES users(id))" + ); + + // Создание таблицы favorites + success &= query.exec( + "CREATE TABLE IF NOT EXISTS favorites (" + "id_user INTEGER NOT NULL, " + "products TEXT NOT NULL, " // Хранение массива ID продуктов в виде строки + "calories INTEGER NOT NULL, " + "all_cost INTEGER NOT NULL, " + "all_weight INTEGER NOT NULL, " + "FOREIGN KEY(id_user) REFERENCES users(id))" + ); + + // Создание таблицы statistics + success &= query.exec( + "CREATE TABLE IF NOT EXISTS statistics (" + "count_registrations INTEGER DEFAULT 0, " + "count_visits INTEGER DEFAULT 0, " + "count_generations INTEGER DEFAULT 0)" + ); + + return success; +} + +bool Migrations::seedInitialData(QSqlDatabase& db) { + QSqlQuery query(db); + bool success = true; + + // Инициализация статистики, если таблица пуста + success &= query.exec("INSERT OR IGNORE INTO statistics (count_registrations, count_visits, count_generations) VALUES (0, 0, 0)"); + + // Создание администратора по умолчанию + success &= query.exec("INSERT OR IGNORE INTO users(name, email, pass, is_admin) VALUES ('NewDev','admin@new-devs.ru','admin',true)"); + + return success; +} \ No newline at end of file diff --git a/server/database/migrations.h b/server/database/migrations.h new file mode 100644 index 0000000..bd676b9 --- /dev/null +++ b/server/database/migrations.h @@ -0,0 +1,37 @@ +#ifndef MIGRATIONS_H +#define MIGRATIONS_H + +#include +#include +#include +#include + +/** + * @brief Класс для управления миграциями базы данных + */ +class Migrations { +public: + /** + * @brief Выполняет все миграции базы данных + * @param db Ссылка на открытое соединение с БД + * @return true в случае успеха, false при ошибке + */ + static bool runMigrations(QSqlDatabase& db); + +private: + /** + * @brief Создает основные таблицы базы данных + * @param db Ссылка на открытое соединение с БД + * @return true в случае успеха, false при ошибке + */ + static bool createTables(QSqlDatabase& db); + + /** + * @brief Инициализирует начальные данные + * @param db Ссылка на открытое соединение с БД + * @return true в случае успеха, false при ошибке + */ + static bool seedInitialData(QSqlDatabase& db); +}; + +#endif // MIGRATIONS_H \ No newline at end of file diff --git a/server/databasesingleton.h b/server/databasesingleton.h deleted file mode 100644 index db7e7e3..0000000 --- a/server/databasesingleton.h +++ /dev/null @@ -1,62 +0,0 @@ -#ifndef DATABASESINGLETON_H -#define DATABASESINGLETON_H - -#include -#include -#include -#include -#include -#include - -class DataBaseSingleton; - -// Класс-разрушитель для корректного удаления Singleton -class SingletonDestroyer { -private: - DataBaseSingleton* p_instance; // Указатель на экземпляр Singleton -public: - ~SingletonDestroyer(); // Деструктор для удаления Singleton - void initialize(DataBaseSingleton* p); // Инициализация указателя -}; - -// Основной класс для работы с базой данных -class DataBaseSingleton { -private: - static DataBaseSingleton* p_instance; // Единственный экземпляр класса - static SingletonDestroyer destroyer; // Объект-разрушитель - QSqlDatabase db; // Объект базы данных - - DataBaseSingleton(); // Приватный конструктор - DataBaseSingleton(const DataBaseSingleton&) = delete; // Запрет копирования - DataBaseSingleton& operator=(const DataBaseSingleton&) = delete; - ~DataBaseSingleton() = default; // Приватный деструктор - friend class SingletonDestroyer; // Дружественный класс для доступа к деструктору - -public: - // Получение экземпляра Singleton - static DataBaseSingleton* getInstance(); - - // Инициализация БД: имя файла, создание таблиц - bool initialize(const QString& databaseName); - - // Выполнение параметризованного SQL-запроса - QSqlQuery executeQuery(const QString& query, const QVariantMap& params = QVariantMap()); - - // Методы для работы с таблицей users - bool checkUserCredentials(const QString& login, const QString& password); - bool addUser(const QString& name, const QString& email, const QString& password, bool isAdmin = false); - - // Методы для работы с таблицей products - bool addProduct(int userId, const QString& name, int proteins, int fatness, int carbs, int weight, int cost, int type); - QVector getProductsByUser(int userId); - - // Методы для работы с таблицей favorites - bool addFavoriteRation(int userId, const QVector& productIds, int calories, int allCost, int allWeight); - QVector getFavoritesByUser(int userId); - - // Методы для работы с таблицей statistics - bool updateStatistics(int registrations, int visits, int generations); - QVariantMap getStatistics(); -}; - -#endif // DATABASESINGLETON_H diff --git a/server/echoServer.pro b/server/echoServer.pro index fa16d4f..3aef311 100644 --- a/server/echoServer.pro +++ b/server/echoServer.pro @@ -18,10 +18,15 @@ DEFINES += QT_DEPRECATED_WARNINGS #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 SOURCES += \ - databasesingleton.cpp \ - func2serv.cpp \ + database/databasesingleton.cpp \ + database/migrations.cpp \ main.cpp \ - mytcpserver.cpp + network/mytcpserver.cpp \ + network/requesthandler.cpp \ + services/authservice.cpp \ + services/productservice.cpp \ + services/rationservice.cpp \ + services/statsservice.cpp # Default rules for deployment. qnx: target.path = /tmp/$${TARGET}/bin @@ -29,6 +34,11 @@ else: unix:!android: target.path = /opt/$${TARGET}/bin !isEmpty(target.path): INSTALLS += target HEADERS += \ - databasesingleton.h \ - func2serv.h \ - mytcpserver.h + database/databasesingleton.h \ + database/migrations.h \ + network/mytcpserver.h \ + network/requesthandler.h \ + services/authservice.h \ + services/productservice.h \ + services/rationservice.h \ + services/statsservice.h \ No newline at end of file diff --git a/server/func2serv.cpp b/server/func2serv.cpp deleted file mode 100644 index c2c676f..0000000 --- a/server/func2serv.cpp +++ /dev/null @@ -1,376 +0,0 @@ -#include "func2serv.h" -#include -#include -#include -#include -#include -#include -#include -#include -// Заглушка для базы данных - -QMap> mockDatabase = { - {"1", {"orange_11_45_12_24", "banana_11_45_12_24", "orange_11_45_12_24", "orange_11_45_12_24", "orange_11_45_12_24", "orange_11_45_12_24", "orange_11_45_12_24", "orange_11_45_12_24"}}, - {"2", {"apple_11_45_12_24", "grape_11_45_12_24"}} - }; - -using namespace std; -// КРИТИЧЕСКИ ВАЖНОЕ исправление в func2serv.cpp -// Полная замена функции parsing с корректной обработкой admin команд: - -QByteArray parsing(QString input, int socdes) -{ - // Архитектурное решение: нормализация входных данных - QStringList container = input.remove("\r\n").split("//"); - - if (container.isEmpty()) { - qDebug() << "ERROR: Empty command received"; - return "server error: empty command\r\n"; - } - - // Диагностика для отладки маршрутизации - qDebug() << "=== PARSING REQUEST ==="; - qDebug() << "Socket:" << socdes; - qDebug() << "Raw input:" << input; - qDebug() << "Parsed command:" << container; - qDebug() << "Command[0]:" << (container.size() > 0 ? container[0] : "NONE"); - qDebug() << "Command[1]:" << (container.size() > 1 ? container[1] : "NONE"); - - QString var = container[0]; - - // КРИТИЧЕСКОЕ ИСПРАВЛЕНИЕ: правильная обработка admin команд - if (var == "admin") { - qDebug() << "Admin command detected"; - - if (container.size() < 2) { - qDebug() << "ERROR: Admin command without subcommand"; - return "server error: admin command requires subcommand\r\n"; - } - - QString adminCmd = container[1]; - qDebug() << "Admin subcommand:" << adminCmd; - - if (adminCmd == "get_all_users") { - qDebug() << "Executing get_all_users..."; - QByteArray result = get_all_users(); - qDebug() << "get_all_users returned:" << result.size() << "bytes"; - return result; - } - else if (adminCmd == "dynamic_stat") { - return get_dynamic_stat(); - } - else if (adminCmd == "stable_stat") { - return get_stable_stat(); - } - else { - qDebug() << "Unknown admin command:" << adminCmd; - return "server error: unknown admin command\r\n"; - } - } - else if (var == "check_task") { - return check_task(); - } - else if (var == "auth") { - return auth(container); - } - else if (var == "add_product") { - return add_product(container); - } - else if (var == "user" && container.size() >= 3) { - if (container[2] == "get_products") { - return get_products(container[1]); - } - else if (container[2] == "add_favorite_ration") { - return add_favorite_ration(container); - } - } - else if (var == "reg") { - return reg(container); - } - else if (var == "get_stat") { - return get_stat(); - } - else if (var == "menu_export") { - return menu_export(); - } - else { - qDebug() << "ERROR: Unknown command:" << var; - return "server error: unknown command\r\n"; - } -} - -// дарова Руслан, когда будешь писать тут функцию эту, при успешной авторизации просто пропиши return "true", при неуспешной return "false", на клиенте я так принимаю ответ -QByteArray auth(QStringList log) { - // Проверяем количество параметров - if (log.size() < 3) { - return "auth_failed//Недостаточно параметров для авторизации\r\n"; - } - - DataBaseSingleton* db = DataBaseSingleton::getInstance(); - bool authSuccess = db->checkUserCredentials(log[1], log[2]); - - if (!authSuccess) { - return "auth_failed//Неверный логин или пароль\r\n"; - } - - - - QSqlQuery query = db->executeQuery( - "SELECT id, name, email, pass FROM users WHERE (email = :login OR name = :login) AND pass = :pass", - { - {":login", log[1]}, // логин - {":pass", log[2]} // пароль - } - ); - - - if (!query.next()) { - return "auth_failed//Ошибка при получении данных пользователя\r\n"; - } - - QString userId = query.value("id").toString(); - QString userLogin = query.value("name").toString(); - QString userEmail = query.value("email").toString(); - - QString response = QString("auth_success//%1//%2//%3\r\n") - .arg(userId) - .arg(userLogin) - .arg(userEmail); - - return response.toUtf8(); -} - - -QByteArray reg(QStringList params) { - // 1️⃣ Проверка количества параметров - if (params.size() != 4) { - return "reg_failed//Недостаточно параметров для регистрации\r\n"; - } - - // 2️⃣ Извлечение данных из запроса - QString name = params[1]; // Имя пользователя - QString email = params[2]; // Email (должен быть уникальным) - QString password = params[3]; // Пароль - - DataBaseSingleton* db = DataBaseSingleton::getInstance(); - - // 3️⃣ Проверка, не занят ли email - QSqlQuery checkQuery = db->executeQuery( - "SELECT id FROM users WHERE email = :email", - {{":email", email}} - ); - - // Если запрос не выполнился (ошибка БД) - if (!checkQuery.exec()) { - return "reg_failed//Ошибка при проверке email\r\n"; - } - - // Если email уже существует (найдена запись) - if (checkQuery.next()) { - return "reg_failed//Пользователь с таким email уже зарегистрирован\r\n"; - } - - // 4️⃣ Попытка добавить пользователя - bool success = db->addUser(name, email, password, false); - - if (success) { - // 5️⃣ Обновление статистики (увеличиваем счетчик регистраций) - QVariantMap stats = db->getStatistics(); - db->updateStatistics( - stats["registrations"].toInt() + 1, // +1 новая регистрация - stats["visits"].toInt(), // Визиты без изменений - stats["generations"].toInt() // Генерации без изменений - ); - return "reg_success//Регистрация прошла успешно\r\n"; - } else { - // Если INSERT не сработал (например, из-за UNIQUE INDEX) - return "reg_failed//Ошибка при регистрации (возможно, email уже занят)\r\n"; - } -} - -QByteArray add_product(QStringList params) { - if (params.size() != 9) { - return "add_product//failed//Неверные аргументы\r\n"; - } - - DataBaseSingleton *db = DataBaseSingleton::getInstance(); - int userId = params[1].toInt(); - QString name = params[2]; -/* - // Проверяем, существует ли уже такой продукт - QSqlQuery checkQuery = db->executeQuery( - "SELECT id FROM products WHERE id_user = :id_user AND name = :name", - {{":id_user", userId}, {":name", name}} - ); - - if (checkQuery.next()) { - return "add_product//failed//Продукт уже существует\r\n"; - } -*/ - // Добавляем продукт - bool success = db->addProduct( - userId, - name, - params[3].toInt(), // proteins - params[4].toInt(), // fatness - params[5].toInt(), // carbs - params[6].toInt(), // weight - params[7].toInt(), // cost - params[8].toInt() // type - ); - - return success ? "add_product//success\r\n" : "add_product//failed//Ошибка БД\r\n"; -} - -QByteArray get_stat(/*QStringList*/){ - return "Your Statistic: null\r\n"; -} - -QByteArray check_task(/*QStringList*/){ - return "Task was succesful completed\r\n"; -} -QByteArray menu_export(/*QStringList*/){ - return "Меню успешно экспортировано!\r\n"; -} - -void fetch_products_from_db(const QString& userId, QStringList& products) { - - if (mockDatabase.contains(userId)) { - products = mockDatabase[userId]; - } -} -QByteArray get_products(QString userId) { - DataBaseSingleton* db = DataBaseSingleton::getInstance(); - int userIdInt = userId.toInt(); - QVector products = db->getProductsByUser(userIdInt); - - QJsonArray jsonArray; - for (const QVariantMap& product : products) { - QJsonObject obj = QJsonObject::fromVariantMap(product); - jsonArray.append(obj); - } - - QJsonDocument doc(jsonArray); - QByteArray jsonBytes = doc.toJson(QJsonDocument::Compact); - - qDebug() << "Отправляем продукты в виде JSON:" << jsonBytes; - - return jsonBytes; -} - -QByteArray get_all_users() { - qDebug() << "\n=== EXECUTING GET_ALL_USERS ==="; - - // Техническое решение: early return паттерн для минимизации задержек - DataBaseSingleton* db = DataBaseSingleton::getInstance(); - if (!db) { - qDebug() << "CRITICAL: Database not initialized"; - return "[]"; - } - - // Архитектурное решение: единственный SQL запрос без дополнительных проверок - QSqlQuery query = db->executeQuery( - "SELECT id, name, email, is_admin FROM users ORDER BY id" - ); - - // Быстрая проверка на ошибки SQL - if (query.lastError().isValid()) { - qDebug() << "SQL Error:" << query.lastError().text(); - return "[]"; - } - - // Оптимизация: резервирование памяти для JSON - QJsonArray usersArray; - - while (query.next()) { - QJsonObject userObj; - userObj["id"] = query.value(0).toInt(); - userObj["name"] = query.value(1).toString(); - userObj["email"] = query.value(2).toString(); - userObj["is_admin"] = query.value(3).toBool(); - - usersArray.append(userObj); - } - - qDebug() << "Users found:" << usersArray.size(); - - // Критически важно: компактная сериализация для минимизации размера - QJsonDocument doc(usersArray); - QByteArray result = doc.toJson(QJsonDocument::Compact); - - qDebug() << "JSON generated, size:" << result.size(); - qDebug() << "=== GET_ALL_USERS COMPLETE ===\n"; - - return result; -} - -int get_user_count() { - // Здесь будет SQL-запрос, пока заглушка - return 152; // Примерное значение -} - -int get_product_count() { - // Здесь будет SQL-запрос, пока заглушка - return 732; // Примерное значение -} -QByteArray get_stable_stat() { - - int userCount = 0; - int productCount = 0; - - userCount = get_user_count(); - productCount = get_product_count(); - - // Формируем строку ответа - QString response = "Users: " + QString::number(userCount) + "\r\n" + - "Products: " + QString::number(productCount) + "\r\n"; - - return response.toUtf8(); -} -int get_weekly_logins() { - // Заглушка, пока без БД - return 78; // Примерное значение -} - -int get_monthly_logins() { - // Заглушка, пока без БД - return 312; // Примерное значение -} -QByteArray get_dynamic_stat() { - int weeklyLogins = 0; - int monthlyLogins = 0; - - // Получаем данные из БД (пока заглушки) - weeklyLogins = get_weekly_logins(); - monthlyLogins = get_monthly_logins(); - - // Формируем строку ответа - QString response = "Logins per week: " + QString::number(weeklyLogins) + "\r\n" + - "Logins per month: " + QString::number(monthlyLogins) + "\r\n"; - - return response.toUtf8(); -} -QByteArray add_favorite_ration(const QStringList& container) { - QString userId = container[1]; // ID пользователя - QString rationId = container[2]; // ID рациона - - bool success = add_ration_to_favorites(userId, rationId); // Вызов функции-заглушки - - if (success) { - return "Ration successfully added to favorites\r\n"; - } else { - return "Error: failed to add ration to favorites\r\n"; - } -} -bool add_ration_to_favorites(const QString& userId, const QString& rationId) { - qDebug() << "Adding ration for user:" << userId << ", ration ID:" << rationId; - return true; // Заглушка, потом заменить на SQL-запрос -} - - - - - - - - diff --git a/server/func2serv.h b/server/func2serv.h deleted file mode 100644 index 86f0946..0000000 --- a/server/func2serv.h +++ /dev/null @@ -1,27 +0,0 @@ -#ifndef FUNC2SERV_H -#define FUNC2SERV_H - -#include - -QByteArray parsing(QString input, int socdes); - -QByteArray auth(QStringList ); -QByteArray reg(QStringList); -QByteArray add_product(QStringList); -QByteArray get_stat(/*QStringList*/); -QByteArray check_task(/*QStringList*/); -QByteArray menu_export(/*QStringList*/); -QByteArray get_products(QString UserId); -QByteArray get_all_users(); -QByteArray get_stable_stat(); -int get_user_count(); -int get_product_count(); -QByteArray get_dynamic_stat(); -int get_weekly_logins(); -int get_monthly_logins(); -QByteArray add_favorite_ration(const QStringList& container); -bool add_ration_to_favorites(const QString& userId, const QString& rationId); - -#include - -#endif // FUNC2SERV_H diff --git a/server/main.cpp b/server/main.cpp index c999dfb..ec07699 100644 --- a/server/main.cpp +++ b/server/main.cpp @@ -1,12 +1,10 @@ #include #include -#include +#include #include #include -#include "mytcpserver.h" -#include "databasesingleton.h" - - +#include "network/mytcpserver.h" +#include "database/databasesingleton.h" int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); @@ -17,7 +15,8 @@ int main(int argc, char *argv[]) { qFatal("Failed to initialize database"); } - + // Создание и запуск TCP-сервера MyTcpServer myserv; + return a.exec(); -} +} \ No newline at end of file diff --git a/server/mytcpserver.h b/server/mytcpserver.h deleted file mode 100644 index fe179a1..0000000 --- a/server/mytcpserver.h +++ /dev/null @@ -1,27 +0,0 @@ -#ifndef MYTCPSERVER_H -#define MYTCPSERVER_H -#include -#include -#include -#include -#include -#include -#include - -class MyTcpServer : public QObject -{ - Q_OBJECT -public: - explicit MyTcpServer(QObject *parent = nullptr); - ~MyTcpServer(); -public slots: - void slotNewConnection(); - void slotClientDisconnected(); - void slotServerRead(); -private: - QTcpServer * mTcpServer; - QTcpSocket * mTcpSocket; - int server_status; - QMap mSocketDescriptors; // Хранение дескрипторов сокетов -}; -#endif // MYTCPSERVER_H diff --git a/server/mytcpserver.cpp b/server/network/mytcpserver.cpp similarity index 90% rename from server/mytcpserver.cpp rename to server/network/mytcpserver.cpp index cb74f94..7af72b4 100644 --- a/server/mytcpserver.cpp +++ b/server/network/mytcpserver.cpp @@ -1,134 +1,135 @@ -#include "mytcpserver.h" -#include -#include -#include -#include "func2serv.h" - -MyTcpServer::~MyTcpServer() -{ - mTcpServer->close(); - server_status = 0; - qDeleteAll(mSocketDescriptors); // Удаляем все сокеты -} - -MyTcpServer::MyTcpServer(QObject *parent) : QObject(parent) -{ - mTcpServer = new QTcpServer(this); - - connect(mTcpServer, &QTcpServer::newConnection, this, &MyTcpServer::slotNewConnection); - - if (!mTcpServer->listen(QHostAddress::Any, 33333)) { - qDebug() << "server is not started"; - } else { - server_status = 1; - qDebug() << "server is started"; - } -} - -void MyTcpServer::slotNewConnection() -{ - if (server_status == 1) { - QTcpSocket *clientSocket = mTcpServer->nextPendingConnection(); - int socketDescriptor = clientSocket->socketDescriptor(); // Получаем дескриптор сокета - - if (socketDescriptor == -1) { - qDebug() << "Invalid socket descriptor!"; - clientSocket->deleteLater(); // Удаляем сокет, если дескриптор недействителен - return; - } - - mSocketDescriptors.insert(socketDescriptor, clientSocket); // Сохраняем сокет в контейнере - - qDebug() << "New connection, socket descriptor:" << socketDescriptor; - - connect(clientSocket, &QTcpSocket::readyRead, this, &MyTcpServer::slotServerRead); - connect(clientSocket, &QTcpSocket::disconnected, this, &MyTcpServer::slotClientDisconnected); - } -} - -void MyTcpServer::slotServerRead() { - QTcpSocket *clientSocket = qobject_cast(sender()); - if (!clientSocket) { - qDebug() << "CRITICAL ERROR: Invalid socket in slotServerRead"; - return; - } - - QByteArray data = clientSocket->readAll(); - - // Архитектурное решение: детальное логирование для диагностики - qDebug() << "\n=== SERVER REQUEST PROCESSING ==="; - qDebug() << "Socket descriptor:" << clientSocket->socketDescriptor(); - qDebug() << "Received bytes:" << data.size(); - qDebug() << "Raw data:" << data; - qDebug() << "As string:" << QString(data); - - // Техническое решение: валидация данных перед обработкой - if (data.isEmpty()) { - qDebug() << "WARNING: Empty data received"; - return; - } - - // Обработка запроса - QString trimmedInput = QString(data).trimmed(); - qDebug() << "Processing command:" << trimmedInput; - - QByteArray response = parsing(trimmedInput, clientSocket->socketDescriptor()); - - qDebug() << "Response size:" << response.size(); - qDebug() << "Response preview:" << response.left(100); - - // КРИТИЧЕСКИ ВАЖНО: правильная отправка данных - if (!response.isEmpty()) { - qint64 bytesWritten = clientSocket->write(response); - qDebug() << "Bytes written:" << bytesWritten; - - // Архитектурное решение: гарантированная отправка данных - if (bytesWritten > 0) { - // КРИТИЧЕСКИ ВАЖНО: форсируем отправку - clientSocket->flush(); - qDebug() << "Data flushed successfully"; - - // Дополнительная проверка состояния сокета - if (clientSocket->state() == QTcpSocket::ConnectedState) { - qDebug() << "Socket still connected after write"; - } else { - qDebug() << "WARNING: Socket state changed:" << clientSocket->state(); - } - } else { - qDebug() << "ERROR: Failed to write response"; - } - } else { - qDebug() << "WARNING: Empty response from parsing"; - // Отправляем пустой JSON массив как fallback - clientSocket->write("[]"); - clientSocket->flush(); - } - - qDebug() << "=== SERVER REQUEST COMPLETE ===\n"; -} - -void MyTcpServer::slotClientDisconnected() -{ - QTcpSocket *clientSocket = qobject_cast(sender()); - if (!clientSocket) { - return; - } - - // Получаем дескриптор сокета из контейнера - int socketDescriptor = -1; - for (auto it = mSocketDescriptors.begin(); it != mSocketDescriptors.end(); ++it) { - if (it.value() == clientSocket) { - socketDescriptor = it.key(); - break; - } - } - - if (socketDescriptor != -1) { - mSocketDescriptors.remove(socketDescriptor); // Удаляем сокет из контейнера - qDebug() << "Client disconnected, socket descriptor:" << socketDescriptor; - } else { - qDebug() << "Client disconnected, but socket descriptor not found!"; - } - - clientSocket->deleteLater(); // Удаляем сокет -} +#include "mytcpserver.h" +#include "requesthandler.h" +#include +#include +#include + +MyTcpServer::MyTcpServer(QObject *parent) : QObject(parent) +{ + mTcpServer = new QTcpServer(this); + mRequestHandler = new RequestHandler(this); + + connect(mTcpServer, &QTcpServer::newConnection, this, &MyTcpServer::slotNewConnection); + + if (!mTcpServer->listen(QHostAddress::Any, 33333)) { + qDebug() << "server is not started"; + server_status = 0; + } else { + server_status = 1; + qDebug() << "server is started"; + } +} + +MyTcpServer::~MyTcpServer() +{ + mTcpServer->close(); + server_status = 0; + qDeleteAll(mSocketDescriptors); // Удаляем все сокеты + delete mRequestHandler; +} + +void MyTcpServer::slotNewConnection() +{ + if (server_status == 1) { + QTcpSocket *clientSocket = mTcpServer->nextPendingConnection(); + int socketDescriptor = clientSocket->socketDescriptor(); // Получаем дескриптор сокета + + if (socketDescriptor == -1) { + qDebug() << "Invalid socket descriptor!"; + clientSocket->deleteLater(); // Удаляем сокет, если дескриптор недействителен + return; + } + + mSocketDescriptors.insert(socketDescriptor, clientSocket); // Сохраняем сокет в контейнере + + qDebug() << "New connection, socket descriptor:" << socketDescriptor; + + connect(clientSocket, &QTcpSocket::readyRead, this, &MyTcpServer::slotServerRead); + connect(clientSocket, &QTcpSocket::disconnected, this, &MyTcpServer::slotClientDisconnected); + } +} + +void MyTcpServer::slotServerRead() { + QTcpSocket *clientSocket = qobject_cast(sender()); + if (!clientSocket) { + qDebug() << "CRITICAL ERROR: Invalid socket in slotServerRead"; + return; + } + + QByteArray data = clientSocket->readAll(); + int socketDescriptor = clientSocket->socketDescriptor(); + + // Архитектурное решение: детальное логирование для диагностики + qDebug() << "\n=== SERVER REQUEST PROCESSING ==="; + qDebug() << "Socket descriptor:" << socketDescriptor; + qDebug() << "Received bytes:" << data.size(); + qDebug() << "Raw data:" << data; + qDebug() << "As string:" << QString(data); + + // Техническое решение: валидация данных перед обработкой + if (data.isEmpty()) { + qDebug() << "WARNING: Empty data received"; + return; + } + + // Обработка запроса через RequestHandler + QByteArray response = mRequestHandler->handleRequest(data, socketDescriptor); + + qDebug() << "Response size:" << response.size(); + qDebug() << "Response preview:" << response.left(100); + + // КРИТИЧЕСКИ ВАЖНО: правильная отправка данных + if (!response.isEmpty()) { + qint64 bytesWritten = clientSocket->write(response); + qDebug() << "Bytes written:" << bytesWritten; + + // Архитектурное решение: гарантированная отправка данных + if (bytesWritten > 0) { + // КРИТИЧЕСКИ ВАЖНО: форсируем отправку + clientSocket->flush(); + qDebug() << "Data flushed successfully"; + + // Дополнительная проверка состояния сокета + if (clientSocket->state() == QTcpSocket::ConnectedState) { + qDebug() << "Socket still connected after write"; + } else { + qDebug() << "WARNING: Socket state changed:" << clientSocket->state(); + } + } else { + qDebug() << "ERROR: Failed to write response"; + } + } else { + qDebug() << "WARNING: Empty response from parsing"; + // Отправляем пустой JSON массив как fallback + clientSocket->write("[]"); + clientSocket->flush(); + } + + qDebug() << "=== SERVER REQUEST COMPLETE ===\n"; +} + +void MyTcpServer::slotClientDisconnected() +{ + QTcpSocket *clientSocket = qobject_cast(sender()); + if (!clientSocket) { + return; + } + + // Получаем дескриптор сокета из контейнера + int socketDescriptor = -1; + for (auto it = mSocketDescriptors.begin(); it != mSocketDescriptors.end(); ++it) { + if (it.value() == clientSocket) { + socketDescriptor = it.key(); + break; + } + } + + if (socketDescriptor != -1) { + mSocketDescriptors.remove(socketDescriptor); // Удаляем сокет из контейнера + qDebug() << "Client disconnected, socket descriptor:" << socketDescriptor; + } else { + qDebug() << "Client disconnected, but socket descriptor not found!"; + } + + clientSocket->deleteLater(); // Удаляем сокет +} \ No newline at end of file diff --git a/server/network/mytcpserver.h b/server/network/mytcpserver.h new file mode 100644 index 0000000..fd67278 --- /dev/null +++ b/server/network/mytcpserver.h @@ -0,0 +1,53 @@ +#ifndef MYTCPSERVER_H +#define MYTCPSERVER_H +#include +#include +#include +#include +#include +#include +#include + +class RequestHandler; + +/** + * @brief Класс TCP-сервера для обработки клиентских соединений + */ +class MyTcpServer : public QObject +{ + Q_OBJECT +public: + /** + * @brief Конструктор сервера + * @param parent Родительский объект + */ + explicit MyTcpServer(QObject *parent = nullptr); + + /** + * @brief Деструктор сервера + */ + ~MyTcpServer(); + +public slots: + /** + * @brief Обработка нового соединения + */ + void slotNewConnection(); + + /** + * @brief Обработка отключения клиента + */ + void slotClientDisconnected(); + + /** + * @brief Обработка входящих данных + */ + void slotServerRead(); + +private: + QTcpServer* mTcpServer; ///< Объект TCP-сервера + QMap mSocketDescriptors; ///< Хранение дескрипторов сокетов + int server_status; ///< Статус сервера + RequestHandler* mRequestHandler; ///< Обработчик запросов +}; +#endif // MYTCPSERVER_H \ No newline at end of file diff --git a/server/network/requesthandler.cpp b/server/network/requesthandler.cpp new file mode 100644 index 0000000..0d19371 --- /dev/null +++ b/server/network/requesthandler.cpp @@ -0,0 +1,98 @@ +#include "requesthandler.h" +#include "../services/authservice.h" +#include "../services/productservice.h" +#include "../services/rationservice.h" +#include "../services/statsservice.h" +#include + +RequestHandler::RequestHandler(QObject *parent) : QObject(parent) +{ + m_authService = new AuthService(this); + m_productService = new ProductService(this); + m_rationService = new RationService(this); + m_statsService = new StatsService(this); +} + +RequestHandler::~RequestHandler() +{ + // QObject автоматически удалит дочерние объекты +} + +QByteArray RequestHandler::handleRequest(const QByteArray& data, int socketDescriptor) +{ + // Архитектурное решение: нормализация входных данных + QString input = QString::fromUtf8(data); + QStringList container = input.remove("\r\n").split("//"); + + if (container.isEmpty()) { + qDebug() << "ERROR: Empty command received"; + return "server error: empty command\r\n"; + } + + // Диагностика для отладки маршрутизации + qDebug() << "=== PARSING REQUEST ==="; + qDebug() << "Socket:" << socketDescriptor; + qDebug() << "Raw input:" << input; + qDebug() << "Parsed command:" << container; + qDebug() << "Command[0]:" << (container.size() > 0 ? container[0] : "NONE"); + qDebug() << "Command[1]:" << (container.size() > 1 ? container[1] : "NONE"); + + QString command = container[0]; + + // Маршрутизация запроса к соответствующему сервису + if (command == "admin") { + if (container.size() < 2) { + qDebug() << "ERROR: Admin command without subcommand"; + return "server error: admin command requires subcommand\r\n"; + } + + QString adminCmd = container[1]; + qDebug() << "Admin subcommand:" << adminCmd; + + if (adminCmd == "get_all_users") { + return m_authService->getAllUsers(); + } + else if (adminCmd == "dynamic_stat") { + return m_statsService->getDynamicStats(); + } + else if (adminCmd == "stable_stat") { + return m_statsService->getStableStats(); + } + else { + qDebug() << "Unknown admin command:" << adminCmd; + return "server error: unknown admin command\r\n"; + } + } + else if (command == "check_task") { + return "Task was succesful completed\r\n"; + } + else if (command == "auth") { + return m_authService->authenticateUser(container); + } + else if (command == "add_product") { + return m_productService->addProduct(container); + } + else if (command == "user" && container.size() >= 3) { + if (container[2] == "get_products") { + return m_productService->getProductsByUser(container[1]); + } + else if (container[2] == "add_favorite_ration") { + return m_rationService->addFavoriteRation(container); + } + } + else if (command == "reg") { + return m_authService->registerUser(container); + } + else if (command == "get_stat") { + return m_statsService->getStatistics(); + } + else if (command == "menu_export") { + return "Меню успешно экспортировано!\r\n"; + } + else { + qDebug() << "ERROR: Unknown command:" << command; + return "server error: unknown command\r\n"; + } + + return "Error: Request not processed\r\n"; +} \ No newline at end of file diff --git a/server/network/requesthandler.h b/server/network/requesthandler.h new file mode 100644 index 0000000..dee8520 --- /dev/null +++ b/server/network/requesthandler.h @@ -0,0 +1,48 @@ +#ifndef REQUESTHANDLER_H +#define REQUESTHANDLER_H + +#include +#include +#include +#include + +// Предварительные объявления сервисов +class AuthService; +class ProductService; +class RationService; +class StatsService; + +/** + * @brief Класс для обработки запросов от клиентов + */ +class RequestHandler : public QObject +{ + Q_OBJECT +public: + /** + * @brief Конструктор обработчика запросов + * @param parent Родительский объект + */ + explicit RequestHandler(QObject *parent = nullptr); + + /** + * @brief Деструктор обработчика запросов + */ + ~RequestHandler(); + + /** + * @brief Обработка запроса от клиента + * @param data Данные запроса + * @param socketDescriptor Дескриптор сокета клиента + * @return Ответ на запрос + */ + QByteArray handleRequest(const QByteArray& data, int socketDescriptor); + +private: + AuthService* m_authService; ///< Сервис аутентификации + ProductService* m_productService; ///< Сервис работы с продуктами + RationService* m_rationService; ///< Сервис работы с рационами + StatsService* m_statsService; ///< Сервис статистики +}; + +#endif // REQUESTHANDLER_H \ No newline at end of file diff --git a/server/services/authservice.cpp b/server/services/authservice.cpp new file mode 100644 index 0000000..744f59d --- /dev/null +++ b/server/services/authservice.cpp @@ -0,0 +1,144 @@ +#include "authservice.h" +#include "../database/databasesingleton.h" +#include +#include +#include +#include +#include + +AuthService::AuthService(QObject *parent) : QObject(parent) +{ +} + +QByteArray AuthService::authenticateUser(const QStringList& params) +{ + // Проверяем количество параметров + if (params.size() < 3) { + return "auth_failed//Недостаточно параметров для авторизации\r\n"; + } + + DataBaseSingleton* db = DataBaseSingleton::getInstance(); + bool authSuccess = db->checkUserCredentials(params[1], params[2]); + + if (!authSuccess) { + return "auth_failed//Неверный логин или пароль\r\n"; + } + + QSqlQuery query = db->executeQuery( + "SELECT id, name, email, pass FROM users WHERE (email = :login OR name = :login) AND pass = :pass", + { + {":login", params[1]}, // логин + {":pass", params[2]} // пароль + } + ); + + if (!query.next()) { + return "auth_failed//Ошибка при получении данных пользователя\r\n"; + } + + QString userId = query.value("id").toString(); + QString userLogin = query.value("name").toString(); + QString userEmail = query.value("email").toString(); + + QString response = QString("auth_success//%1//%2//%3\r\n") + .arg(userId) + .arg(userLogin) + .arg(userEmail); + + return response.toUtf8(); +} + +QByteArray AuthService::registerUser(const QStringList& params) +{ + // Проверка количества параметров + if (params.size() != 4) { + return "reg_failed//Недостаточно параметров для регистрации\r\n"; + } + + // Извлечение данных из запроса + QString name = params[1]; // Имя пользователя + QString email = params[2]; // Email (должен быть уникальным) + QString password = params[3]; // Пароль + + DataBaseSingleton* db = DataBaseSingleton::getInstance(); + + // Проверка, не занят ли email + QSqlQuery checkQuery = db->executeQuery( + "SELECT id FROM users WHERE email = :email", + {{":email", email}} + ); + + // Если запрос не выполнился (ошибка БД) + if (!checkQuery.exec()) { + return "reg_failed//Ошибка при проверке email\r\n"; + } + + // Если email уже существует (найдена запись) + if (checkQuery.next()) { + return "reg_failed//Пользователь с таким email уже зарегистрирован\r\n"; + } + + // Попытка добавить пользователя + bool success = db->addUser(name, email, password, false); + + if (success) { + // Обновление статистики (увеличиваем счетчик регистраций) + QVariantMap stats = db->getStatistics(); + db->updateStatistics( + stats["registrations"].toInt() + 1, // +1 новая регистрация + stats["visits"].toInt(), // Визиты без изменений + stats["generations"].toInt() // Генерации без изменений + ); + return "reg_success//Регистрация прошла успешно\r\n"; + } else { + // Если INSERT не сработал (например, из-за UNIQUE INDEX) + return "reg_failed//Ошибка при регистрации (возможно, email уже занят)\r\n"; + } +} + +QByteArray AuthService::getAllUsers() +{ + qDebug() << "\n=== EXECUTING GET_ALL_USERS ==="; + + // Техническое решение: early return паттерн для минимизации задержек + DataBaseSingleton* db = DataBaseSingleton::getInstance(); + if (!db) { + qDebug() << "CRITICAL: Database not initialized"; + return "[]"; + } + + // Архитектурное решение: единственный SQL запрос без дополнительных проверок + QSqlQuery query = db->executeQuery( + "SELECT id, name, email, is_admin FROM users ORDER BY id" + ); + + // Быстрая проверка на ошибки SQL + if (query.lastError().isValid()) { + qDebug() << "SQL Error:" << query.lastError().text(); + return "[]"; + } + + // Оптимизация: резервирование памяти для JSON + QJsonArray usersArray; + + while (query.next()) { + QJsonObject userObj; + userObj["id"] = query.value(0).toInt(); + userObj["name"] = query.value(1).toString(); + userObj["email"] = query.value(2).toString(); + userObj["is_admin"] = query.value(3).toBool(); + + usersArray.append(userObj); + } + + qDebug() << "Users found:" << usersArray.size(); + + // Критически важно: компактная сериализация для минимизации размера + QJsonDocument doc(usersArray); + QByteArray result = doc.toJson(QJsonDocument::Compact); + + qDebug() << "JSON generated, size:" << result.size(); + qDebug() << "=== GET_ALL_USERS COMPLETE ===\n"; + + return result; +} \ No newline at end of file diff --git a/server/services/authservice.h b/server/services/authservice.h new file mode 100644 index 0000000..f2987bd --- /dev/null +++ b/server/services/authservice.h @@ -0,0 +1,42 @@ +#ifndef AUTHSERVICE_H +#define AUTHSERVICE_H + +#include +#include +#include + +/** + * @brief Сервис для управления аутентификацией пользователей + */ +class AuthService : public QObject +{ + Q_OBJECT +public: + /** + * @brief Конструктор сервиса аутентификации + * @param parent Родительский объект + */ + explicit AuthService(QObject *parent = nullptr); + + /** + * @brief Аутентификация пользователя + * @param params Параметры запроса + * @return Результат аутентификации + */ + QByteArray authenticateUser(const QStringList& params); + + /** + * @brief Регистрация нового пользователя + * @param params Параметры запроса + * @return Результат регистрации + */ + QByteArray registerUser(const QStringList& params); + + /** + * @brief Получение списка всех пользователей + * @return JSON с данными пользователей + */ + QByteArray getAllUsers(); +}; + +#endif // AUTHSERVICE_H \ No newline at end of file diff --git a/server/services/productservice.cpp b/server/services/productservice.cpp new file mode 100644 index 0000000..7135587 --- /dev/null +++ b/server/services/productservice.cpp @@ -0,0 +1,55 @@ +#include "productservice.h" +#include "../database/databasesingleton.h" +#include +#include +#include +#include + +ProductService::ProductService(QObject *parent) : QObject(parent) +{ +} + +QByteArray ProductService::addProduct(const QStringList& params) +{ + if (params.size() != 9) { + return "add_product//failed//Неверные аргументы\r\n"; + } + + DataBaseSingleton* db = DataBaseSingleton::getInstance(); + int userId = params[1].toInt(); + QString name = params[2]; + + // Добавляем продукт + bool success = db->addProduct( + userId, + name, + params[3].toInt(), // proteins + params[4].toInt(), // fatness + params[5].toInt(), // carbs + params[6].toInt(), // weight + params[7].toInt(), // cost + params[8].toInt() // type + ); + + return success ? "add_product//success\r\n" : "add_product//failed//Ошибка БД\r\n"; +} + +QByteArray ProductService::getProductsByUser(const QString& userId) +{ + DataBaseSingleton* db = DataBaseSingleton::getInstance(); + int userIdInt = userId.toInt(); + QVector products = db->getProductsByUser(userIdInt); + + QJsonArray jsonArray; + for (const QVariantMap& product : products) { + QJsonObject obj = QJsonObject::fromVariantMap(product); + jsonArray.append(obj); + } + + QJsonDocument doc(jsonArray); + QByteArray jsonBytes = doc.toJson(QJsonDocument::Compact); + + qDebug() << "Отправляем продукты в виде JSON:" << jsonBytes; + + return jsonBytes; +} \ No newline at end of file diff --git a/server/services/productservice.h b/server/services/productservice.h new file mode 100644 index 0000000..d770112 --- /dev/null +++ b/server/services/productservice.h @@ -0,0 +1,36 @@ +#ifndef PRODUCTSERVICE_H +#define PRODUCTSERVICE_H + +#include +#include +#include + +/** + * @brief Сервис для управления продуктами + */ +class ProductService : public QObject +{ + Q_OBJECT +public: + /** + * @brief Конструктор сервиса продуктов + * @param parent Родительский объект + */ + explicit ProductService(QObject *parent = nullptr); + + /** + * @brief Добавление нового продукта + * @param params Параметры запроса + * @return Результат добавления + */ + QByteArray addProduct(const QStringList& params); + + /** + * @brief Получение продуктов пользователя + * @param userId ID пользователя + * @return JSON с данными продуктов + */ + QByteArray getProductsByUser(const QString& userId); +}; + +#endif // PRODUCTSERVICE_H \ No newline at end of file diff --git a/server/services/rationservice.cpp b/server/services/rationservice.cpp new file mode 100644 index 0000000..872898c --- /dev/null +++ b/server/services/rationservice.cpp @@ -0,0 +1,30 @@ +#include "rationservice.h" +#include + +RationService::RationService(QObject *parent) : QObject(parent) +{ +} + +QByteArray RationService::addFavoriteRation(const QStringList& params) +{ + if (params.size() < 3) { + return "Error: invalid parameters for add_favorite_ration\r\n"; + } + + QString userId = params[1]; // ID пользователя + QString rationId = params[2]; // ID рациона + + bool success = addRationToFavorites(userId, rationId); + + if (success) { + return "Ration successfully added to favorites\r\n"; + } else { + return "Error: failed to add ration to favorites\r\n"; + } +} + +bool RationService::addRationToFavorites(const QString& userId, const QString& rationId) +{ + qDebug() << "Adding ration for user:" << userId << ", ration ID:" << rationId; + return true; // Заглушка, потом заменить на SQL-запрос +} \ No newline at end of file diff --git a/server/services/rationservice.h b/server/services/rationservice.h new file mode 100644 index 0000000..9f018d7 --- /dev/null +++ b/server/services/rationservice.h @@ -0,0 +1,38 @@ +#ifndef RATIONSERVICE_H +#define RATIONSERVICE_H + +#include +#include +#include + +/** + * @brief Сервис для управления рационами + */ +class RationService : public QObject +{ + Q_OBJECT +public: + /** + * @brief Конструктор сервиса рационов + * @param parent Родительский объект + */ + explicit RationService(QObject *parent = nullptr); + + /** + * @brief Добавление избранного рациона + * @param params Параметры запроса + * @return Результат добавления + */ + QByteArray addFavoriteRation(const QStringList& params); + +private: + /** + * @brief Добавление рациона в избранное + * @param userId ID пользователя + * @param rationId ID рациона + * @return Успешность операции + */ + bool addRationToFavorites(const QString& userId, const QString& rationId); +}; + +#endif // RATIONSERVICE_H \ No newline at end of file diff --git a/server/services/statsservice.cpp b/server/services/statsservice.cpp new file mode 100644 index 0000000..0db7e8b --- /dev/null +++ b/server/services/statsservice.cpp @@ -0,0 +1,66 @@ +#include "statsservice.h" +#include "../database/databasesingleton.h" + +StatsService::StatsService(QObject *parent) : QObject(parent) +{ +} + +QByteArray StatsService::getStatistics() +{ + return "Your Statistic: null\r\n"; +} + +QByteArray StatsService::getStableStats() +{ + int userCount = 0; + int productCount = 0; + + userCount = getUserCount(); + productCount = getProductCount(); + + // Формируем строку ответа + QString response = "Users: " + QString::number(userCount) + "\r\n" + + "Products: " + QString::number(productCount) + "\r\n"; + + return response.toUtf8(); +} + +QByteArray StatsService::getDynamicStats() +{ + int weeklyLogins = 0; + int monthlyLogins = 0; + + // Получаем данные из БД (пока заглушки) + weeklyLogins = getWeeklyLogins(); + monthlyLogins = getMonthlyLogins(); + + // Формируем строку ответа + QString response = "Logins per week: " + QString::number(weeklyLogins) + "\r\n" + + "Logins per month: " + QString::number(monthlyLogins) + "\r\n"; + + return response.toUtf8(); +} + +int StatsService::getUserCount() +{ + // Здесь будет SQL-запрос, пока заглушка + return 152; // Примерное значение +} + +int StatsService::getProductCount() +{ + // Здесь будет SQL-запрос, пока заглушка + return 732; // Примерное значение +} + +int StatsService::getWeeklyLogins() +{ + // Заглушка, пока без БД + return 78; // Примерное значение +} + +int StatsService::getMonthlyLogins() +{ + // Заглушка, пока без БД + return 312; // Примерное значение +} \ No newline at end of file diff --git a/server/services/statsservice.h b/server/services/statsservice.h new file mode 100644 index 0000000..515425b --- /dev/null +++ b/server/services/statsservice.h @@ -0,0 +1,64 @@ +#ifndef STATSSERVICE_H +#define STATSSERVICE_H + +#include +#include + +/** + * @brief Сервис для управления статистикой + */ +class StatsService : public QObject +{ + Q_OBJECT +public: + /** + * @brief Конструктор сервиса статистики + * @param parent Родительский объект + */ + explicit StatsService(QObject *parent = nullptr); + + /** + * @brief Получение общей статистики + * @return Данные статистики + */ + QByteArray getStatistics(); + + /** + * @brief Получение стабильной статистики + * @return Данные стабильной статистики + */ + QByteArray getStableStats(); + + /** + * @brief Получение динамической статистики + * @return Данные динамической статистики + */ + QByteArray getDynamicStats(); + +private: + /** + * @brief Получение количества пользователей + * @return Количество пользователей + */ + int getUserCount(); + + /** + * @brief Получение количества продуктов + * @return Количество продуктов + */ + int getProductCount(); + + /** + * @brief Получение количества входов за неделю + * @return Количество входов за неделю + */ + int getWeeklyLogins(); + + /** + * @brief Получение количества входов за месяц + * @return Количество входов за месяц + */ + int getMonthlyLogins(); +}; + +#endif // STATSSERVICE_H \ No newline at end of file