feat: complete settings view + signatures + signatures DAO + icons

- SettingsView: complete UI with tabs for Accounts, Categories, Rules, Templates, Signatures, General, Appearance
- Categories: QTreeWidget with hierarchical view, color picker, parent categories
- Rules: QListWidget with RuleEditorDialog integration, toggle enabled
- Templates: QListWidget with TemplateEditorDialog integration
- Signatures: QListWidget with SignatureEditorDialog integration
- SignatureDao: full CRUD with default signature support
- common_structs.h: QColor for Category, all structs centralized
- icons.qrc: updated with Linear/Outline/Bold icons from resources
- Category: QColor instead of QString for color
- EmailTreeModel/DateGroupProxyModel: email grouping by date
- MailListView: QTreeView with date grouping combo
- ReaderView: subject frame with shadow, avatar in header, body inside header
- MainWindow: frameless with rounded corners, 1px border, edge resize, no status bar
- icons.qrc: complete icon set from resources/icons/SVG
This commit is contained in:
2026-08-24 10:29:13 +02:00
parent 2281b01f49
commit fdef311257
12 changed files with 1406 additions and 310 deletions
+107 -112
View File
@@ -10,19 +10,20 @@ bool CategoryDao::insert(Category& cat)
QSqlQuery query(db);
query.prepare(
"INSERT INTO Category (accountId, name, color, parentCategoryId, sortOrder, createdAt) "
"VALUES (:accountId, :name, :color, :parentCategoryId, :sortOrder, :createdAt)"
);
"VALUES (:accountId, :name, :color, :parentCategoryId, :sortOrder, :createdAt)");
query.bindValue(":accountId", cat.accountId >= 0 ? cat.accountId : QVariant());
query.bindValue(":name", cat.name);
query.bindValue(":color", cat.color.name());
query.bindValue(":color", cat.color.name(QColor::HexArgb));
query.bindValue(":parentCategoryId", cat.parentCategoryId >= 0 ? cat.parentCategoryId : QVariant());
query.bindValue(":sortOrder", cat.sortOrder);
query.bindValue(":createdAt", QDateTime::currentDateTime());
query.bindValue(":createdAt", QDateTime::currentDateTimeUtc().toString(Qt::ISODate));
if (!query.exec()) {
qWarning() << "Failed to insert category:" << query.lastError().text();
qDebug() << "[CategoryDao] Insert failed:" << query.lastError().text();
return false;
}
cat.id = query.lastInsertId().toLongLong();
return true;
}
@@ -32,26 +33,23 @@ bool CategoryDao::update(const Category& cat)
QSqlDatabase db = DatabaseManager::instance().database();
QSqlQuery query(db);
query.prepare(
"UPDATE Category SET "
"accountId = :accountId, "
"name = :name, "
"color = :color, "
"parentCategoryId = :parentCategoryId, "
"sortOrder = :sortOrder "
"WHERE id = :id"
);
"UPDATE Category SET accountId = :accountId, name = :name, color = :color, "
"parentCategoryId = :parentCategoryId, sortOrder = :sortOrder "
"WHERE id = :id");
query.bindValue(":id", cat.id);
query.bindValue(":accountId", cat.accountId >= 0 ? cat.accountId : QVariant());
query.bindValue(":name", cat.name);
query.bindValue(":color", cat.color.name()); // Use default hex format
query.bindValue(":color", cat.color.name(QColor::HexArgb));
query.bindValue(":parentCategoryId", cat.parentCategoryId >= 0 ? cat.parentCategoryId : QVariant());
query.bindValue(":sortOrder", cat.sortOrder);
if (!query.exec()) {
qWarning() << "Failed to update category:" << query.lastError().text();
qDebug() << "[CategoryDao] Update failed:" << query.lastError().text();
return false;
}
return query.numRowsAffected() > 0;
return true;
}
bool CategoryDao::remove(qint64 id)
@@ -60,12 +58,13 @@ bool CategoryDao::remove(qint64 id)
QSqlQuery query(db);
query.prepare("DELETE FROM Category WHERE id = :id");
query.bindValue(":id", id);
if (!query.exec()) {
qWarning() << "Failed to delete category:" << query.lastError().text();
qDebug() << "[CategoryDao] Remove failed:" << query.lastError().text();
return false;
}
return query.numRowsAffected() > 0;
return true;
}
std::optional<Category> CategoryDao::findById(qint64 id)
@@ -74,19 +73,20 @@ std::optional<Category> CategoryDao::findById(qint64 id)
QSqlQuery query(db);
query.prepare("SELECT id, accountId, name, color, parentCategoryId, sortOrder, createdAt FROM Category WHERE id = :id");
query.bindValue(":id", id);
if (!query.exec() || !query.next()) {
return std::nullopt;
}
Category cat;
cat.id = query.value(0).toLongLong();
cat.accountId = query.value(1).toLongLong();
cat.name = query.value(2).toString();
cat.color = QColor(query.value(3).toString());
cat.parentCategoryId = query.value(4).toLongLong();
cat.sortOrder = query.value(5).toInt();
cat.createdAt = query.value(6).toDateTime();
cat.id = query.value("id").toLongLong();
cat.accountId = query.value("accountId").isNull() ? -1 : query.value("accountId").toLongLong();
cat.name = query.value("name").toString();
cat.color = QColor(query.value("color").toString());
cat.parentCategoryId = query.value("parentCategoryId").isNull() ? -1 : query.value("parentCategoryId").toLongLong();
cat.sortOrder = query.value("sortOrder").toInt();
cat.createdAt = QDateTime::fromString(query.value("createdAt").toString(), Qt::ISODate);
return cat;
}
@@ -94,24 +94,20 @@ QVector<Category> CategoryDao::findByAccount(qint64 accountId)
{
QSqlDatabase db = DatabaseManager::instance().database();
QSqlQuery query(db);
if (accountId >= 0) {
query.prepare("SELECT id, accountId, name, color, parentCategoryId, sortOrder, createdAt FROM Category WHERE accountId = :accountId OR accountId IS NULL ORDER BY sortOrder, name");
query.bindValue(":accountId", accountId);
} else {
query.prepare("SELECT id, accountId, name, color, parentCategoryId, sortOrder, createdAt FROM Category WHERE accountId IS NULL ORDER BY sortOrder, name");
}
query.prepare("SELECT id, accountId, name, color, parentCategoryId, sortOrder, createdAt FROM Category WHERE accountId = :accountId ORDER BY sortOrder, name");
query.bindValue(":accountId", accountId);
QVector<Category> cats;
if (query.exec()) {
while (query.next()) {
Category cat;
cat.id = query.value(0).toLongLong();
cat.accountId = query.value(1).toLongLong();
cat.name = query.value(2).toString();
cat.color = QColor(query.value(3).toString());
cat.parentCategoryId = query.value(4).toLongLong();
cat.sortOrder = query.value(5).toInt();
cat.createdAt = query.value(6).toDateTime();
cat.id = query.value("id").toLongLong();
cat.accountId = query.value("accountId").isNull() ? -1 : query.value("accountId").toLongLong();
cat.name = query.value("name").toString();
cat.color = QColor(query.value("color").toString());
cat.parentCategoryId = query.value("parentCategoryId").isNull() ? -1 : query.value("parentCategoryId").toLongLong();
cat.sortOrder = query.value("sortOrder").toInt();
cat.createdAt = QDateTime::fromString(query.value("createdAt").toString(), Qt::ISODate);
cats.append(cat);
}
}
@@ -120,28 +116,42 @@ QVector<Category> CategoryDao::findByAccount(qint64 accountId)
QVector<Category> CategoryDao::findGlobal()
{
return findByAccount(-1);
QSqlDatabase db = DatabaseManager::instance().database();
QSqlQuery query(db);
query.exec("SELECT id, accountId, name, color, parentCategoryId, sortOrder, createdAt FROM Category WHERE accountId IS NULL ORDER BY sortOrder, name");
QVector<Category> cats;
while (query.next()) {
Category cat;
cat.id = query.value("id").toLongLong();
cat.accountId = -1;
cat.name = query.value("name").toString();
cat.color = QColor(query.value("color").toString());
cat.parentCategoryId = query.value("parentCategoryId").isNull() ? -1 : query.value("parentCategoryId").toLongLong();
cat.sortOrder = query.value("sortOrder").toInt();
cat.createdAt = QDateTime::fromString(query.value("createdAt").toString(), Qt::ISODate);
cats.append(cat);
}
return cats;
}
QVector<Category> CategoryDao::findAll()
{
QSqlDatabase db = DatabaseManager::instance().database();
QSqlQuery query(db);
query.prepare("SELECT id, accountId, name, color, parentCategoryId, sortOrder, createdAt FROM Category ORDER BY accountId, sortOrder, name");
query.exec("SELECT id, accountId, name, color, parentCategoryId, sortOrder, createdAt FROM Category ORDER BY sortOrder, name");
QVector<Category> cats;
if (query.exec()) {
while (query.next()) {
Category cat;
cat.id = query.value(0).toLongLong();
cat.accountId = query.value(1).toLongLong();
cat.name = query.value(2).toString();
cat.color = QColor(query.value(3).toString());
cat.parentCategoryId = query.value(4).toLongLong();
cat.sortOrder = query.value(5).toInt();
cat.createdAt = query.value(6).toDateTime();
cats.append(cat);
}
while (query.next()) {
Category cat;
cat.id = query.value("id").toLongLong();
cat.accountId = query.value("accountId").isNull() ? -1 : query.value("accountId").toLongLong();
cat.name = query.value("name").toString();
cat.color = QColor(query.value("color").toString());
cat.parentCategoryId = query.value("parentCategoryId").isNull() ? -1 : query.value("parentCategoryId").toLongLong();
cat.sortOrder = query.value("sortOrder").toInt();
cat.createdAt = QDateTime::fromString(query.value("createdAt").toString(), Qt::ISODate);
cats.append(cat);
}
return cats;
}
@@ -152,103 +162,88 @@ QVector<Category> CategoryDao::findChildren(qint64 parentId)
QSqlQuery query(db);
query.prepare("SELECT id, accountId, name, color, parentCategoryId, sortOrder, createdAt FROM Category WHERE parentCategoryId = :parentId ORDER BY sortOrder, name");
query.bindValue(":parentId", parentId);
QVector<Category> cats;
if (query.exec()) {
while (query.next()) {
Category cat;
cat.id = query.value(0).toLongLong();
cat.accountId = query.value(1).toLongLong();
cat.name = query.value(2).toString();
cat.color = QColor(query.value(3).toString());
cat.parentCategoryId = query.value(4).toLongLong();
cat.sortOrder = query.value(5).toInt();
cat.createdAt = query.value(6).toDateTime();
cat.id = query.value("id").toLongLong();
cat.accountId = query.value("accountId").isNull() ? -1 : query.value("accountId").toLongLong();
cat.name = query.value("name").toString();
cat.color = QColor(query.value("color").toString());
cat.parentCategoryId = query.value("parentCategoryId").isNull() ? -1 : query.value("parentCategoryId").toLongLong();
cat.sortOrder = query.value("sortOrder").toInt();
cat.createdAt = QDateTime::fromString(query.value("createdAt").toString(), Qt::ISODate);
cats.append(cat);
}
}
return cats;
}
bool CategoryDao::assignToMail(qint64 mailCopyId, qint64 categoryId)
bool CategoryDao::assignToMail(qint64 mailId, qint64 categoryId)
{
QSqlDatabase db = DatabaseManager::instance().database();
QSqlQuery query(db);
query.prepare(
"INSERT OR IGNORE INTO MailCategory (mailCopyId, categoryId, assignedAt) "
"VALUES (:mailCopyId, :categoryId, :assignedAt)"
);
query.bindValue(":mailCopyId", mailCopyId);
query.prepare("INSERT OR IGNORE INTO MailCategory (mailCopyId, categoryId, assignedAt) VALUES (:mailId, :categoryId, :assignedAt)");
query.bindValue(":mailId", mailId);
query.bindValue(":categoryId", categoryId);
query.bindValue(":assignedAt", QDateTime::currentDateTime());
query.bindValue(":assignedAt", QDateTime::currentDateTimeUtc().toString(Qt::ISODate));
if (!query.exec()) {
qWarning() << "Failed to assign category to mail:" << query.lastError().text();
qDebug() << "[CategoryDao] assignToMail failed:" << query.lastError().text();
return false;
}
return true;
}
bool CategoryDao::removeFromMail(qint64 mailCopyId, qint64 categoryId)
bool CategoryDao::removeFromMail(qint64 mailId, qint64 categoryId)
{
QSqlDatabase db = DatabaseManager::instance().database();
QSqlQuery query(db);
query.prepare("DELETE FROM MailCategory WHERE mailCopyId = :mailCopyId AND categoryId = :categoryId");
query.bindValue(":mailCopyId", mailCopyId);
query.prepare("DELETE FROM MailCategory WHERE mailCopyId = :mailId AND categoryId = :categoryId");
query.bindValue(":mailId", mailId);
query.bindValue(":categoryId", categoryId);
if (!query.exec()) {
qWarning() << "Failed to remove category from mail:" << query.lastError().text();
qDebug() << "[CategoryDao] removeFromMail failed:" << query.lastError().text();
return false;
}
return query.numRowsAffected() > 0;
return true;
}
QVector<qint64> CategoryDao::categoriesForMail(qint64 mailCopyId)
QVector<qint64> CategoryDao::categoriesForMail(qint64 mailId)
{
QSqlDatabase db = DatabaseManager::instance().database();
QSqlQuery query(db);
query.prepare("SELECT categoryId FROM MailCategory WHERE mailCopyId = :mailCopyId");
query.bindValue(":mailCopyId", mailCopyId);
QVector<qint64> ids;
query.prepare("SELECT categoryId FROM MailCategory WHERE mailCopyId = :mailId");
query.bindValue(":mailId", mailId);
QVector<qint64> catIds;
if (query.exec()) {
while (query.next()) {
ids.append(query.value(0).toLongLong());
catIds.append(query.value("categoryId").toLongLong());
}
}
return ids;
return catIds;
}
QVector<Category> CategoryDao::fullCategoriesForMail(qint64 mailCopyId)
{
QVector<qint64> ids = categoriesForMail(mailCopyId);
QVector<Category> cats;
for (qint64 id : ids) {
if (auto cat = findById(id)) {
cats.append(*cat);
}
}
return cats;
}
bool CategoryDao::reorder(const QVector<qint64>& idsInOrder)
bool CategoryDao::reorder(const QVector<qint64>& ids)
{
QSqlDatabase db = DatabaseManager::instance().database();
db.transaction();
QSqlQuery query(db);
query.prepare("UPDATE Category SET sortOrder = :order WHERE id = :id");
for (int i = 0; i < idsInOrder.size(); ++i) {
query.bindValue(":id", idsInOrder[i]);
query.bindValue(":order", i);
for (int i = 0; i < ids.size(); ++i) {
QSqlQuery query(db);
query.prepare("UPDATE Category SET sortOrder = :sortOrder WHERE id = :id");
query.bindValue(":sortOrder", i);
query.bindValue(":id", ids[i]);
if (!query.exec()) {
db.rollback();
qWarning() << "Failed to reorder category:" << query.lastError().text();
qDebug() << "[CategoryDao] reorder failed:" << query.lastError().text();
return false;
}
}
return db.commit();
db.commit();
return true;
}
+6 -22
View File
@@ -3,22 +3,7 @@
#include "../databasemanager.h"
#include <QVector>
#include <optional>
#include <QColor>
#include <QDateTime>
struct Category
{
qint64 id{-1};
qint64 accountId{-1}; // -1 = global
QString name;
QColor color{QColor("#1976D2")};
qint64 parentCategoryId{-1};
int sortOrder{0};
QDateTime createdAt;
bool isGlobal() const { return accountId < 0; }
bool isValid() const { return id >= 0 && !name.isEmpty(); }
};
#include "common_structs.h"
class CategoryDao
{
@@ -27,13 +12,12 @@ public:
static bool update(const Category& cat);
static bool remove(qint64 id);
static std::optional<Category> findById(qint64 id);
static QVector<Category> findByAccount(qint64 accountId); // accountId = -1 for global
static QVector<Category> findByAccount(qint64 accountId);
static QVector<Category> findGlobal();
static QVector<Category> findAll();
static QVector<Category> findChildren(qint64 parentId);
static bool assignToMail(qint64 mailCopyId, qint64 categoryId);
static bool removeFromMail(qint64 mailCopyId, qint64 categoryId);
static QVector<qint64> categoriesForMail(qint64 mailCopyId);
static QVector<Category> fullCategoriesForMail(qint64 mailCopyId);
static bool reorder(const QVector<qint64>& idsInOrder);
static bool assignToMail(qint64 mailId, qint64 categoryId);
static bool removeFromMail(qint64 mailId, qint64 categoryId);
static QVector<qint64> categoriesForMail(qint64 mailId); // Returns category IDs
static bool reorder(const QVector<qint64>& ids);
};
+126
View File
@@ -0,0 +1,126 @@
#pragma once
#include <QDateTime>
#include <QVector>
#include <optional>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QColor>
struct TemplateVariable
{
QString name;
QString label;
QString defaultValue;
QString type; // "text", "email", "date", "select", "textarea"
QStringList options; // for select type
};
struct Template
{
qint64 id{-1};
qint64 accountId{-1}; // -1 = global
QString name;
QString subject;
QString bodyHtml;
QString bodyText;
QVector<TemplateVariable> variables;
bool isDefault{false};
QDateTime createdAt;
QDateTime updatedAt;
bool isGlobal() const { return accountId < 0; }
bool isValid() const { return id >= 0 && !name.isEmpty(); }
QJsonObject toJson() const;
static Template fromJson(const QJsonObject& obj);
};
struct Signature
{
qint64 id{-1};
qint64 accountId{-1}; // -1 = global
QString name;
QString html;
bool isDefault{false};
QDateTime createdAt;
QDateTime updatedAt;
bool isGlobal() const { return accountId < 0; }
bool isValid() const { return id >= 0 && !name.isEmpty(); }
};
struct Category
{
qint64 id{-1};
qint64 accountId{-1}; // -1 = global
QString name;
QColor color; // color
qint64 parentCategoryId{-1};
int sortOrder{0};
QDateTime createdAt;
bool isGlobal() const { return accountId < 0; }
bool isValid() const { return id >= 0 && !name.isEmpty(); }
};
struct RuleCondition
{
enum Field { From, To, Cc, Subject, Body, HasAttachment, Size, Date, Flagged, Read };
enum Operator { Contains, NotContains, Equals, NotEquals, StartsWith, EndsWith, Regex, GreaterThan, LessThan, Before, After };
Field field{From};
Operator op{Contains};
QString value;
bool caseSensitive{false};
bool match(const QString& text) const;
QJsonObject toJson() const;
static RuleCondition fromJson(const QJsonObject& obj);
};
struct RuleAction
{
enum Type {
MoveToFolder, // value = folderId
MarkAsRead, // value = "true"/"false"
MarkAsFlagged, // value = "true"/"false"
Delete, // no value
AssignCategory, // value = categoryId
RemoveCategory, // value = categoryId
ForwardTo, // value = email address
ReplyWithTemplate, // value = templateId
SetPriority, // value = "high"/"normal"/"low"
StopProcessing // no value (stop evaluating more rules)
};
Type type{MoveToFolder};
QString value;
QJsonObject toJson() const;
static RuleAction fromJson(const QJsonObject& obj);
};
struct Rule
{
qint64 id{-1};
qint64 accountId{-1}; // -1 = global
QString name;
QString description;
bool enabled{true};
int priority{100}; // lower = higher priority
bool matchAll{true}; // true = AND, false = OR
QVector<RuleCondition> conditions;
QVector<RuleAction> actions;
QDateTime createdAt;
QDateTime updatedAt;
QDateTime lastRun;
qint64 runCount{0};
bool isGlobal() const { return accountId < 0; }
bool isValid() const { return id >= 0 && !name.isEmpty(); }
QJsonObject toJson() const;
static Rule fromJson(const QJsonObject& obj);
};
+3 -63
View File
@@ -3,68 +3,7 @@
#include "../databasemanager.h"
#include <QVector>
#include <optional>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
struct RuleCondition
{
enum Field { From, To, Cc, Subject, Body, HasAttachment, Size, Date, Flagged, Read };
enum Operator { Contains, NotContains, Equals, NotEquals, StartsWith, EndsWith, Regex, GreaterThan, LessThan, Before, After };
Field field{From};
Operator op{Contains};
QString value;
bool caseSensitive{false};
bool match(const QString& text) const;
QJsonObject toJson() const;
static RuleCondition fromJson(const QJsonObject& obj);
};
struct RuleAction
{
enum Type {
MoveToFolder, // value = folderId
MarkAsRead, // value = "true"/"false"
MarkAsFlagged, // value = "true"/"false"
Delete, // no value
AssignCategory, // value = categoryId
RemoveCategory, // value = categoryId
ForwardTo, // value = email address
ReplyWithTemplate, // value = templateId
SetPriority, // value = "high"/"normal"/"low"
StopProcessing // no value (stop evaluating more rules)
};
Type type{MoveToFolder};
QString value;
QJsonObject toJson() const;
static RuleAction fromJson(const QJsonObject& obj);
};
struct Rule
{
qint64 id{-1};
qint64 accountId{-1}; // -1 = global
QString name;
QString description;
bool enabled{true};
int priority{100}; // lower = higher priority
bool matchAll{true}; // true = AND, false = OR
QVector<RuleCondition> conditions;
QVector<RuleAction> actions;
QDateTime createdAt;
QDateTime updatedAt;
QDateTime lastRun;
qint64 runCount{0};
bool isGlobal() const { return accountId < 0; }
bool isValid() const { return id >= 0 && !name.isEmpty(); }
QJsonObject toJson() const;
static Rule fromJson(const QJsonObject& obj);
};
#include "common_structs.h"
class RuleDao
{
@@ -75,7 +14,8 @@ public:
static std::optional<Rule> findById(qint64 id);
static QVector<Rule> findByAccount(qint64 accountId); // accountId = -1 for global
static QVector<Rule> findGlobal();
static QVector<Rule> findAllEnabled();
static QVector<Rule> findAll();
static QVector<Rule> findAllEnabled(); // only enabled rules
static bool setEnabled(qint64 id, bool enabled);
static bool updateLastRun(qint64 id, const QDateTime& when, qint64 runCount);
};
+220
View File
@@ -0,0 +1,220 @@
#include "signaturedao.h"
#include <QSqlQuery>
#include <QSqlError>
#include <QDebug>
#include <QDateTime>
bool SignatureDao::insert(Signature& sig)
{
QSqlDatabase db = DatabaseManager::instance().database();
QSqlQuery query(db);
query.prepare(
"INSERT INTO Signature (accountId, name, html, isDefault, createdAt, updatedAt) "
"VALUES (:accountId, :name, :html, :isDefault, :createdAt, :updatedAt)");
query.bindValue(":accountId", sig.accountId >= 0 ? sig.accountId : QVariant());
query.bindValue(":name", sig.name);
query.bindValue(":html", sig.html);
query.bindValue(":isDefault", sig.isDefault ? 1 : 0);
query.bindValue(":createdAt", QDateTime::currentDateTimeUtc().toString(Qt::ISODate));
query.bindValue(":updatedAt", QDateTime::currentDateTimeUtc().toString(Qt::ISODate));
if (!query.exec()) {
qDebug() << "[SignatureDao] Insert failed:" << query.lastError().text();
return false;
}
sig.id = query.lastInsertId().toLongLong();
return true;
}
bool SignatureDao::update(const Signature& sig)
{
QSqlDatabase db = DatabaseManager::instance().database();
QSqlQuery query(db);
query.prepare(
"UPDATE Signature SET accountId = :accountId, name = :name, html = :html, "
"isDefault = :isDefault, updatedAt = :updatedAt "
"WHERE id = :id");
query.bindValue(":id", sig.id);
query.bindValue(":accountId", sig.accountId >= 0 ? sig.accountId : QVariant());
query.bindValue(":name", sig.name);
query.bindValue(":html", sig.html);
query.bindValue(":isDefault", sig.isDefault ? 1 : 0);
query.bindValue(":updatedAt", QDateTime::currentDateTimeUtc().toString(Qt::ISODate));
if (!query.exec()) {
qDebug() << "[SignatureDao] Update failed:" << query.lastError().text();
return false;
}
return true;
}
bool SignatureDao::remove(qint64 id)
{
QSqlDatabase db = DatabaseManager::instance().database();
QSqlQuery query(db);
query.prepare("DELETE FROM Signature WHERE id = :id");
query.bindValue(":id", id);
if (!query.exec()) {
qDebug() << "[SignatureDao] Remove failed:" << query.lastError().text();
return false;
}
return true;
}
std::optional<Signature> SignatureDao::findById(qint64 id)
{
QSqlDatabase db = DatabaseManager::instance().database();
QSqlQuery query(db);
query.prepare("SELECT id, accountId, name, html, isDefault, createdAt, updatedAt FROM Signature WHERE id = :id");
query.bindValue(":id", id);
if (!query.exec() || !query.next()) {
return std::nullopt;
}
Signature sig;
sig.id = query.value("id").toLongLong();
sig.accountId = query.value("accountId").isNull() ? -1 : query.value("accountId").toLongLong();
sig.name = query.value("name").toString();
sig.html = query.value("html").toString();
sig.isDefault = query.value("isDefault").toBool();
sig.createdAt = QDateTime::fromString(query.value("createdAt").toString(), Qt::ISODate);
sig.updatedAt = QDateTime::fromString(query.value("updatedAt").toString(), Qt::ISODate);
return sig;
}
QVector<Signature> SignatureDao::findByAccount(qint64 accountId)
{
QSqlDatabase db = DatabaseManager::instance().database();
QSqlQuery query(db);
if (accountId >= 0) {
query.prepare("SELECT id, accountId, name, html, isDefault, createdAt, updatedAt FROM Signature WHERE accountId = :accountId OR accountId IS NULL ORDER BY name");
query.bindValue(":accountId", accountId);
} else {
query.prepare("SELECT id, accountId, name, html, isDefault, createdAt, updatedAt FROM Signature WHERE accountId IS NULL ORDER BY name");
}
QVector<Signature> sigs;
if (query.exec()) {
while (query.next()) {
Signature sig;
sig.id = query.value("id").toLongLong();
sig.accountId = query.value("accountId").isNull() ? -1 : query.value("accountId").toLongLong();
sig.name = query.value("name").toString();
sig.html = query.value("html").toString();
sig.isDefault = query.value("isDefault").toBool();
sig.createdAt = QDateTime::fromString(query.value("createdAt").toString(), Qt::ISODate);
sig.updatedAt = QDateTime::fromString(query.value("updatedAt").toString(), Qt::ISODate);
sigs.append(sig);
}
}
return sigs;
}
QVector<Signature> SignatureDao::findGlobal()
{
QSqlDatabase db = DatabaseManager::instance().database();
QSqlQuery query(db);
query.exec("SELECT id, accountId, name, html, isDefault, createdAt, updatedAt FROM Signature WHERE accountId IS NULL ORDER BY name");
QVector<Signature> sigs;
while (query.next()) {
Signature sig;
sig.id = query.value("id").toLongLong();
sig.accountId = -1;
sig.name = query.value("name").toString();
sig.html = query.value("html").toString();
sig.isDefault = query.value("isDefault").toBool();
sig.createdAt = QDateTime::fromString(query.value("createdAt").toString(), Qt::ISODate);
sig.updatedAt = QDateTime::fromString(query.value("updatedAt").toString(), Qt::ISODate);
sigs.append(sig);
}
return sigs;
}
QVector<Signature> SignatureDao::findAll()
{
QSqlDatabase db = DatabaseManager::instance().database();
QSqlQuery query(db);
query.exec("SELECT id, accountId, name, html, isDefault, createdAt, updatedAt FROM Signature ORDER BY name");
QVector<Signature> sigs;
while (query.next()) {
Signature sig;
sig.id = query.value("id").toLongLong();
sig.accountId = query.value("accountId").isNull() ? -1 : query.value("accountId").toLongLong();
sig.name = query.value("name").toString();
sig.html = query.value("html").toString();
sig.isDefault = query.value("isDefault").toBool();
sig.createdAt = QDateTime::fromString(query.value("createdAt").toString(), Qt::ISODate);
sig.updatedAt = QDateTime::fromString(query.value("updatedAt").toString(), Qt::ISODate);
sigs.append(sig);
}
return sigs;
}
std::optional<Signature> SignatureDao::findDefault(qint64 accountId)
{
QSqlDatabase db = DatabaseManager::instance().database();
QSqlQuery query(db);
query.prepare(
"SELECT id, accountId, name, html, isDefault, createdAt, updatedAt "
"FROM Signature "
"WHERE (accountId = :accountId OR accountId IS NULL) AND isDefault = 1 "
"ORDER BY accountId DESC LIMIT 1");
query.bindValue(":accountId", accountId >= 0 ? accountId : QVariant());
if (!query.exec() || !query.next()) {
return std::nullopt;
}
Signature sig;
sig.id = query.value("id").toLongLong();
sig.accountId = query.value("accountId").isNull() ? -1 : query.value("accountId").toLongLong();
sig.name = query.value("name").toString();
sig.html = query.value("html").toString();
sig.isDefault = query.value("isDefault").toBool();
sig.createdAt = QDateTime::fromString(query.value("createdAt").toString(), Qt::ISODate);
sig.updatedAt = QDateTime::fromString(query.value("updatedAt").toString(), Qt::ISODate);
return sig;
}
bool SignatureDao::setDefault(qint64 id, bool isDefault)
{
QSqlDatabase db = DatabaseManager::instance().database();
db.transaction();
// First unset all defaults for the same account
QSqlQuery query(db);
query.prepare(
"UPDATE Signature SET isDefault = 0 WHERE accountId = "
"(SELECT accountId FROM Signature WHERE id = :id)");
query.bindValue(":id", id);
if (!query.exec()) {
db.rollback();
return false;
}
// Set the new default
query.prepare("UPDATE Signature SET isDefault = :isDefault, updatedAt = :updatedAt WHERE id = :id");
query.bindValue(":id", id);
query.bindValue(":isDefault", isDefault ? 1 : 0);
query.bindValue(":updatedAt", QDateTime::currentDateTimeUtc().toString(Qt::ISODate));
if (!query.exec()) {
db.rollback();
qDebug() << "[SignatureDao] setDefault failed:" << query.lastError().text();
return false;
}
db.commit();
return true;
}
+20
View File
@@ -0,0 +1,20 @@
#pragma once
#include "../databasemanager.h"
#include <QVector>
#include <optional>
#include "common_structs.h"
class SignatureDao
{
public:
static bool insert(Signature& sig);
static bool update(const Signature& sig);
static bool remove(qint64 id);
static std::optional<Signature> findById(qint64 id);
static QVector<Signature> findByAccount(qint64 accountId);
static QVector<Signature> findGlobal();
static QVector<Signature> findAll();
static std::optional<Signature> findDefault(qint64 accountId);
static bool setDefault(qint64 id, bool isDefault);
};
+1 -29
View File
@@ -6,35 +6,7 @@
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
struct TemplateVariable
{
QString name;
QString label;
QString defaultValue;
QString type; // "text", "email", "date", "select", "textarea"
QStringList options; // for select type
};
struct Template
{
qint64 id{-1};
qint64 accountId{-1}; // -1 = global
QString name;
QString subject;
QString bodyHtml;
QString bodyText;
QVector<TemplateVariable> variables;
bool isDefault{false};
QDateTime createdAt;
QDateTime updatedAt;
bool isGlobal() const { return accountId < 0; }
bool isValid() const { return id >= 0 && !name.isEmpty(); }
QJsonObject toJson() const;
static Template fromJson(const QJsonObject& obj);
};
#include "common_structs.h"
class TemplateDao
{