From d47988addaa55892ac8f05792d278f9eb8c03343 Mon Sep 17 00:00:00 2001 From: alex Date: Sun, 9 Mar 2025 10:26:52 +0500 Subject: [PATCH] =?UTF-8?q?=D0=A3=D1=87=D0=B8=D0=BC=D1=81=D1=8F=20=D0=BF?= =?UTF-8?q?=D0=BE=D0=BB=D1=8C=D0=B7=D0=BE=D0=B2=D0=B0=D1=82=D1=8C=D1=81?= =?UTF-8?q?=D1=8F=20odb=20=D0=B2=20=D0=BD=D0=B0=D1=88=D0=B5=D0=BC=20=D0=BC?= =?UTF-8?q?=D0=B5=D0=BB=D0=B5=D0=BD=D1=8C=D0=BA=D0=BE=D0=BC=20=D0=BF=D1=80?= =?UTF-8?q?=D0=BE=D0=B5=D0=BA=D1=82=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 5 + cpp-opds.qbs | 13 -- main.cpp | 19 --- project.qbs | 19 +++ qbs/modules/odbModule/odbModule.qbs | 48 ++++++++ src/author_s.h | 63 ++++++++++ src/book_s.h | 46 +++++++ src/cpp-opds.qbs | 58 +++++++++ src/database.hxx | 177 +++++++++++++++++++++++++++ src/databasemanager.cpp | 118 ++++++++++++++++++ src/databasemanager.h | 59 +++++++++ src/main.cpp | 180 ++++++++++++++++++++++++++++ src/restapiserver.cpp | 129 ++++++++++++++++++++ src/restapiserver.h | 28 +++++ test/requests/all_books.http | 1 + test/requests/autor_books.http | 1 + 16 files changed, 932 insertions(+), 32 deletions(-) delete mode 100644 cpp-opds.qbs delete mode 100644 main.cpp create mode 100644 project.qbs create mode 100644 qbs/modules/odbModule/odbModule.qbs create mode 100644 src/author_s.h create mode 100644 src/book_s.h create mode 100644 src/cpp-opds.qbs create mode 100644 src/database.hxx create mode 100644 src/databasemanager.cpp create mode 100644 src/databasemanager.h create mode 100644 src/main.cpp create mode 100644 src/restapiserver.cpp create mode 100644 src/restapiserver.h create mode 100644 test/requests/all_books.http create mode 100644 test/requests/autor_books.http diff --git a/.gitignore b/.gitignore index aa3808c..a14b034 100644 --- a/.gitignore +++ b/.gitignore @@ -80,3 +80,8 @@ CMakeLists.txt.user* .rcc/ .uic/ /build*/ + + +ccache.log + +*-odb.*xx \ No newline at end of file diff --git a/cpp-opds.qbs b/cpp-opds.qbs deleted file mode 100644 index 4e93c3a..0000000 --- a/cpp-opds.qbs +++ /dev/null @@ -1,13 +0,0 @@ -QtApplication { - cpp.defines: [ - // You can make your code fail to compile if it uses deprecated APIs. - // In order to do so, uncomment the following line. - //"QT_DISABLE_DEPRECATED_BEFORE=0x060000" // disables all the APIs deprecated before Qt 6.0.0 - ] - - consoleApplication: true - install: true - files: [ - "main.cpp", - ] -} diff --git a/main.cpp b/main.cpp deleted file mode 100644 index 3994cd4..0000000 --- a/main.cpp +++ /dev/null @@ -1,19 +0,0 @@ -#include - -int main(int argc, char *argv[]) -{ - QCoreApplication a(argc, argv); - - // Set up code that uses the Qt event loop here. - // Call a.quit() or a.exit() to quit the application. - // A not very useful example would be including - // #include - // near the top of the file and calling - // QTimer::singleShot(5000, &a, &QCoreApplication::quit); - // which quits the application after 5 seconds. - - // If you do not need a running Qt event loop, remove the call - // to a.exec() or use the Non-Qt Plain C++ Application template. - - return a.exec(); -} diff --git a/project.qbs b/project.qbs new file mode 100644 index 0000000..11f7f19 --- /dev/null +++ b/project.qbs @@ -0,0 +1,19 @@ +import qbs + +Project { + name: "opds++" + qbsSearchPaths: [ + "qbs", + // "redkitty/qbs", + // // "guiness/qbs", + // // "prerequisites/qbs", + // // "report/qbs", + // "redkbuild", + // // "ext_libs/openssl/qbs", + // "redkitty/lib/redkit-gen-integration/qbs", + ] + + references: [ + "src/cpp-opds.qbs", + ] +} diff --git a/qbs/modules/odbModule/odbModule.qbs b/qbs/modules/odbModule/odbModule.qbs new file mode 100644 index 0000000..0cd4980 --- /dev/null +++ b/qbs/modules/odbModule/odbModule.qbs @@ -0,0 +1,48 @@ +import qbs +import qbs.FileInfo + +Module { + Depends { name: "cpp" } + Depends { name: "Qt.core" } + + FileTagger { + patterns: "*.hxx" + fileTags: ["odb"] + } + + Rule { + inputs: ["odb"] + Artifact { + // filePath: FileInfo.baseName(input.fileName)+ "-odb.hxx" + filePath: FileInfo.cleanPath(input.filePath + "/../" ) + "/" + FileInfo.baseName(input.fileName) + "-odb.hxx" + fileTags: ["hpp"] + } + Artifact { + // filePath: FileInfo.baseName(input.fileName) + "-odb.cxx" + filePath: { + // console.error("THIS ART "+FileInfo.cleanPath(input.filePath + "/../") + "/" + FileInfo.baseName(input.fileName) + "-odb.cxx") + return FileInfo.cleanPath(input.filePath + "/../") + "/" + FileInfo.baseName(input.fileName) + "-odb.cxx"} + fileTags: ["cpp"] + } + + prepare: { + var cmd = new Command("odb", [ + "--std", "c++11", + "-d", "sqlite", + "--generate-query", + "--generate-schema", + "--generate-prepared", + // "--include-prefix", FileInfo.cleanPath(input.filePath + "/../"), + // "--output-dir", product.Qt.core.qmDir, + "--output-dir", FileInfo.cleanPath(input.filePath + "/../"), + input.filePath, + ]); + cmd.description = "Generating ODB files for " + input.fileName; + + console.error(cmd.arguments) + + return [cmd]; + } + } + cpp.includePaths: [ project.sourceDirectory, product.Qt.core.qmDir ] +} diff --git a/src/author_s.h b/src/author_s.h new file mode 100644 index 0000000..0dc1949 --- /dev/null +++ b/src/author_s.h @@ -0,0 +1,63 @@ +// file : hello/person.hxx +// copyright : not copyrighted - public domain + +#ifndef AUTHOR_S_H +#define AUTHOR_S_H + +#include // std::size_t +#include +#include + +#include +#include +#include + +#pragma db object +class Author_S +{ +public: + Author_S() = default; + + Author_S(const std::string& first, + const std::string& last, + const unsigned short age) : + m_first(first), + m_last(last), + m_age(age) + { + } + + unsigned long long id() const { return m_id; } + std::string first() const { return m_first; } + std::string last() const { return m_last; } + unsigned short age() const { return m_age; } + + std::string full_name() const { return m_first + " " + m_last; } + + void age(unsigned short age) { m_age = age; } + +private: + friend class odb::access; + +#pragma db id auto + unsigned long long m_id; + + std::string m_first; + std::string m_last; + unsigned short m_age; +}; + +#pragma db view object(Author_S) +struct person_stat +{ +#pragma db column("count(" + Author_S::m_id + ")") + std::size_t count; + +#pragma db column("min(" + Author_S::m_age + ")") + unsigned short min_age; + +#pragma db column("max(" + Author_S::m_age + ")") + unsigned short max_age; +}; + +#endif // AUTHOR_S_H diff --git a/src/book_s.h b/src/book_s.h new file mode 100644 index 0000000..bb34c7b --- /dev/null +++ b/src/book_s.h @@ -0,0 +1,46 @@ +// file : hello/person.hxx +// copyright : not copyrighted - public domain + +#ifndef BOOK_S_H +#define BOOK_S_H + +#include +#include + +#include + +#include "author_s.h" + +#pragma db object +class Book_S +{ +public: + Book_S() = default; + + Book_S(const std::string& name, + const std::shared_ptr& author, + const int year) + { + m_author = author; + m_name = name; + m_year = year; + }; + + unsigned long long id() const { return m_id; } + std::shared_ptr author() const { return m_author; } + std::string name() const { return m_name; } + int year() const { return m_year; } + +private: + friend class odb::access; + +private: +#pragma db id auto + unsigned long long m_id; + + std::shared_ptr m_author; + std::string m_name; + int m_year; +}; + +#endif // BOOK_S_H diff --git a/src/cpp-opds.qbs b/src/cpp-opds.qbs new file mode 100644 index 0000000..bea1d78 --- /dev/null +++ b/src/cpp-opds.qbs @@ -0,0 +1,58 @@ +/*! + \qmltype cpp-opds + \inherits Project + \brief Описание +*/ +Application { + // CppApplication { + cpp.defines: [ + // You can make your code fail to compile if it uses deprecated APIs. + // In order to do so, uncomment the following line. + //"QT_DISABLE_DEPRECATED_BEFORE=0x060000" // disables all the APIs deprecated before Qt 6.0.0 + "DATABASE_SQLITE" + ] + consoleApplication: true + + Depends { name: "Qt"; submodules: [ "core", "sql", "network" ] } + Depends { name: "cpp" } + Depends { name: "odbModule"} + + // Depends { name: "redkit_gen" } + + cpp.cxxLanguageVersion: "c++20" + + Group { + name: "cpp" + files: [ + "books.*", + "databasemanager.*", + "main.cpp", + "restapiserver.*", + ] + } + + Group { + name: "odb" + files: [ + "author_s.h", + "book_s.h", + ] + fileTags: ["odb"] + } + + cpp.includePaths: [ + "/usr/include", // Общие заголовки + "/usr/include/odb" // Заголовки ODB + ] + + // Подключаем библиотеки ODB + cpp.libraryPaths: [ + "/usr/lib" // Путь к библиотекам + ] + + cpp.dynamicLibraries: [ + "odb", + "odb-sqlite" + ] + // } +} // Project diff --git a/src/database.hxx b/src/database.hxx new file mode 100644 index 0000000..8961f54 --- /dev/null +++ b/src/database.hxx @@ -0,0 +1,177 @@ +// file : hello/database.hxx +// copyright : not copyrighted - public domain + +// +// Create concrete database instance based on the DATABASE_* macros. +// + +#ifndef DATABASE_HXX +#define DATABASE_HXX + +#include // std::exit +#include +#include // std::unique_ptr +#include + +#include + +#if defined(DATABASE_MYSQL) +#include +#elif defined(DATABASE_SQLITE) +#include +#include +#include +#include +#elif defined(DATABASE_PGSQL) +#include +#elif defined(DATABASE_ORACLE) +#include +#elif defined(DATABASE_MSSQL) +#include +#else +#error unknown database; did you forget to define the DATABASE_* macros? +#endif + +#include +#include + +struct testData +{ + std::string first; + std::string last; + int age; +}; + +// Функция для генерации случайной строки (имени или фамилии) +std::string generate_random_string(const std::vector& pool) +{ + static std::random_device rd; + static std::mt19937 gen(rd()); + std::uniform_int_distribution<> dis(0, pool.size() - 1); + return pool[dis(gen)]; +} + +// Функция для генерации случайного года +int generate_random_year(int min_year = 1900, int max_year = 2020) +{ + static std::random_device rd; + static std::mt19937 gen(rd()); + std::uniform_int_distribution<> dis(min_year, max_year); + return dis(gen); +} + +std::vector fillDB() +{ + std::vector first_names = { + "John", "Jane", "Alex", "Chris", "Robert", "Emily", "James", "Linda", "David", "Sarah", + "Michael", "Elizabeth", "Daniel", "Samantha", "William", "Olivia", "Ethan", "Sophia", "Joshua", "Charlotte", + "Daniel", "Grace", "Benjamin", "Isabella", "Matthew", "Victoria", "Henry", "Abigail", "Samuel", "Megan", + "Lucas", "Lily", "Andrew", "Madison", "Jackson", "Chloe", "Aiden", "Amelia", "Thomas", "Natalie", + "Ryan", "Zoe", "Jack", "Harper", "Elijah", "Ava", "Isaac", "Mia", "Caleb", "Ella" + }; + std::vector last_names = { + "Doe", "Smith", "Johnson", "Williams", "Jones", "Brown", "Davis", "Miller", "Wilson", "Moore", + "Taylor", "Anderson", "Thomas", "Jackson", "White", "Harris", "Martin", "Thompson", "Garcia", "Martinez", + "Roberts", "Clark", "Lewis", "Walker", "Young", "Allen", "King", "Wright", "Scott", "Adams", + "Baker", "Gonzalez", "Nelson", "Carter", "Mitchell", "Perez", "Robinson", "Hughes", "Flores", "Cook", + "Rogers", "Gutierrez", "Ramirez", "Diaz", "Perez", "Ross", "Sanders", "Price", "Howard", "Cooper" + }; + + std::vector vecTest; + + for (int i = 0; i < 50; ++i) + { + std::string first_name = generate_random_string(first_names); + std::string last_name = generate_random_string(last_names); + int birth_year = generate_random_year(1900, 2000); + + vecTest.push_back({ first_name, last_name, birth_year }); + } + + return vecTest; +} + +inline std::unique_ptr openDB(std::string path_db) +{ + // Открыть БД, если она существует + if (std::filesystem::is_regular_file(path_db)) + return std::unique_ptr(new odb::sqlite::database(path_db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE)); + + // И создать новую, если не существует + std::unique_ptr db(new odb::sqlite::database(path_db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE)); + + odb::core::connection_ptr connect_ptr(db->connection()); + + connect_ptr->execute("PRAGMA foreign_keys=OFF"); + + odb::core::transaction tans(db->begin()); + odb::core::schema_catalog::create_schema(*db); + tans.commit(); + + connect_ptr->execute("PRAGMA foreign_keys=ON"); + + return db; +} + +inline std::unique_ptr create_database(int& argc, char* argv[]) +{ + using namespace std; + using namespace odb::core; + + if (argc > 1 && argv[1] == string("--help")) + { + cout << "Usage: " << argv[0] << " [options]" << endl + << "Options:" << endl; + +#if defined(DATABASE_MYSQL) + odb::mysql::database::print_usage(cout); +#elif defined(DATABASE_SQLITE) + odb::sqlite::database::print_usage(cout); +#elif defined(DATABASE_PGSQL) + odb::pgsql::database::print_usage(cout); +#elif defined(DATABASE_ORACLE) + odb::oracle::database::print_usage(cout); +#elif defined(DATABASE_MSSQL) + odb::mssql::database::print_usage(cout); +#endif + + exit(0); + } + +#if defined(DATABASE_MYSQL) + unique_ptr db(new odb::mysql::database(argc, argv)); +#elif defined(DATABASE_SQLITE) + + unique_ptr db(new odb::sqlite::database(argc, + argv, + false, + SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE)); + + // Create the database schema. Due to bugs in SQLite foreign key + // support for DDL statements, we need to temporarily disable + // foreign keys. + // + { + connection_ptr connect_ptr(db->connection()); + + connect_ptr->execute("PRAGMA foreign_keys=OFF"); + + transaction tans(connect_ptr->begin()); + schema_catalog::create_schema(*db); + tans.commit(); + + connect_ptr->execute("PRAGMA foreign_keys=ON"); + } +#elif defined(DATABASE_PGSQL) + unique_ptr db(new odb::pgsql::database(argc, argv)); +#elif defined(DATABASE_ORACLE) + unique_ptr db(new odb::oracle::database(argc, argv)); +#elif defined(DATABASE_MSSQL) + unique_ptr db( + new odb::mssql::database(argc, argv, false, "TrustServerCertificate=yes")); +#endif + + return db; +} + +#endif // DATABASE_HXX diff --git a/src/databasemanager.cpp b/src/databasemanager.cpp new file mode 100644 index 0000000..93dcd2d --- /dev/null +++ b/src/databasemanager.cpp @@ -0,0 +1,118 @@ +#include "databasemanager.h" + +// Singleton instance +DatabaseManager& DatabaseManager::instance() +{ + static DatabaseManager instance; + return instance; +} + +// Конструктор: создаем подключение +DatabaseManager::DatabaseManager() +{ + db = QSqlDatabase::addDatabase("QSQLITE"); + db.setDatabaseName("mydatabase.db"); + + if (!db.open()) + { + qDebug() << "Ошибка открытия БД:" << db.lastError().text(); + } + else + { + qDebug() << "База данных успешно открыта"; + } +} + +// Деструктор: закрываем подключение +DatabaseManager::~DatabaseManager() +{ + if (db.isOpen()) + { + db.close(); + } +} + +// Возвращает ссылку на объект БД +QSqlDatabase& DatabaseManager::database() +{ + return db; +} + +// Выполняет SQL-запрос (без параметров) +bool DatabaseManager::executeQuery(const QString& query) +{ + QSqlQuery q; + if (!q.exec(query)) + { + qDebug() << "Ошибка выполнения запроса:" << q.lastError().text(); + return false; + } + return true; +} + +// Выполняет подготовленный SQL-запрос с параметрами +bool DatabaseManager::executePreparedQuery(const QString& query, const QStringList& values) +{ + QSqlQuery q; + q.prepare(query); + + for (const auto& value : values) + q.addBindValue(value); + + if (!q.exec()) + { + qDebug() << "Ошибка выполнения подготовленного запроса:" << q.lastError().text(); + return false; + } + return true; +} + +QList DatabaseManager::executeSelectQuery(const QString& query, const QStringList& values) +{ + QSqlQuery q; + q.prepare(query); + + for (const auto& value : values) + q.addBindValue(value); + + if (!q.exec()) + { + qDebug() << "Ошибка выполнения SELECT запроса:" << q.lastError().text(); + return {}; + } + + QList results; + while (q.next()) + { + QVariantList row; + for (int i = 0; i < q.record().count(); ++i) + row.append(q.value(i)); + results.append(row); + } + + return results; +} + +QList DatabaseManager::executeSelect(const SelectBuilder& selectBuilder) +{ + const auto queryString = selectBuilder.getSelect(); + qDebug() << "Подготовленный запрос: " << queryString; + + QSqlQuery q; + if (!q.prepare(queryString)) + qDebug() << "Ошибка при подготовке запроса:" << q.lastError().text(); + + if (!q.exec()) + qDebug() << "Ошибка при выполнении запроса:" << q.lastError().text(); + + QList results; + while (q.next()) // Проходимся по строкам + { + QVariantList row; + for (int i = 0; i < q.record().count(); ++i) // Проходимся по столбцам + row.append(q.value(i)); + results.append(row); + } + + return results; +} diff --git a/src/databasemanager.h b/src/databasemanager.h new file mode 100644 index 0000000..f7be5ad --- /dev/null +++ b/src/databasemanager.h @@ -0,0 +1,59 @@ +#ifndef DATABASEMANAGER_H +#define DATABASEMANAGER_H + +#include +#include + +struct SelectBuilder +{ + QString tableName; + QStringList rows = { "*" }; + QString where; + + QString getSelect() const + { + QString allRows; + + auto ri = rows.begin(); + while (ri != rows.end()) + { + allRows += (*ri++); + + if (ri != rows.end()) + allRows += ", "; + } + + QString queryString = QString("SELECT %1").arg(allRows); + + queryString += QString(" FROM %1").arg(tableName); + + if (!where.isEmpty()) + queryString += QString(" WHERE %1").arg(where); + + queryString += ';'; + return queryString; + } +}; + +class DatabaseManager +{ +public: + static DatabaseManager& instance(); // Singleton + + QSqlDatabase& database(); + + bool executeQuery(const QString& query); + bool executePreparedQuery(const QString& query, const QStringList& values); + + QList executeSelectQuery(const QString& query, const QStringList& values = {}); + + QList executeSelect(const SelectBuilder& selectBuilder); + +private: + DatabaseManager(); + ~DatabaseManager(); + + QSqlDatabase db; +}; + +#endif // DATABASEMANAGER_H diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..7d8945b --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,180 @@ +#include + +#include "restapiserver.h" + +/* Опыты с odb */ +#include "author_s-odb.hxx" // Должен быть здесь +#include "author_s.h" + +#include "book_s-odb.hxx" // Должен быть здесь +#include "book_s.h" + +#include + +#include "database.hxx" // create_database + +using unidb = std::unique_ptr; + +void fillBooksBD(unidb& db) +{ + try + { + std::vector> authors = { + std::make_shared("George", "Orwell", 142), + std::make_shared("J.K.", "Rowling", 56), + std::make_shared("J.R.R.", "Tolkien", 81), + std::make_shared("Leo", "Tolstoy", 189), + std::make_shared("Fyodor", "Dostoevsky", 174), + std::make_shared("Mark", "Twain", 183), + std::make_shared("Charles", "Dickens", 208), + std::make_shared("Virginia", "Woolf", 134), + std::make_shared("Ernest", "Hemingway", 122), + std::make_shared("Gabriel", "García Márquez", 98), + std::make_shared("Franz", "Kafka", 100), + std::make_shared("Harper", "Lee", 64), + std::make_shared("William", "Shakespeare", 459), + std::make_shared("Oscar", "Wilde", 155), + std::make_shared("Aldous", "Huxley", 123), + std::make_shared("Jane", "Austen", 210), + std::make_shared("John", "Steinbeck", 116), + std::make_shared("Agatha", "Christie", 124), + std::make_shared("Isaac", "Asimov", 105) + }; + + // Список книг + std::vector books = { + Book_S("1984", authors[0], 1949), + Book_S("Harry Potter and the Philosopher's Stone", authors[1], 1997), + Book_S("The Hobbit", authors[2], 1937), + Book_S("War and Peace", authors[3], 1869), + Book_S("Crime and Punishment", authors[4], 1866), + Book_S("Adventures of Huckleberry Finn", authors[5], 1884), + Book_S("A Tale of Two Cities", authors[6], 1859), + Book_S("Mrs. Dalloway", authors[7], 1925), + Book_S("The Old Man and the Sea", authors[8], 1952), + Book_S("One Hundred Years of Solitude", authors[9], 1967), + Book_S("The Trial", authors[10], 1925), + Book_S("To Kill a Mockingbird", authors[11], 1960), + Book_S("Macbeth", authors[12], 1606), + Book_S("The Picture of Dorian Gray", authors[13], 1890), + Book_S("Brave New World", authors[14], 1932), + Book_S("Pride and Prejudice", authors[15], 1813), + Book_S("The Brothers Karamazov", authors[4], 1880), + Book_S("The Grapes of Wrath", authors[16], 1939), + Book_S("Murder on the Orient Express", authors[17], 1934), + Book_S("I, Robot", authors[18], 1950), + Book_S("Animal Farm", authors[0], 1945), + Book_S("Harry Potter and the Chamber of Secrets", authors[1], 1998), + Book_S("The Lord of the Rings: The Fellowship of the Ring", authors[2], 1954), + Book_S("Anna Karenina", authors[3], 1878), + Book_S("The Idiot", authors[4], 1869), + Book_S("The Adventures of Tom Sawyer", authors[5], 1876), + Book_S("Oliver Twist", authors[6], 1837), + Book_S("To the Lighthouse", authors[7], 1927), + Book_S("For Whom the Bell Tolls", authors[8], 1940), + Book_S("Love in the Time of Cholera", authors[9], 1985), + Book_S("The Metamorphosis", authors[10], 1915), + Book_S("Go Set a Watchman", authors[11], 2015), + Book_S("King Lear", authors[12], 1605), + Book_S("The Importance of Being Earnest", authors[13], 1895), + Book_S("Brave New World Revisited", authors[14], 1958), + Book_S("Emma", authors[15], 1815), + Book_S("The Double", authors[4], 1846), + Book_S("Of Mice and Men", authors[16], 1937), + Book_S("And Then There Were None", authors[17], 1939), + Book_S("The Foundation Trilogy", authors[18], 1951), + Book_S("Down and Out in Paris and London", authors[0], 1933), + Book_S("Harry Potter and the Prisoner of Azkaban", authors[1], 1999), + Book_S("The Lord of the Rings: The Two Towers", authors[2], 1954), + Book_S("War and Peace", authors[3], 1869), + Book_S("The Brothers Karamazov", authors[4], 1880), + Book_S("The Prince and the Pauper", authors[5], 1881), + Book_S("David Copperfield", authors[6], 1850), + Book_S("The Waves", authors[7], 1931), + Book_S("A Farewell to Arms", authors[8], 1929), + Book_S("Chronicle of a Death Foretold", authors[9], 1981) + }; + + // Сохранение авторов в базу данных + { + odb::core::transaction t(db->begin()); + for (auto& author : authors) + db->persist(author.get()); // Используем get() для получения сырого указателя + t.commit(); + } + + // Сохранение книг в базу данных + { + odb::core::transaction t(db->begin()); + for (auto& book : books) + db->persist(book); + t.commit(); + } + + std::cout << "Test data added successfully." << std::endl; + } + catch (const odb::exception& e) + { + std::cerr << "Error adding test data: " << e.what() << std::endl; + } +} + +int main(int argc, char* argv[]) +{ + QCoreApplication a(argc, argv); + + const std::string dbPath = "test_db.sqlite"; + std::unique_ptr db(openDB(dbPath)); + + // fillBooksBD(db); + + RestApiServer server(*db); + server.start(8080); + + /* Опыты с odb */ + + // const std::string dbPath = "test_db.sqlite"; + + // std::vector authors; + + // odb::core::transaction trans(db->begin()); + // for (auto data : fillDB()) + // { + // auto a = Author_S(data.first, data.last, data.age); + // db->persist(a); + // } + // trans.commit(); + + // try + // { + // odb::core::transaction tr(db->begin()); + // odb::query query; + // odb::result result; + + // odb::result r(db->query(odb::query::age >= 2000)); + + // for (const auto& author : r) + // { + // std::cout << "First: " << author.first() << ", Last: " << author.last() << ", Year: " << author.age() << std::endl; + // } + + // tr.commit(); + // } + // catch (const odb::exception& e) + // { + // std::cerr << "ODB error: " << e.what() << std::endl; + // return 1; + // } + // Set up code that uses the Qt event loop here. + // Call a.quit() or a.exit() to quit the application. + // A not very useful example would be including + // #include + // near the top of the file and calling + // QTimer::singleShot(5000, &a, &QCoreApplication::quit); + // which quits the application after 5 seconds. + + // If you do not need a running Qt event loop, remove the call + // to a.exec() or use the Non-Qt Plain C++ Application template. + + return a.exec(); +} diff --git a/src/restapiserver.cpp b/src/restapiserver.cpp new file mode 100644 index 0000000..bdde6dd --- /dev/null +++ b/src/restapiserver.cpp @@ -0,0 +1,129 @@ +#include "restapiserver.h" + +#include +#include +#include +#include + +#include "book_s-odb.hxx" +#include "book_s.h" + +#include +#include +#include + +RestApiServer::RestApiServer(odb::database& db, QObject* parent) : + m_db(db), QTcpServer(parent) {} + +void RestApiServer::start(quint16 port) +{ + if (!this->listen(QHostAddress::Any, port)) + { + qWarning() << "Ошибка запуска сервера!"; + } + else + { + qDebug() << "REST API сервер запущен на порту:" << port; + } +} + +void RestApiServer::incomingConnection(qintptr socketDescriptor) +{ + QTcpSocket* socket = new QTcpSocket(this); + socket->setSocketDescriptor(socketDescriptor); + + connect(socket, &QTcpSocket::readyRead, this, &RestApiServer::handleRequest); + connect(socket, &QTcpSocket::disconnected, socket, &QTcpSocket::deleteLater); +} + +void RestApiServer::handleRequest() +{ + QTcpSocket* socket = qobject_cast(sender()); + if (!socket) + return; + + QString request = QString::fromUtf8(socket->readAll()); + QByteArray response = processRequest(request); + + socket->write(response); + socket->flush(); + socket->disconnectFromHost(); +} + +QByteArray RestApiServer::processRequest(const QString& request) +{ + if (request.startsWith("GET /books/author/")) + { + QString author = request.section(' ', 1, 1).section('/', 3, 3).replace("%20", " "); + + // Books books; + // QList results = books.getBooksByAuthor(author); + + QByteArray jsonResponse = "{ \"books\": ["; + // for (const BookRecord& book : results) + // { + // jsonResponse += QString("{ \"id\": %1, \"title\": \"%2\", \"author\": \"%3\", \"year\": %4 },") + // .arg(book.id) + // .arg(book.title) + // .arg(book.author) + // .arg(book.year) + // .toUtf8(); + // } + // if (results.size() > 0) + // jsonResponse.chop(1); // Убираем последнюю запятую + // jsonResponse += "] }"; + + return "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\n\r\n" + jsonResponse; + } + + if (request.startsWith("GET /books")) + { + odb::transaction t(m_db.begin()); + odb::result res(m_db.query()); + + QJsonArray jArray; + + for (auto it = res.begin(); it != res.end(); ++it) + { + const auto& book = *it; + + QJsonObject j; + j["id"] = QString::number(book.id()); + j["title"] = QString::fromStdString(book.name()); + j["author"] = QString::fromStdString(book.author()->full_name()); + j["year"] = book.year(); + jArray.push_back(j); + } + auto jDoc = QJsonDocument(jArray); + + t.commit(); + + return "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\n\r\n" + jDoc.toJson(QJsonDocument::Indented); + } + + if (request.startsWith("GET /author")) + { + odb::transaction t(m_db.begin()); + odb::result res(m_db.query()); + + QJsonArray jArray; + + for (auto it = res.begin(); it != res.end(); ++it) + { + const auto& author = *it; + + QJsonObject j; + j["id"] = QString::number(author.id()); + j["first"] = QString::fromStdString(author.first()); + j["last"] = QString::fromStdString(author.last()); + j["age"] = author.age(); + jArray.push_back(j); + } + auto jDoc = QJsonDocument(jArray); + + t.commit(); + return "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\n\r\n" + jDoc.toJson(QJsonDocument::Indented); + } + + return "HTTP/1.1 404 Not Found\r\nContent-Type: text/plain\r\n\r\nNot Found"; +} diff --git a/src/restapiserver.h b/src/restapiserver.h new file mode 100644 index 0000000..33232d9 --- /dev/null +++ b/src/restapiserver.h @@ -0,0 +1,28 @@ +#ifndef RESTAPISERVER_H +#define RESTAPISERVER_H + +#include + +#include +#include + +class RestApiServer : public QTcpServer +{ + Q_OBJECT +public: + explicit RestApiServer(odb::core::database& db, QObject* parent = nullptr); + void start(quint16 port = 8080); + +protected: + void incomingConnection(qintptr socketDescriptor) override; + +private slots: + void handleRequest(); + +private: + QByteArray processRequest(const QString& request); + + odb::core::database& m_db; +}; + +#endif // RESTAPISERVER_H diff --git a/test/requests/all_books.http b/test/requests/all_books.http new file mode 100644 index 0000000..c57e9a9 --- /dev/null +++ b/test/requests/all_books.http @@ -0,0 +1 @@ +GET http://localhost:8080/books/ \ No newline at end of file diff --git a/test/requests/autor_books.http b/test/requests/autor_books.http new file mode 100644 index 0000000..710f4b4 --- /dev/null +++ b/test/requests/autor_books.http @@ -0,0 +1 @@ +GET http://localhost:8080/books/author/George%20Orwell \ No newline at end of file -- 2.49.1