diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..84c048a --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/build/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 3f8822d..a1e1956 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,18 +10,20 @@ set(CMAKE_AUTOMOC ON) set(CMAKE_AUTOUIC ON) set(CMAKE_AUTORCC ON) -find_package(Qt6 6.2 REQUIRED COMPONENTS Widgets) +find_package(Qt6 6.2 REQUIRED COMPONENTS Widgets Sql) qt_standard_project_setup() add_executable(orbithub src/main.cpp + src/profile_repository.cpp + src/profile_repository.h src/profiles_window.cpp src/profiles_window.h src/session_window.cpp src/session_window.h ) -target_link_libraries(orbithub PRIVATE Qt6::Widgets) +target_link_libraries(orbithub PRIVATE Qt6::Widgets Qt6::Sql) install(TARGETS orbithub RUNTIME DESTINATION bin) diff --git a/src/profile_repository.cpp b/src/profile_repository.cpp new file mode 100644 index 0000000..59884e3 --- /dev/null +++ b/src/profile_repository.cpp @@ -0,0 +1,163 @@ +#include "profile_repository.h" + +#include +#include +#include +#include +#include +#include + +namespace { +QString buildDatabasePath() +{ + QString appDataPath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); + if (appDataPath.isEmpty()) { + appDataPath = QDir::currentPath(); + } + + QDir dataDir(appDataPath); + dataDir.mkpath(QStringLiteral(".")); + + return dataDir.filePath(QStringLiteral("orbithub_profiles.sqlite")); +} +} + +ProfileRepository::ProfileRepository() : m_connectionName(QStringLiteral("orbithub_main")) +{ + if (!initializeDatabase()) { + QSqlDatabase::removeDatabase(m_connectionName); + } +} + +ProfileRepository::~ProfileRepository() +{ + if (QSqlDatabase::contains(m_connectionName)) { + QSqlDatabase db = QSqlDatabase::database(m_connectionName); + if (db.isOpen()) { + db.close(); + } + } + QSqlDatabase::removeDatabase(m_connectionName); +} + +QString ProfileRepository::initError() const +{ + return m_initError; +} + +std::vector ProfileRepository::listProfiles(const QString& searchQuery) const +{ + std::vector result; + + if (!QSqlDatabase::contains(m_connectionName)) { + return result; + } + + QSqlQuery query(QSqlDatabase::database(m_connectionName)); + if (searchQuery.trimmed().isEmpty()) { + query.prepare(QStringLiteral( + "SELECT id, name FROM profiles ORDER BY lower(name) ASC, id ASC")); + } else { + query.prepare(QStringLiteral( + "SELECT id, name FROM profiles " + "WHERE lower(name) LIKE lower(?) " + "ORDER BY lower(name) ASC, id ASC")); + query.addBindValue(QStringLiteral("%") + searchQuery.trimmed() + QStringLiteral("%")); + } + + if (!query.exec()) { + return result; + } + + while (query.next()) { + result.push_back(Profile{query.value(0).toLongLong(), query.value(1).toString()}); + } + + return result; +} + +std::optional ProfileRepository::createProfile(const QString& name) const +{ + if (!QSqlDatabase::contains(m_connectionName)) { + return std::nullopt; + } + + const QString trimmedName = name.trimmed(); + if (trimmedName.isEmpty()) { + return std::nullopt; + } + + QSqlQuery query(QSqlDatabase::database(m_connectionName)); + query.prepare(QStringLiteral("INSERT INTO profiles(name) VALUES (?)")); + query.addBindValue(trimmedName); + + if (!query.exec()) { + return std::nullopt; + } + + return Profile{query.lastInsertId().toLongLong(), trimmedName}; +} + +bool ProfileRepository::updateProfile(qint64 id, const QString& name) const +{ + if (!QSqlDatabase::contains(m_connectionName)) { + return false; + } + + const QString trimmedName = name.trimmed(); + if (trimmedName.isEmpty()) { + return false; + } + + QSqlQuery query(QSqlDatabase::database(m_connectionName)); + query.prepare(QStringLiteral("UPDATE profiles SET name = ? WHERE id = ?")); + query.addBindValue(trimmedName); + query.addBindValue(id); + + if (!query.exec()) { + return false; + } + + return query.numRowsAffected() > 0; +} + +bool ProfileRepository::deleteProfile(qint64 id) const +{ + if (!QSqlDatabase::contains(m_connectionName)) { + return false; + } + + QSqlQuery query(QSqlDatabase::database(m_connectionName)); + query.prepare(QStringLiteral("DELETE FROM profiles WHERE id = ?")); + query.addBindValue(id); + + if (!query.exec()) { + return false; + } + + return query.numRowsAffected() > 0; +} + +bool ProfileRepository::initializeDatabase() +{ + QSqlDatabase database = QSqlDatabase::addDatabase(QStringLiteral("QSQLITE"), m_connectionName); + database.setDatabaseName(buildDatabasePath()); + + if (!database.open()) { + m_initError = database.lastError().text(); + return false; + } + + QSqlQuery query(database); + const bool created = query.exec(QStringLiteral( + "CREATE TABLE IF NOT EXISTS profiles (" + "id INTEGER PRIMARY KEY AUTOINCREMENT," + "name TEXT NOT NULL UNIQUE" + ")")); + + if (!created) { + m_initError = query.lastError().text(); + } + + return created; +} diff --git a/src/profile_repository.h b/src/profile_repository.h new file mode 100644 index 0000000..7ef5e8f --- /dev/null +++ b/src/profile_repository.h @@ -0,0 +1,35 @@ +#ifndef ORBITHUB_PROFILE_REPOSITORY_H +#define ORBITHUB_PROFILE_REPOSITORY_H + +#include + +#include +#include + +struct Profile +{ + qint64 id; + QString name; +}; + +class ProfileRepository +{ +public: + ProfileRepository(); + ~ProfileRepository(); + + QString initError() const; + + std::vector listProfiles(const QString& searchQuery = QString()) const; + std::optional createProfile(const QString& name) const; + bool updateProfile(qint64 id, const QString& name) const; + bool deleteProfile(qint64 id) const; + +private: + QString m_connectionName; + QString m_initError; + + bool initializeDatabase(); +}; + +#endif