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:
+107
-112
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
};
|
||||
@@ -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
@@ -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);
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
};
|
||||
@@ -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
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user