diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..20cc13f --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "redkbuild"] + path = redkbuild + url = git@gitea.redkit-lab.work:redkit-lab/redkbuild.git diff --git a/project.qbs b/project.qbs index 11f7f19..f857caf 100644 --- a/project.qbs +++ b/project.qbs @@ -3,17 +3,16 @@ 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", + "redkbuild" ] references: [ "src/cpp-opds.qbs", + "src/database/database.qbs", + "src/restapi/restapi.qbs", + "src/model/model.qbs", + "src/repositories/repositories.qbs", + "src/services/services.qbs", + "src/sql_builder/sql_builder.qbs", ] } diff --git a/qbs/modules/odbModule/odbModule.qbs b/qbs/modules/odbModule/odbModule.qbs deleted file mode 100644 index 0cd4980..0000000 --- a/qbs/modules/odbModule/odbModule.qbs +++ /dev/null @@ -1,48 +0,0 @@ -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/redkbuild b/redkbuild new file mode 160000 index 0000000..9f3a81c --- /dev/null +++ b/redkbuild @@ -0,0 +1 @@ +Subproject commit 9f3a81cc2386d6f8653b29f1f17bc0c5e03dad62 diff --git a/src/author_s.h b/src/author_s.h deleted file mode 100644 index 0dc1949..0000000 --- a/src/author_s.h +++ /dev/null @@ -1,63 +0,0 @@ -// 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 deleted file mode 100644 index bb34c7b..0000000 --- a/src/book_s.h +++ /dev/null @@ -1,46 +0,0 @@ -// 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 index bea1d78..a5e1f25 100644 --- a/src/cpp-opds.qbs +++ b/src/cpp-opds.qbs @@ -1,58 +1,27 @@ /*! \qmltype cpp-opds - \inherits Project + \inherits Application \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" +PSApplication { + 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: "Qt"; submodules: ["core", "sql", "network"] } + Depends { name: "cpp" } + Depends { name: "database" } + Depends { name: "services" } + Depends { name: "restapi" } + + Group { + name: "cpp" + files: [ + "main.cpp", ] - 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 + } +} // Application diff --git a/src/database.hxx b/src/database.hxx deleted file mode 100644 index 8961f54..0000000 --- a/src/database.hxx +++ /dev/null @@ -1,177 +0,0 @@ -// 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/database/database.qbs b/src/database/database.qbs new file mode 100644 index 0000000..3fe9dce --- /dev/null +++ b/src/database/database.qbs @@ -0,0 +1,16 @@ +import qbs + +PSLibrary { + Depends { name: "Qt"; submodules: "sql"} + + Depends { name: "model" } + Depends { name: "sql_builder" } + + Group { + name: "cpp" + files: [ + "**/*.h", + "**/*.cpp" + ] + } +} diff --git a/src/database/databasemanager.cpp b/src/database/databasemanager.cpp new file mode 100644 index 0000000..21bd43d --- /dev/null +++ b/src/database/databasemanager.cpp @@ -0,0 +1,38 @@ +#include "databasemanager.h" + +#include +#include + +DatabaseManager::DatabaseManager() {} + +DatabaseManager::~DatabaseManager() +{ + if (db.isOpen()) + db.close(); +} + +DatabaseManager& DatabaseManager::instance() +{ + static DatabaseManager instance; + return instance; +} + +bool DatabaseManager::connect(const QString& dbPath) +{ + db = QSqlDatabase::addDatabase("QSQLITE"); + db.setDatabaseName(dbPath); + return db.open(); +} + +QSqlDatabase& DatabaseManager::database() +{ + return db; +} + +void DatabaseManager::initializeDatabase() +{ + QSqlQuery query; + + query.exec(model::createTableBook()); + query.exec(model::createTableAuthor()); +} diff --git a/src/database/databasemanager.h b/src/database/databasemanager.h new file mode 100644 index 0000000..00ca359 --- /dev/null +++ b/src/database/databasemanager.h @@ -0,0 +1,21 @@ +#ifndef DATABASEMANAGER_H +#define DATABASEMANAGER_H + +#include +#include + +class Q_DECL_EXPORT DatabaseManager +{ +public: + static DatabaseManager& instance(); + bool connect(const QString& dbPath); + QSqlDatabase& database(); + void initializeDatabase(); + +private: + DatabaseManager(); + ~DatabaseManager(); + QSqlDatabase db; +}; + +#endif // DATABASEMANAGER_H diff --git a/src/databasemanager.cpp b/src/databasemanager.cpp deleted file mode 100644 index 93dcd2d..0000000 --- a/src/databasemanager.cpp +++ /dev/null @@ -1,118 +0,0 @@ -#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 deleted file mode 100644 index f7be5ad..0000000 --- a/src/databasemanager.h +++ /dev/null @@ -1,59 +0,0 @@ -#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 index 7d8945b..06cbde0 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,170 +1,55 @@ #include -#include "restapiserver.h" +#include -/* Опыты с odb */ -#include "author_s-odb.hxx" // Должен быть здесь -#include "author_s.h" +#include -#include "book_s-odb.hxx" // Должен быть здесь -#include "book_s.h" +#include +#include -#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; - } -} +#include int main(int argc, char* argv[]) { QCoreApplication a(argc, argv); - const std::string dbPath = "test_db.sqlite"; - std::unique_ptr db(openDB(dbPath)); + if (!DatabaseManager::instance().connect("library.db")) + { + qCritical() << "Ошибка: не удалось подключиться к базе данных!"; + return -1; + } - // fillBooksBD(db); + DatabaseManager::instance().initializeDatabase(); - RestApiServer server(*db); + if (BookService::add({ .title = "Война и мир", + .author = { .name = "Лев Толстой" }, + .filePath = "/books/war_and_peace.epub" })) + { + qDebug() << "Книга успешно добавлена!"; + } + else + { + qCritical() << "Ошибка при добавлении книги!"; + } + + QVector books = BookService::fetchAll(); + + for (const model::Book& book : books) + { + qDebug() << "ID:" << book.id << "Название:" << book.title + << "Автор:" << book.author.name << "Файл:" << book.filePath; + } + + QVector authors = AuthorService::fetchAll(); + + for (const model::Author& author : authors) + { + qDebug() << "ID:" << author.id << "Имя:" << author.name; + } + + RestApiServer server; 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 diff --git a/src/model/author.h b/src/model/author.h new file mode 100644 index 0000000..dd5713f --- /dev/null +++ b/src/model/author.h @@ -0,0 +1,56 @@ +#ifndef AUTHOR_H +#define AUTHOR_H + +#include + +#include + +namespace model +{ + +namespace authorconst +{ + +const QString TABLE = "authors"; + +const QString ID = "id"; +const QString NAME = "name"; + +// const QString M_AUTHOR_ID = "id"; +// const QString M_AUTHOR_FIRST_NAME = "first_name"; +// const QString M_AUTHOR_LAST_NAME = "last_name"; +// const QString M_AUTHOR_YEAR = "year"; + +} + +struct Author +{ + int id = 0; + QString name; + // QString firstName; + // QString lastName; + // QString birthdayYear; +}; + +static inline QString createTableAuthor() +{ + // query.exec("CREATE TABLE IF NOT EXISTS authors (" + // "id INTEGER PRIMARY KEY AUTOINCREMENT," + // "name TEXT UNIQUE NOT NULL" + // ")"); + + Builder::TableSchema tBuilder(model::authorconst::TABLE); + tBuilder.addColumn({ .name = model::authorconst::ID, + .type = "INTEGER", + .primaryKey = true }); + + tBuilder.addColumn({ .name = model::authorconst::NAME, + .type = "TEXT", + .notNull = true, + .unique = true }); + return tBuilder.get(); +} + +} // namespace model + +#endif // AUTHOR_H diff --git a/src/model/book.h b/src/model/book.h new file mode 100644 index 0000000..49a7435 --- /dev/null +++ b/src/model/book.h @@ -0,0 +1,65 @@ +#ifndef BOOK_H +#define BOOK_H + +#include + +#include + +#include + +namespace model +{ + +namespace bookconst +{ + +const QString TABLE = "books"; + +const QString ID = "id"; +const QString TITLE = "title"; +const QString AUTHOR_ID = "author_id"; +const QString FILEPATH = "file_path"; + +} + +struct Book +{ + int id = 0; + QString title; + Author author; + QString filePath; +}; + +static inline QString createTableBook() +{ + // query.exec("CREATE TABLE IF NOT EXISTS books (" + // "id INTEGER PRIMARY KEY AUTOINCREMENT," + // "title TEXT NOT NULL," + // "file_path TEXT NOT NULL," + // "author_id INTEGER NOT NULL," + // "FOREIGN KEY (author_id) REFERENCES authors(id) ON DELETE CASCADE" + // ");"); + + Builder::TableSchema tBuilder(model::bookconst::TABLE); + tBuilder.addColumn({ .name = model::bookconst::ID, + .type = "INTEGER", + .primaryKey = true }); + + tBuilder.addColumn({ .name = model::bookconst::TITLE, + .type = "TEXT", + .notNull = true }); + + tBuilder.addColumn({ .name = model::bookconst::FILEPATH, + .type = "TEXT", + .notNull = true }); + + tBuilder.addColumn({ .name = model::bookconst::AUTHOR_ID, + .type = "INTEGER", + .notNull = true, + .foreignKeyTable = model::authorconst::TABLE, + .foreignKeyColumn = model::authorconst::ID }); + return tBuilder.get(); +} + +} // namespace model +#endif // BOOK_H diff --git a/src/model/model.qbs b/src/model/model.qbs new file mode 100644 index 0000000..37ec88a --- /dev/null +++ b/src/model/model.qbs @@ -0,0 +1,15 @@ +import qbs + +PSLibrary { + Depends { name: "Qt"; submodules: "core", "sql"} + + Depends { name: "sql_builder" } + + Group { + name: "cpp" + files: [ + "*.h", + "*.cpp" + ] + } +} diff --git a/src/repositories/AuthorRepository.cpp b/src/repositories/AuthorRepository.cpp new file mode 100644 index 0000000..c25725f --- /dev/null +++ b/src/repositories/AuthorRepository.cpp @@ -0,0 +1,78 @@ +// #define BUILD_REPOSITORIES +#include "AuthorRepository.h" + +#include +#include + +#include + +QVector AuthorRepository::getAll() +{ + QVector authors; + + Builder::Select sBuilder({ .tableName = model::authorconst::TABLE, .rows = { model::authorconst::ID, model::authorconst::NAME } }); + QSqlQuery query(sBuilder.get()); + + qDebug() << query.lastError().text(); + + while (query.next()) + { + authors.append({ query.value(0).toInt(), + query.value(1).toString() }); + } + + return authors; +} + +std::optional AuthorRepository::getByName(const QString& name) +{ + Q_UNUSED(name); + Builder::Select sBuilder({ .tableName = model::authorconst::TABLE, .rows = { model::authorconst::ID, model::authorconst::NAME } }); + QString qResult = sBuilder.get() + QString(" WHERE %1 = \"%2\"").arg(model::authorconst::NAME).arg(name); + + QSqlQuery query(qResult); + + if (query.next()) + { + model::Author author({ .id = query.value(0).toInt(), + .name = query.value(1).toString() }); + return { author }; + } + + return std::nullopt; +} + +bool AuthorRepository::insert(const model::Author& author) +{ + QSqlQuery query(DatabaseManager::instance().database()); + Builder::Insert iBuild({ model::authorconst::TABLE, + { model::authorconst::NAME }, + true }); + + query.prepare(iBuild.get()); + query.bindValue(":name", author.name); + int authorId = 0; + Q_UNUSED(authorId); + + if (!query.exec()) + { + qDebug() << query.lastError().text(); + if (query.lastError().nativeErrorCode() == 19) + { + // Автор уже есть, ищем его ID + QSqlQuery findQuery(DatabaseManager::instance().database()); + findQuery.prepare("SELECT id FROM authors WHERE name = :name"); + findQuery.bindValue(":name", author.name); + if (findQuery.exec() && findQuery.next()) + { + authorId = findQuery.value(0).toInt(); + return true; + } + } + qCritical() << "Ошибка добавления автора:" << query.lastError().text(); + return false; + } + + authorId = query.lastInsertId().toInt(); + return true; +} diff --git a/src/repositories/AuthorRepository.h b/src/repositories/AuthorRepository.h new file mode 100644 index 0000000..4cfaa75 --- /dev/null +++ b/src/repositories/AuthorRepository.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#ifdef BUILD_REPOSITORIES +#define REPOSITORIES_EXPORT Q_DECL_EXPORT +#else +#define REPOSITORIES_EXPORT Q_DECL_IMPORT +#endif + +class REPOSITORIES_EXPORT AuthorRepository +{ +public: + static QVector getAll(); + static std::optional getByName(const QString& name); + static bool insert(const model::Author& author); + + // TODO Возвращать не bool - а номер id записи +}; diff --git a/src/repositories/BookRepository.cpp b/src/repositories/BookRepository.cpp new file mode 100644 index 0000000..9c94ddc --- /dev/null +++ b/src/repositories/BookRepository.cpp @@ -0,0 +1,59 @@ +// #define BUILD_REPOSITORIES +#include "BookRepository.h" + +#include +#include + +#include +#include + +QVector BookRepository::getAll() +{ + QVector books; + Builder::Select qBuilder { + model::bookconst::TABLE, + { model::bookconst::ID, + model::bookconst::TITLE, + model::bookconst::AUTHOR_ID, + model::bookconst::FILEPATH } + }; + + QSqlQuery query(qBuilder.get()); + + qDebug() << query.lastError().text(); + + while (query.next()) + { + books.append({ query.value(0).toInt(), + query.value(1).toString(), + { query.value(2).toInt() }, + query.value(3).toString() }); + } + + // TODO получить так же авторов как строку + + return books; +} + +bool BookRepository::insert(const model::Book& book) +{ + QSqlQuery query(DatabaseManager::instance().database()); + + Builder::Insert iBuilder { + model::bookconst::TABLE, + { model::bookconst::TITLE, + model::bookconst::FILEPATH, + model::bookconst::AUTHOR_ID } + }; + + query.prepare(iBuilder.get()); + query.bindValue(":" + model::bookconst::TITLE, book.title); + query.bindValue(":" + model::bookconst::FILEPATH, book.filePath); + query.bindValue(":" + model::bookconst::AUTHOR_ID, book.author.id); + + bool res = query.exec(); + if (!res) + qDebug() << query.lastError().text(); + + return res; +} diff --git a/src/repositories/BookRepository.h b/src/repositories/BookRepository.h new file mode 100644 index 0000000..2454a5f --- /dev/null +++ b/src/repositories/BookRepository.h @@ -0,0 +1,22 @@ +#pragma once + +#include + +#include + +#include +#include +#include + +#ifdef BUILD_REPOSITORIES +#define REPOSITORIES_EXPORT Q_DECL_EXPORT +#else +#define REPOSITORIES_EXPORT Q_DECL_IMPORT +#endif + +class REPOSITORIES_EXPORT BookRepository +{ +public: + static QVector getAll(); + static bool insert(const model::Book& book); +}; diff --git a/src/repositories/repositories.qbs b/src/repositories/repositories.qbs new file mode 100644 index 0000000..877e100 --- /dev/null +++ b/src/repositories/repositories.qbs @@ -0,0 +1,21 @@ +import qbs + +PSLibrary { + Depends { name: "Qt"; submodules: "core", "sql"} + + Depends { name: "database" } + Depends { name: "sql_builder" } + + Group { + name: "cpp" + files: [ + "*.h", + "*.cpp" + ] + } + + Properties { + condition: qbs.buildVariant === "debug" + cpp.defines: ["BUILD_REPOSITORIES"] + } +} diff --git a/src/restapi/restapi.qbs b/src/restapi/restapi.qbs new file mode 100644 index 0000000..d7efb54 --- /dev/null +++ b/src/restapi/restapi.qbs @@ -0,0 +1,15 @@ +import qbs + +PSLibrary { + Depends { name: "Qt"; submodules: ["core", "sql", "network"] } + + Depends { name: "services" } + + Group { + name: "cpp" + files: [ + "*.h", + "*.cpp" + ] + } +} diff --git a/src/restapiserver.cpp b/src/restapi/restapiserver.cpp similarity index 73% rename from src/restapiserver.cpp rename to src/restapi/restapiserver.cpp index bdde6dd..d00ac71 100644 --- a/src/restapiserver.cpp +++ b/src/restapi/restapiserver.cpp @@ -1,19 +1,15 @@ #include "restapiserver.h" +#include +#include + #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) {} +RestApiServer::RestApiServer(QObject* parent) : + QTcpServer(parent) {} void RestApiServer::start(quint16 port) { @@ -24,6 +20,7 @@ void RestApiServer::start(quint16 port) else { qDebug() << "REST API сервер запущен на порту:" << port; + qDebug() << QString("http://127.0.0.1:%1").arg(port); } } @@ -78,50 +75,45 @@ QByteArray RestApiServer::processRequest(const QString& request) if (request.startsWith("GET /books")) { - odb::transaction t(m_db.begin()); - odb::result res(m_db.query()); + auto books = BookService::fetchAll(); QJsonArray jArray; - for (auto it = res.begin(); it != res.end(); ++it) + for (auto it = books.begin(); it != books.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(); + j["id"] = QString::number(book.id); + j["title"] = QString(book.title); + j["author"] = QString(book.author.name); + j["local_path"] = book.filePath; 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()); + // auto books = BookService::fetchAll(); + auto authors = AuthorService::fetchAll(); QJsonArray jArray; - - for (auto it = res.begin(); it != res.end(); ++it) + for (auto it = authors.begin(); it != authors.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(); + j["id"] = QString::number(author.id); + j["name"] = QString(author.name); + // 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); } diff --git a/src/restapiserver.h b/src/restapi/restapiserver.h similarity index 66% rename from src/restapiserver.h rename to src/restapi/restapiserver.h index 33232d9..5f8998b 100644 --- a/src/restapiserver.h +++ b/src/restapi/restapiserver.h @@ -1,16 +1,14 @@ #ifndef RESTAPISERVER_H #define RESTAPISERVER_H -#include - #include #include -class RestApiServer : public QTcpServer +class Q_DECL_EXPORT RestApiServer : public QTcpServer { Q_OBJECT public: - explicit RestApiServer(odb::core::database& db, QObject* parent = nullptr); + explicit RestApiServer(QObject* parent = nullptr); void start(quint16 port = 8080); protected: @@ -21,8 +19,6 @@ private slots: private: QByteArray processRequest(const QString& request); - - odb::core::database& m_db; }; #endif // RESTAPISERVER_H diff --git a/src/services/AuthorService.cpp b/src/services/AuthorService.cpp new file mode 100644 index 0000000..adb5f8e --- /dev/null +++ b/src/services/AuthorService.cpp @@ -0,0 +1,12 @@ +// #define BUILD_SERVICES +#include "AuthorService.h" + +QVector AuthorService::fetchAll() +{ + return AuthorRepository::getAll(); +} + +bool AuthorService::add(const model::Author& author) +{ + return AuthorRepository::insert(author); +} diff --git a/src/services/AuthorService.h b/src/services/AuthorService.h new file mode 100644 index 0000000..cc518ab --- /dev/null +++ b/src/services/AuthorService.h @@ -0,0 +1,20 @@ +#pragma once + +#include + +#include + +#include + +#ifdef BUILD_SERVICES +#define SERVICES_EXPORT Q_DECL_EXPORT +#else +#define SERVICES_EXPORT Q_DECL_IMPORT +#endif + +class SERVICES_EXPORT AuthorService +{ +public: + static QVector fetchAll(); + static bool add(const model::Author& name); +}; diff --git a/src/services/BookService.cpp b/src/services/BookService.cpp new file mode 100644 index 0000000..105dee0 --- /dev/null +++ b/src/services/BookService.cpp @@ -0,0 +1,28 @@ +// #define BUILD_SERVICES +#include "BookService.h" +#include "AuthorService.h" + +QVector BookService::fetchAll() +{ + return BookRepository::getAll(); +} + +bool BookService::add(const model::Book& book) +{ + auto res = AuthorRepository::getByName(book.author.name); + + if (!res.has_value()) + { + if (!AuthorService::add(book.author)) + { + qCritical() << "Ошибка добавления автора!"; + return false; + } + + res = AuthorRepository::getByName(book.author.name); + } + + model::Book BookAuthor = book; + BookAuthor.author = res.value(); + return BookRepository::insert(BookAuthor); +} diff --git a/src/services/BookService.h b/src/services/BookService.h new file mode 100644 index 0000000..b051c99 --- /dev/null +++ b/src/services/BookService.h @@ -0,0 +1,20 @@ +#pragma once + +#include + +#include + +#include + +#ifdef BUILD_SERVICES +#define SERVICES_EXPORT Q_DECL_EXPORT +#else +#define SERVICES_EXPORT Q_DECL_IMPORT +#endif + +class SERVICES_EXPORT BookService +{ +public: + static QVector fetchAll(); + static bool add(const model::Book& book); +}; diff --git a/src/services/services.qbs b/src/services/services.qbs new file mode 100644 index 0000000..60fecf4 --- /dev/null +++ b/src/services/services.qbs @@ -0,0 +1,22 @@ +import qbs + +PSLibrary { + Depends { name: "Qt"; submodules: "core", "sql"} + + Depends { name: "repositories" } + + Group { + name: "cpp" + files: [ + "*.h", + "*.cpp" + ] + } + + + + Properties { + condition: qbs.buildVariant === "debug" + cpp.defines: ["BUILD_SERVICES"] + } +} diff --git a/src/sql_builder/create_table.cpp b/src/sql_builder/create_table.cpp new file mode 100644 index 0000000..82c5884 --- /dev/null +++ b/src/sql_builder/create_table.cpp @@ -0,0 +1,45 @@ +#include "create_table.h" + +#include + +namespace Builder +{ + +QString Column::toSQL() const +{ + QString columnDef = name + " " + type; + if (primaryKey) + columnDef += " PRIMARY KEY"; + if (notNull) + columnDef += " NOT NULL"; + if (unique) + columnDef += " UNIQUE"; + if (defaultValue) + columnDef += " DEFAULT " + *defaultValue; + if (foreignKeyTable && foreignKeyColumn) + { + columnDef += " REFERENCES " + *foreignKeyTable + "(" + *foreignKeyColumn + ")"; + } + return columnDef; +} + +TableSchema& TableSchema::addColumn(const Column& column) +{ + columns.push_back(column); + return *this; +} + +QString TableSchema::get() const +{ + QStringList columnDefs; + for (const auto& col : columns) + { + columnDefs.append(col.toSQL()); + } + + QString result = "CREATE TABLE IF NOT EXISTS " + name + " ( " + columnDefs.join(", ") + " );"; + // qDebug() << result; + return result; +} + +} // namespace Builder diff --git a/src/sql_builder/create_table.h b/src/sql_builder/create_table.h new file mode 100644 index 0000000..239c40c --- /dev/null +++ b/src/sql_builder/create_table.h @@ -0,0 +1,50 @@ +#pragma once + +#include +#include +#include + +#include + +namespace Builder +{ + +struct Column +{ + QString name; // Имя колонки + QString type; // Тип колонки + bool primaryKey = false; // Является ли колонка первичным ключом? + bool notNull = false; // Колонка не должна быть пуста + bool unique = false; // Значение в колонке должны быть уникальны + std::optional defaultValue; + std::optional foreignKeyTable; + std::optional foreignKeyColumn; + + /*! + * \brief Сформировать SQL код для колонки + */ + QString toSQL() const; +}; + +class Q_DECL_EXPORT TableSchema +{ +public: + TableSchema(QString tableName) : + name(std::move(tableName)) {} + + /*! + * \brief Добавить колонку + */ + TableSchema& addColumn(const Column& column); + + /*! + * \brief Сформировать SQL код для создания таблицы + */ + QString get() const; + +private: + QString name; // Имя таблицы + QVector columns; // Список колонок таблицы +}; + +} // namespace Builder diff --git a/src/sql_builder/insert.cpp b/src/sql_builder/insert.cpp new file mode 100644 index 0000000..80384b3 --- /dev/null +++ b/src/sql_builder/insert.cpp @@ -0,0 +1,39 @@ +#include "insert.h" + +#include + +namespace Builder +{ + +namespace +{ + +const QString INSERT = QStringLiteral("INSERT"); +const QString INTO = QStringLiteral("INTO"); +const QString VALUES = QStringLiteral("VALUES"); +const QString IGNORE = QStringLiteral("IGNORE"); + +} + +QString Insert::get() const +{ + QString resultString = QString("%1 ").arg(INSERT); + if (orIgnore) + resultString += QString("OR %1 ").arg(IGNORE); + resultString += QString("%1 %2 ").arg(INTO).arg(tableName); + + resultString += QString("( %1 ) ").arg(rows.join(", ")); + + // TODO как будто бы проще сразу это заполнить значениями + QStringList allValue; + for (const auto& row : rows) + allValue.push_back(QString(":%1").arg(row)); + + resultString += QString("%1 ( %2 )").arg(VALUES).arg(allValue.join(", ")); + + resultString += ';'; + // qDebug() << resultString; + return resultString; +} + +} // namespace Builder diff --git a/src/sql_builder/insert.h b/src/sql_builder/insert.h new file mode 100644 index 0000000..b53cec1 --- /dev/null +++ b/src/sql_builder/insert.h @@ -0,0 +1,22 @@ +#pragma once + +#include +#include + +namespace Builder +{ + +struct Q_DECL_EXPORT Insert +{ + QString tableName; // Имя таблицы + QStringList rows; // Именования колонок + // QStringList params; + bool orIgnore = false; + + /*! + * \brief Сформировать SQL код для выполнения + */ + QString get() const; +}; + +} // namespace Builder diff --git a/src/sql_builder/select.cpp b/src/sql_builder/select.cpp new file mode 100644 index 0000000..880609e --- /dev/null +++ b/src/sql_builder/select.cpp @@ -0,0 +1,32 @@ +#include "select.h" + +#include + +namespace Builder +{ + +namespace +{ + +const QString SELECT = QStringLiteral("SELECT"); +const QString FROM = QStringLiteral("FROM"); +const QString WHERE = QStringLiteral("WHERE"); + +} + +QString Select::get() const +{ + QString allRows = rows.join(", "); + + QString resultString = QString("%1 %2").arg(SELECT).arg(allRows); + + resultString += QString(" %1 %2").arg(FROM).arg(tableName); + + if (!where.isEmpty()) + resultString += QString(" %1 %2").arg(WHERE).arg(where); + + // resultString += ';'; + return resultString; +} + +} // namespace Builder diff --git a/src/sql_builder/select.h b/src/sql_builder/select.h new file mode 100644 index 0000000..a3fe574 --- /dev/null +++ b/src/sql_builder/select.h @@ -0,0 +1,21 @@ +#pragma once + +#include +#include + +namespace Builder +{ + +struct Q_DECL_EXPORT Select +{ + QString tableName; // Имя таблицы + QStringList rows = { "*" }; // Колонки + QString where = {}; // Условие поиска + + /*! + * \brief Сформировать SQL код для выполнения + */ + QString get() const; +}; + +} // namespace Builder diff --git a/src/sql_builder/sql_builder.qbs b/src/sql_builder/sql_builder.qbs new file mode 100644 index 0000000..d03417d --- /dev/null +++ b/src/sql_builder/sql_builder.qbs @@ -0,0 +1,14 @@ +import qbs + +PSLibrary { + Depends { name: "Qt"; submodules: "sql"} + + + Group { + name: "cpp" + files: [ + "**/*.h", + "**/*.cpp" + ] + } +}