Internal
Public Access
Implement Milestone 2 profile schema, dialog, and connect lifecycle
This commit is contained in:
+183
-36
@@ -1,6 +1,7 @@
|
||||
#include "profile_repository.h"
|
||||
|
||||
#include <QDir>
|
||||
#include <QSet>
|
||||
#include <QSqlDatabase>
|
||||
#include <QSqlError>
|
||||
#include <QSqlQuery>
|
||||
@@ -20,6 +21,35 @@ QString buildDatabasePath()
|
||||
|
||||
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"))
|
||||
@@ -45,6 +75,11 @@ QString ProfileRepository::initError() const
|
||||
return m_initError;
|
||||
}
|
||||
|
||||
QString ProfileRepository::lastError() const
|
||||
{
|
||||
return m_lastError;
|
||||
}
|
||||
|
||||
std::vector<Profile> ProfileRepository::listProfiles(const QString& searchQuery) const
|
||||
{
|
||||
std::vector<Profile> result;
|
||||
@@ -53,68 +88,115 @@ std::vector<Profile> ProfileRepository::listProfiles(const QString& searchQuery)
|
||||
return result;
|
||||
}
|
||||
|
||||
setLastError(QString());
|
||||
|
||||
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"));
|
||||
"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 FROM profiles "
|
||||
"WHERE lower(name) LIKE lower(?) "
|
||||
"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"));
|
||||
query.addBindValue(QStringLiteral("%") + searchQuery.trimmed() + QStringLiteral("%"));
|
||||
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(Profile{query.value(0).toLongLong(), query.value(1).toString()});
|
||||
result.push_back(profileFromQuery(query));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::optional<Profile> ProfileRepository::createProfile(const QString& name) const
|
||||
std::optional<Profile> ProfileRepository::getProfile(qint64 id) const
|
||||
{
|
||||
if (!QSqlDatabase::contains(m_connectionName)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const QString trimmedName = name.trimmed();
|
||||
if (trimmedName.isEmpty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
setLastError(QString());
|
||||
|
||||
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.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<Profile> 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;
|
||||
}
|
||||
|
||||
@@ -127,11 +209,14 @@ bool ProfileRepository::deleteProfile(qint64 id) const
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -152,12 +237,74 @@ bool ProfileRepository::initializeDatabase()
|
||||
const bool created = query.exec(QStringLiteral(
|
||||
"CREATE TABLE IF NOT EXISTS profiles ("
|
||||
"id INTEGER PRIMARY KEY AUTOINCREMENT,"
|
||||
"name TEXT NOT NULL UNIQUE"
|
||||
"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;
|
||||
}
|
||||
|
||||
return created;
|
||||
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<QString> columns;
|
||||
while (tableInfo.next()) {
|
||||
columns.insert(tableInfo.value(1).toString());
|
||||
}
|
||||
|
||||
struct ColumnDef {
|
||||
QString name;
|
||||
QString ddl;
|
||||
};
|
||||
|
||||
const std::vector<ColumnDef> 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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user