feat: comprehensive Wino Mail updates
- ReaderView: complete rewrite with CSS styling, headers, attachments, zoom, find, dark mode, image blocking - Categories: DAO + UI tree widget with colors, nested categories, assignment - Rules engine: DAO + evaluation + actions (move, mark read, flag, delete, category, forward) - Templates: DAO + editor + manager with variables support - Signatures: DAO + manager + auto-per-account + manual selector in compose - ComposeView: signature combo, template selector, auto-signature on new email - MainWindow: frameless with rounded corners, 1px border, resize from edges, no status bar - Database: new tables (Category, MailCategory, Rule, Template, Signature) with indexes
This commit is contained in:
@@ -0,0 +1,387 @@
|
||||
#include "templatedao.h"
|
||||
#include <QSqlQuery>
|
||||
#include <QSqlError>
|
||||
#include <QDebug>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
#include <QDateTime>
|
||||
|
||||
bool TemplateDao::insert(Template& tmpl)
|
||||
{
|
||||
QSqlDatabase db = DatabaseManager::instance().database();
|
||||
QSqlQuery query(db);
|
||||
query.prepare(
|
||||
"INSERT INTO Template (accountId, name, subject, bodyHtml, bodyText, variables, isDefault, createdAt, updatedAt) "
|
||||
"VALUES (:accountId, :name, :subject, :bodyHtml, :bodyText, :variables, :isDefault, :createdAt, :updatedAt)"
|
||||
);
|
||||
query.bindValue(":accountId", tmpl.accountId >= 0 ? tmpl.accountId : QVariant());
|
||||
query.bindValue(":name", tmpl.name);
|
||||
query.bindValue(":subject", tmpl.subject);
|
||||
query.bindValue(":bodyHtml", tmpl.bodyHtml);
|
||||
query.bindValue(":bodyText", tmpl.bodyText);
|
||||
|
||||
QJsonArray varsArray;
|
||||
for (const TemplateVariable& var : tmpl.variables) {
|
||||
QJsonObject varObj;
|
||||
varObj["name"] = var.name;
|
||||
varObj["label"] = var.label;
|
||||
varObj["defaultValue"] = var.defaultValue;
|
||||
varObj["type"] = var.type;
|
||||
varObj["options"] = QJsonArray::fromStringList(var.options);
|
||||
varsArray.append(varObj);
|
||||
}
|
||||
QJsonDocument varsDoc(varsArray);
|
||||
query.bindValue(":variables", varsDoc.toJson(QJsonDocument::Compact));
|
||||
|
||||
query.bindValue(":isDefault", tmpl.isDefault ? 1 : 0);
|
||||
query.bindValue(":createdAt", QDateTime::currentDateTime());
|
||||
query.bindValue(":updatedAt", QDateTime::currentDateTime());
|
||||
|
||||
if (!query.exec()) {
|
||||
qWarning() << "Failed to insert template:" << query.lastError().text();
|
||||
return false;
|
||||
}
|
||||
tmpl.id = query.lastInsertId().toLongLong();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TemplateDao::update(const Template& tmpl)
|
||||
{
|
||||
QSqlDatabase db = DatabaseManager::instance().database();
|
||||
QSqlQuery query(db);
|
||||
query.prepare(
|
||||
"UPDATE Template SET "
|
||||
"accountId = :accountId, "
|
||||
"name = :name, "
|
||||
"subject = :subject, "
|
||||
"bodyHtml = :bodyHtml, "
|
||||
"bodyText = :bodyText, "
|
||||
"variables = :variables, "
|
||||
"isDefault = :isDefault, "
|
||||
"updatedAt = :updatedAt "
|
||||
"WHERE id = :id"
|
||||
);
|
||||
query.bindValue(":id", tmpl.id);
|
||||
query.bindValue(":accountId", tmpl.accountId >= 0 ? tmpl.accountId : QVariant());
|
||||
query.bindValue(":name", tmpl.name);
|
||||
query.bindValue(":subject", tmpl.subject);
|
||||
query.bindValue(":bodyHtml", tmpl.bodyHtml);
|
||||
query.bindValue(":bodyText", tmpl.bodyText);
|
||||
|
||||
QJsonArray varsArray;
|
||||
for (const TemplateVariable& var : tmpl.variables) {
|
||||
QJsonObject varObj;
|
||||
varObj["name"] = var.name;
|
||||
varObj["label"] = var.label;
|
||||
varObj["defaultValue"] = var.defaultValue;
|
||||
varObj["type"] = var.type;
|
||||
varObj["options"] = QJsonArray::fromStringList(var.options);
|
||||
varsArray.append(varObj);
|
||||
}
|
||||
QJsonDocument varsDoc(varsArray);
|
||||
query.bindValue(":variables", varsDoc.toJson(QJsonDocument::Compact));
|
||||
|
||||
query.bindValue(":isDefault", tmpl.isDefault ? 1 : 0);
|
||||
query.bindValue(":updatedAt", QDateTime::currentDateTime());
|
||||
|
||||
if (!query.exec()) {
|
||||
qWarning() << "Failed to update template:" << query.lastError().text();
|
||||
return false;
|
||||
}
|
||||
return query.numRowsAffected() > 0;
|
||||
}
|
||||
|
||||
bool TemplateDao::remove(qint64 id)
|
||||
{
|
||||
QSqlDatabase db = DatabaseManager::instance().database();
|
||||
QSqlQuery query(db);
|
||||
query.prepare("DELETE FROM Template WHERE id = :id");
|
||||
query.bindValue(":id", id);
|
||||
|
||||
if (!query.exec()) {
|
||||
qWarning() << "Failed to delete template:" << query.lastError().text();
|
||||
return false;
|
||||
}
|
||||
return query.numRowsAffected() > 0;
|
||||
}
|
||||
|
||||
std::optional<Template> TemplateDao::findById(qint64 id)
|
||||
{
|
||||
QSqlDatabase db = DatabaseManager::instance().database();
|
||||
QSqlQuery query(db);
|
||||
query.prepare("SELECT id, accountId, name, subject, bodyHtml, bodyText, variables, isDefault, createdAt, updatedAt FROM Template WHERE id = :id");
|
||||
query.bindValue(":id", id);
|
||||
|
||||
if (!query.exec() || !query.next()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
Template tmpl;
|
||||
tmpl.id = query.value(0).toLongLong();
|
||||
tmpl.accountId = query.value(1).isNull() ? -1 : query.value(1).toLongLong();
|
||||
tmpl.name = query.value(2).toString();
|
||||
tmpl.subject = query.value(3).toString();
|
||||
tmpl.bodyHtml = query.value(4).toString();
|
||||
tmpl.bodyText = query.value(5).toString();
|
||||
|
||||
QJsonDocument varsDoc = QJsonDocument::fromJson(query.value(6).toByteArray());
|
||||
QJsonArray varsArray = varsDoc.array();
|
||||
for (const auto& v : varsArray) {
|
||||
QJsonObject obj = v.toObject();
|
||||
TemplateVariable var;
|
||||
var.name = obj["name"].toString();
|
||||
var.label = obj["label"].toString();
|
||||
var.defaultValue = obj["defaultValue"].toString();
|
||||
var.type = obj["type"].toString();
|
||||
QJsonArray optsArray = obj["options"].toArray();
|
||||
for (const auto& opt : optsArray) {
|
||||
var.options.append(opt.toString());
|
||||
}
|
||||
tmpl.variables.append(var);
|
||||
}
|
||||
|
||||
tmpl.isDefault = query.value(7).toBool();
|
||||
tmpl.createdAt = QDateTime::fromString(query.value(8).toString(), Qt::ISODate);
|
||||
tmpl.updatedAt = QDateTime::fromString(query.value(9).toString(), Qt::ISODate);
|
||||
return tmpl;
|
||||
}
|
||||
|
||||
QVector<Template> TemplateDao::findByAccount(qint64 accountId)
|
||||
{
|
||||
QSqlDatabase db = DatabaseManager::instance().database();
|
||||
QSqlQuery query(db);
|
||||
if (accountId >= 0) {
|
||||
query.prepare("SELECT id, accountId, name, subject, bodyHtml, bodyText, variables, isDefault, createdAt, updatedAt FROM Template WHERE accountId = :accountId OR accountId IS NULL ORDER BY isDefault DESC, name");
|
||||
query.bindValue(":accountId", accountId);
|
||||
} else {
|
||||
query.prepare("SELECT id, accountId, name, subject, bodyHtml, bodyText, variables, isDefault, createdAt, updatedAt FROM Template WHERE accountId IS NULL ORDER BY isDefault DESC, name");
|
||||
}
|
||||
|
||||
QVector<Template> templates;
|
||||
if (query.exec()) {
|
||||
while (query.next()) {
|
||||
Template tmpl;
|
||||
tmpl.id = query.value(0).toLongLong();
|
||||
tmpl.accountId = query.value(1).isNull() ? -1 : query.value(1).toLongLong();
|
||||
tmpl.name = query.value(2).toString();
|
||||
tmpl.subject = query.value(3).toString();
|
||||
tmpl.bodyHtml = query.value(4).toString();
|
||||
tmpl.bodyText = query.value(5).toString();
|
||||
|
||||
QJsonDocument varsDoc = QJsonDocument::fromJson(query.value(6).toByteArray());
|
||||
QJsonArray varsArray = varsDoc.array();
|
||||
for (const auto& v : varsArray) {
|
||||
QJsonObject obj = v.toObject();
|
||||
TemplateVariable var;
|
||||
var.name = obj["name"].toString();
|
||||
var.label = obj["label"].toString();
|
||||
var.defaultValue = obj["defaultValue"].toString();
|
||||
var.type = obj["type"].toString();
|
||||
QJsonArray optsArray = obj["options"].toArray();
|
||||
for (const auto& opt : optsArray) {
|
||||
var.options.append(opt.toString());
|
||||
}
|
||||
tmpl.variables.append(var);
|
||||
}
|
||||
|
||||
tmpl.isDefault = query.value(7).toBool();
|
||||
tmpl.createdAt = QDateTime::fromString(query.value(8).toString(), Qt::ISODate);
|
||||
tmpl.updatedAt = QDateTime::fromString(query.value(9).toString(), Qt::ISODate);
|
||||
templates.append(tmpl);
|
||||
}
|
||||
}
|
||||
return templates;
|
||||
}
|
||||
|
||||
QVector<Template> TemplateDao::findGlobal()
|
||||
{
|
||||
return findByAccount(-1);
|
||||
}
|
||||
|
||||
QVector<Template> TemplateDao::findAll()
|
||||
{
|
||||
QSqlDatabase db = DatabaseManager::instance().database();
|
||||
QSqlQuery query(db);
|
||||
query.prepare("SELECT id, accountId, name, subject, bodyHtml, bodyText, variables, isDefault, createdAt, updatedAt FROM Template ORDER BY accountId, isDefault DESC, name");
|
||||
|
||||
QVector<Template> templates;
|
||||
if (query.exec()) {
|
||||
while (query.next()) {
|
||||
Template tmpl;
|
||||
tmpl.id = query.value(0).toLongLong();
|
||||
tmpl.accountId = query.value(1).isNull() ? -1 : query.value(1).toLongLong();
|
||||
tmpl.name = query.value(2).toString();
|
||||
tmpl.subject = query.value(3).toString();
|
||||
tmpl.bodyHtml = query.value(4).toString();
|
||||
tmpl.bodyText = query.value(5).toString();
|
||||
|
||||
QJsonDocument varsDoc = QJsonDocument::fromJson(query.value(6).toByteArray());
|
||||
QJsonArray varsArray = varsDoc.array();
|
||||
for (const auto& v : varsArray) {
|
||||
QJsonObject obj = v.toObject();
|
||||
TemplateVariable var;
|
||||
var.name = obj["name"].toString();
|
||||
var.label = obj["label"].toString();
|
||||
var.defaultValue = obj["defaultValue"].toString();
|
||||
var.type = obj["type"].toString();
|
||||
QJsonArray optsArray = obj["options"].toArray();
|
||||
for (const auto& opt : optsArray) {
|
||||
var.options.append(opt.toString());
|
||||
}
|
||||
tmpl.variables.append(var);
|
||||
}
|
||||
|
||||
tmpl.isDefault = query.value(7).toBool();
|
||||
tmpl.createdAt = QDateTime::fromString(query.value(8).toString(), Qt::ISODate);
|
||||
tmpl.updatedAt = QDateTime::fromString(query.value(9).toString(), Qt::ISODate);
|
||||
templates.append(tmpl);
|
||||
}
|
||||
}
|
||||
return templates;
|
||||
}
|
||||
|
||||
std::optional<Template> TemplateDao::findDefault(qint64 accountId)
|
||||
{
|
||||
QSqlDatabase db = DatabaseManager::instance().database();
|
||||
QSqlQuery query(db);
|
||||
if (accountId >= 0) {
|
||||
query.prepare("SELECT id, accountId, name, subject, bodyHtml, bodyText, variables, isDefault, createdAt, updatedAt FROM Template WHERE (accountId = :accountId OR accountId IS NULL) AND isDefault = 1 LIMIT 1");
|
||||
query.bindValue(":accountId", accountId);
|
||||
} else {
|
||||
query.prepare("SELECT id, accountId, name, subject, bodyHtml, bodyText, variables, isDefault, createdAt, updatedAt FROM Template WHERE accountId IS NULL AND isDefault = 1 LIMIT 1");
|
||||
}
|
||||
|
||||
if (!query.exec() || !query.next()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
Template tmpl;
|
||||
tmpl.id = query.value(0).toLongLong();
|
||||
tmpl.accountId = query.value(1).isNull() ? -1 : query.value(1).toLongLong();
|
||||
tmpl.name = query.value(2).toString();
|
||||
tmpl.subject = query.value(3).toString();
|
||||
tmpl.bodyHtml = query.value(4).toString();
|
||||
tmpl.bodyText = query.value(5).toString();
|
||||
|
||||
QJsonDocument varsDoc = QJsonDocument::fromJson(query.value(6).toByteArray());
|
||||
QJsonArray varsArray = varsDoc.array();
|
||||
for (const auto& v : varsArray) {
|
||||
QJsonObject obj = v.toObject();
|
||||
TemplateVariable var;
|
||||
var.name = obj["name"].toString();
|
||||
var.label = obj["label"].toString();
|
||||
var.defaultValue = obj["defaultValue"].toString();
|
||||
var.type = obj["type"].toString();
|
||||
QJsonArray optsArray = obj["options"].toArray();
|
||||
for (const auto& opt : optsArray) {
|
||||
var.options.append(opt.toString());
|
||||
}
|
||||
tmpl.variables.append(var);
|
||||
}
|
||||
|
||||
tmpl.isDefault = query.value(7).toBool();
|
||||
tmpl.createdAt = QDateTime::fromString(query.value(8).toString(), Qt::ISODate);
|
||||
tmpl.updatedAt = QDateTime::fromString(query.value(9).toString(), Qt::ISODate);
|
||||
return tmpl;
|
||||
}
|
||||
|
||||
bool TemplateDao::setDefault(qint64 id, bool isDefault)
|
||||
{
|
||||
QSqlDatabase db = DatabaseManager::instance().database();
|
||||
db.transaction();
|
||||
|
||||
QSqlQuery query(db);
|
||||
// First, unset any existing default for this account
|
||||
if (isDefault) {
|
||||
auto tmplOpt = findById(id);
|
||||
if (tmplOpt.has_value()) {
|
||||
qint64 accId = tmplOpt->accountId;
|
||||
if (accId >= 0) {
|
||||
query.prepare("UPDATE Template SET isDefault = 0, updatedAt = :updatedAt WHERE accountId = :accountId AND isDefault = 1");
|
||||
query.bindValue(":accountId", accId);
|
||||
} else {
|
||||
query.prepare("UPDATE Template SET isDefault = 0, updatedAt = :updatedAt WHERE accountId IS NULL AND isDefault = 1");
|
||||
}
|
||||
query.bindValue(":updatedAt", QDateTime::currentDateTime());
|
||||
if (!query.exec()) {
|
||||
db.rollback();
|
||||
qWarning() << "Failed to unset previous default:" << query.lastError().text();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set new default
|
||||
query.prepare("UPDATE Template SET isDefault = :isDefault, updatedAt = :updatedAt WHERE id = :id");
|
||||
query.bindValue(":id", id);
|
||||
query.bindValue(":isDefault", isDefault ? 1 : 0);
|
||||
query.bindValue(":updatedAt", QDateTime::currentDateTime());
|
||||
|
||||
if (!query.exec()) {
|
||||
db.rollback();
|
||||
qWarning() << "Failed to set default template:" << query.lastError().text();
|
||||
return false;
|
||||
}
|
||||
|
||||
return db.commit();
|
||||
}
|
||||
|
||||
QJsonObject Template::toJson() const
|
||||
{
|
||||
QJsonObject obj;
|
||||
obj["id"] = static_cast<qint64>(id);
|
||||
obj["accountId"] = accountId >= 0 ? static_cast<qint64>(accountId) : QJsonValue::Null;
|
||||
obj["name"] = name;
|
||||
obj["subject"] = subject;
|
||||
obj["bodyHtml"] = bodyHtml;
|
||||
obj["bodyText"] = bodyText;
|
||||
|
||||
QJsonArray varsArray;
|
||||
for (const TemplateVariable& var : variables) {
|
||||
QJsonObject varObj;
|
||||
varObj["name"] = var.name;
|
||||
varObj["label"] = var.label;
|
||||
varObj["defaultValue"] = var.defaultValue;
|
||||
varObj["type"] = var.type;
|
||||
varObj["options"] = QJsonArray::fromStringList(var.options);
|
||||
varsArray.append(varObj);
|
||||
}
|
||||
obj["variables"] = varsArray;
|
||||
|
||||
obj["isDefault"] = isDefault;
|
||||
obj["createdAt"] = createdAt.toString(Qt::ISODate);
|
||||
obj["updatedAt"] = updatedAt.toString(Qt::ISODate);
|
||||
return obj;
|
||||
}
|
||||
|
||||
Template Template::fromJson(const QJsonObject& obj)
|
||||
{
|
||||
Template tmpl;
|
||||
tmpl.id = obj["id"].toVariant().toLongLong();
|
||||
tmpl.accountId = obj["accountId"].isNull() ? -1 : obj["accountId"].toVariant().toLongLong();
|
||||
tmpl.name = obj["name"].toString();
|
||||
tmpl.subject = obj["subject"].toString();
|
||||
tmpl.bodyHtml = obj["bodyHtml"].toString();
|
||||
tmpl.bodyText = obj["bodyText"].toString();
|
||||
|
||||
QJsonArray varsArray = obj["variables"].toArray();
|
||||
for (const auto& v : varsArray) {
|
||||
QJsonObject varObj = v.toObject();
|
||||
TemplateVariable var;
|
||||
var.name = varObj["name"].toString();
|
||||
var.label = varObj["label"].toString();
|
||||
var.defaultValue = varObj["defaultValue"].toString();
|
||||
var.type = varObj["type"].toString();
|
||||
QJsonArray optsArray = varObj["options"].toArray();
|
||||
for (const auto& opt : optsArray) {
|
||||
var.options.append(opt.toString());
|
||||
}
|
||||
tmpl.variables.append(var);
|
||||
}
|
||||
|
||||
tmpl.isDefault = obj["isDefault"].toBool();
|
||||
tmpl.createdAt = QDateTime::fromString(obj["createdAt"].toString(), Qt::ISODate);
|
||||
tmpl.updatedAt = QDateTime::fromString(obj["updatedAt"].toString(), Qt::ISODate);
|
||||
return tmpl;
|
||||
}
|
||||
Reference in New Issue
Block a user