#include "profile_repository.h" #include #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")); } void bindProfileFields(QSqlQuery& query, const Profile& profile) { query.addBindValue(profile.name.trimmed()); query.addBindValue(profile.host.trimmed()); query.addBindValue(profile.port); query.addBindValue(profile.username.trimmed()); query.addBindValue(profile.protocol.trimmed()); query.addBindValue(profile.authMode.trimmed()); } Profile profileFromQuery(const QSqlQuery& query) { Profile profile; profile.id = query.value(0).toLongLong(); profile.name = query.value(1).toString(); profile.host = query.value(2).toString(); profile.port = query.value(3).toInt(); profile.username = query.value(4).toString(); profile.protocol = query.value(5).toString(); profile.authMode = query.value(6).toString(); return profile; } bool isProfileValid(const Profile& profile) { return !profile.name.trimmed().isEmpty() && !profile.host.trimmed().isEmpty() && profile.port >= 1 && profile.port <= 65535; } } 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; } QString ProfileRepository::lastError() const { return m_lastError; } std::vector ProfileRepository::listProfiles(const QString& searchQuery) const { std::vector result; if (!QSqlDatabase::contains(m_connectionName)) { return result; } setLastError(QString()); QSqlQuery query(QSqlDatabase::database(m_connectionName)); if (searchQuery.trimmed().isEmpty()) { query.prepare(QStringLiteral( "SELECT id, name, host, port, username, protocol, auth_mode " "FROM profiles " "ORDER BY lower(name) ASC, id ASC")); } else { query.prepare(QStringLiteral( "SELECT id, name, host, port, username, protocol, auth_mode " "FROM profiles " "WHERE lower(name) LIKE lower(?) OR lower(host) LIKE lower(?) " "ORDER BY lower(name) ASC, id ASC")); const QString search = QStringLiteral("%") + searchQuery.trimmed() + QStringLiteral("%"); query.addBindValue(search); query.addBindValue(search); } if (!query.exec()) { setLastError(query.lastError().text()); return result; } while (query.next()) { result.push_back(profileFromQuery(query)); } return result; } std::optional ProfileRepository::getProfile(qint64 id) const { if (!QSqlDatabase::contains(m_connectionName)) { return std::nullopt; } setLastError(QString()); QSqlQuery query(QSqlDatabase::database(m_connectionName)); query.prepare(QStringLiteral( "SELECT id, name, host, port, username, protocol, auth_mode " "FROM profiles WHERE id = ?")); query.addBindValue(id); if (!query.exec()) { setLastError(query.lastError().text()); return std::nullopt; } if (!query.next()) { return std::nullopt; } return profileFromQuery(query); } std::optional ProfileRepository::createProfile(const Profile& profile) const { if (!QSqlDatabase::contains(m_connectionName)) { return std::nullopt; } setLastError(QString()); if (!isProfileValid(profile)) { setLastError(QStringLiteral("Name, host, and a valid port are required.")); return std::nullopt; } QSqlQuery query(QSqlDatabase::database(m_connectionName)); query.prepare(QStringLiteral( "INSERT INTO profiles(name, host, port, username, protocol, auth_mode) " "VALUES (?, ?, ?, ?, ?, ?)")); bindProfileFields(query, profile); if (!query.exec()) { setLastError(query.lastError().text()); return std::nullopt; } Profile created = profile; created.id = query.lastInsertId().toLongLong(); return created; } bool ProfileRepository::updateProfile(const Profile& profile) const { if (!QSqlDatabase::contains(m_connectionName)) { return false; } setLastError(QString()); if (profile.id < 0 || !isProfileValid(profile)) { setLastError(QStringLiteral("Invalid profile data.")); return false; } QSqlQuery query(QSqlDatabase::database(m_connectionName)); query.prepare(QStringLiteral( "UPDATE profiles " "SET name = ?, host = ?, port = ?, username = ?, protocol = ?, auth_mode = ? " "WHERE id = ?")); bindProfileFields(query, profile); query.addBindValue(profile.id); if (!query.exec()) { setLastError(query.lastError().text()); return false; } return query.numRowsAffected() > 0; } bool ProfileRepository::deleteProfile(qint64 id) const { if (!QSqlDatabase::contains(m_connectionName)) { return false; } setLastError(QString()); QSqlQuery query(QSqlDatabase::database(m_connectionName)); query.prepare(QStringLiteral("DELETE FROM profiles WHERE id = ?")); query.addBindValue(id); if (!query.exec()) { setLastError(query.lastError().text()); 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," "host TEXT NOT NULL DEFAULT ''," "port INTEGER NOT NULL DEFAULT 22," "username TEXT NOT NULL DEFAULT ''," "protocol TEXT NOT NULL DEFAULT 'SSH'," "auth_mode TEXT NOT NULL DEFAULT 'Password'" ")")); if (!created) { m_initError = query.lastError().text(); return false; } if (!ensureProfileSchema()) { m_initError = m_lastError; return false; } return true; } bool ProfileRepository::ensureProfileSchema() const { if (!QSqlDatabase::contains(m_connectionName)) { setLastError(QStringLiteral("Database connection missing.")); return false; } QSqlQuery tableInfo(QSqlDatabase::database(m_connectionName)); if (!tableInfo.exec(QStringLiteral("PRAGMA table_info(profiles)"))) { setLastError(tableInfo.lastError().text()); return false; } QSet columns; while (tableInfo.next()) { columns.insert(tableInfo.value(1).toString()); } struct ColumnDef { QString name; QString ddl; }; const std::vector required = { {QStringLiteral("host"), QStringLiteral("ALTER TABLE profiles ADD COLUMN host TEXT NOT NULL DEFAULT ''")}, {QStringLiteral("port"), QStringLiteral("ALTER TABLE profiles ADD COLUMN port INTEGER NOT NULL DEFAULT 22")}, {QStringLiteral("username"), QStringLiteral("ALTER TABLE profiles ADD COLUMN username TEXT NOT NULL DEFAULT ''")}, {QStringLiteral("protocol"), QStringLiteral("ALTER TABLE profiles ADD COLUMN protocol TEXT NOT NULL DEFAULT 'SSH'")}, {QStringLiteral("auth_mode"), QStringLiteral("ALTER TABLE profiles ADD COLUMN auth_mode TEXT NOT NULL DEFAULT 'Password'")}}; for (const ColumnDef& column : required) { if (columns.contains(column.name)) { continue; } QSqlQuery alter(QSqlDatabase::database(m_connectionName)); if (!alter.exec(column.ddl)) { setLastError(alter.lastError().text()); return false; } } setLastError(QString()); return true; } void ProfileRepository::setLastError(const QString& error) const { m_lastError = error; }