From be5c11029adb25c586c4fcde6fedfa01d1bdcd49 Mon Sep 17 00:00:00 2001 From: Aldrik Ramaekers Date: Sun, 28 Dec 2025 14:46:44 +0100 Subject: email send backend --- include/config.hpp | 2 +- include/countries.hpp | 2 +- include/exporter.hpp | 55 ++++++++++++++++++++++++++++++++++ include/importer.hpp | 3 +- run_linux64.sh | 2 +- src/administration_reader.cpp | 2 +- src/exporter.cpp | 69 +++++++++++++++++++++++++++++++++++++++++++ src/importer.cpp | 13 -------- src/main_linux.cpp | 6 ++-- src/providers/MailerSend.cpp | 48 ++++++++++++++++++++++++++---- src/ui/ui_invoices.cpp | 4 ++- src/ui/ui_settings.cpp | 3 +- 12 files changed, 179 insertions(+), 30 deletions(-) create mode 100644 include/exporter.hpp create mode 100644 src/exporter.cpp diff --git a/include/config.hpp b/include/config.hpp index 05659e8..ff36364 100644 --- a/include/config.hpp +++ b/include/config.hpp @@ -39,4 +39,4 @@ namespace config { static const ImU32 COLOR_ERROR = IM_COL32(235, 64, 52, 255); static const ImU32 COLOR_DEFAULT = IM_COL32(235, 255, 255, 255); } -} \ No newline at end of file +} diff --git a/include/countries.hpp b/include/countries.hpp index 7a15a32..1595b85 100644 --- a/include/countries.hpp +++ b/include/countries.hpp @@ -49,4 +49,4 @@ namespace country { float calculate_tax_report_final(char* country_code, tax_report* report); time_t get_invoice_date_to_use_for_tax_report(char* country_code, invoice* inv); u32 get_available_tax_rates(const char* country_code, tax_rate* buffer, u32 buffer_size); -} \ No newline at end of file +} diff --git a/include/exporter.hpp b/include/exporter.hpp new file mode 100644 index 0000000..00eaf32 --- /dev/null +++ b/include/exporter.hpp @@ -0,0 +1,55 @@ +/* +* Copyright (c) 2025 Aldrik Ramaekers +* +* Permission to use, copy, modify, and/or distribute this software for any +* purpose with or without fee is hereby granted, provided that the above +* copyright notice and this permission notice appear in all copies. +* +* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#pragma once + +#include "administration.hpp" + +#define E_ERR_SUCCESS 0 +#define E_ERR_FAILED_REQUEST 1 +#define E_ERR_UNIMPLEMENTED 2 + +typedef uint32_t e_err; + +namespace exporter { + + typedef enum + { + EXPORT_STARTING, + EXPORT_WAITING_FOR_RESPONSE, + EXPORT_DONE, + } status; + + typedef struct + { + time_t started_at; + e_err error; + status status; + char* sender; + char* recipient; + const char* subject; + const char* text; + } export_request; + + typedef struct + { + char* provider_name; + bool (*send_email)(char* sender, char* recipient, const char* subject, const char* text); + } email_provider_impl; + + email_provider_impl get_email_provider_implementation(email_provider provider); + exporter::export_request* send_email(char* sender, char* recipient, const char* subject, const char* text); +} \ No newline at end of file diff --git a/include/importer.hpp b/include/importer.hpp index 8db70ef..2360d29 100644 --- a/include/importer.hpp +++ b/include/importer.hpp @@ -73,14 +73,13 @@ namespace importer { typedef struct { char* provider_name; - bool (*send_email)(char* sender, char* recipients, u32 recipients_count, const char* subject, const char* text); + bool (*send_email)(char* sender, char* recipient, const char* subject, const char* text); } email_provider_impl; const char* error_to_string(i_err error); const char* status_to_string(status status); ai_provider_impl get_ai_provider_implementation(ai_provider provider); - email_provider_impl get_email_provider_implementation(email_provider provider); invoice_request* ai_document_to_invoice(char* file_path); model_list_request* ai_get_available_models(ai_provider service); diff --git a/run_linux64.sh b/run_linux64.sh index 0cc70b3..64a648b 100755 --- a/run_linux64.sh +++ b/run_linux64.sh @@ -14,7 +14,7 @@ libs/timer_lib/*.c \ libs/tinyfiledialogs/tinyfiledialogs.c" SOURCES="src/*.cpp src/ui/*.cpp src/locales/*.cpp src/providers/*.cpp" LIBS="-lstdc++ -lglfw -lGL -lm -lssl -lcrypto" -FLAGS="-Wall -Wno-changes-meaning -Wno-write-strings -Wno-attributes -Wno-unused-variable -fpermissive -Wno-format-zero-length -g" +FLAGS="-Wall -Wno-changes-meaning -Wno-write-strings -Wno-attributes -Wno-unused-variable -fpermissive -Wno-format-zero-length -ggdb" INCLUDE_DIRS="-Ilibs/imgui-1.92.1 \ -Ilibs/imgui-1.92.1/backends \ -Ilibs/openssl-3.6.0-beta1/x64/include \ diff --git a/src/administration_reader.cpp b/src/administration_reader.cpp index 7528711..d790b91 100644 --- a/src/administration_reader.cpp +++ b/src/administration_reader.cpp @@ -182,7 +182,7 @@ bool administration_reader::read_invoice_from_xml(invoice* result, char* buffer, char customer_endpoint_id[50]; xml_get_str_x(root, customer_endpoint_id, 50, "cac:AccountingCustomerParty", "cac:Party", "cbc:EndpointID", 0); if (strops::equals(customer_endpoint_id, "[CONSUMER]")) data.customer.type = contact_type::CONTACT_CONSUMER; - + // Addressee xml_get_str_x(root, data.addressee.name, MAX_LEN_LONG_DESC, "cac:Delivery", "cac:DeliveryParty", "cac:PartyName", "cbc:Name", 0); xml_get_str_x(root, data.addressee.address.address1, MAX_LEN_ADDRESS, "cac:Delivery", "cac:DeliveryLocation", "cac:Address", "cbc:StreetName", 0); diff --git a/src/exporter.cpp b/src/exporter.cpp new file mode 100644 index 0000000..c53b70d --- /dev/null +++ b/src/exporter.cpp @@ -0,0 +1,69 @@ +/* +* Copyright (c) 2025 Aldrik Ramaekers +* +* Permission to use, copy, modify, and/or distribute this software for any +* purpose with or without fee is hereby granted, provided that the above +* copyright notice and this permission notice appear in all copies. +* +* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include + +#define CPPHTTPLIB_OPENSSL_SUPPORT +#include "httplib.h" +#include "logger.hpp" +#include "strops.hpp" +#include "memops.hpp" +#include "locales.hpp" +#include "exporter.hpp" + +extern exporter::email_provider_impl _mailersend_api_provider; + +exporter::email_provider_impl exporter::get_email_provider_implementation(email_provider provider) +{ + switch(provider) + { + case EMAIL_PROVIDER_MAILERSEND: return _mailersend_api_provider; + default: assert(0); break; + } + + return exporter::email_provider_impl {0}; +} + +static int _send_email_t(void* arg) { + exporter::export_request* request = (exporter::export_request*)arg; + request->status = exporter::status::EXPORT_WAITING_FOR_RESPONSE; + + exporter::email_provider_impl impl = exporter::get_email_provider_implementation(administration::get_email_service().provider); + request->error = impl.send_email(request->sender, request->recipient, request->subject, request->text); + + request->status = exporter::status::EXPORT_DONE; + + return 0; +} + +exporter::export_request* exporter::send_email(char* sender, char* recipient, const char* subject, const char* text) +{ + exporter::export_request* result = (exporter::export_request*)memops::alloc(sizeof(exporter::export_request)); + result->started_at = time(NULL); + result->error = E_ERR_SUCCESS; + result->status = exporter::status::EXPORT_STARTING; + result->sender = sender; + result->recipient = recipient; + result->subject = subject; + result->text = text; + + thrd_t thr; + if (thrd_create(&thr, _send_email_t, result) != thrd_success) { + return 0; + } + + return result; +} \ No newline at end of file diff --git a/src/importer.cpp b/src/importer.cpp index 45fb16c..3c56062 100644 --- a/src/importer.cpp +++ b/src/importer.cpp @@ -29,8 +29,6 @@ extern importer::ai_provider_impl _chatgpt_api_provider; extern importer::ai_provider_impl _deepseek_api_provider; -extern importer::email_provider_impl _mailersend_api_provider; - importer::ai_provider_impl importer::get_ai_provider_implementation(ai_provider provider) { switch(provider) @@ -43,17 +41,6 @@ importer::ai_provider_impl importer::get_ai_provider_implementation(ai_provider return importer::ai_provider_impl {0}; } -importer::email_provider_impl importer::get_email_provider_implementation(email_provider provider) -{ - switch(provider) - { - case EMAIL_PROVIDER_MAILERSEND: return _mailersend_api_provider; - default: assert(0); break; - } - - return importer::email_provider_impl {0}; -} - static void _batch_query_response_handler(invoice* buffer, char* json) { int alloc_size = 1000; diff --git a/src/main_linux.cpp b/src/main_linux.cpp index e1dd7f6..867073a 100644 --- a/src/main_linux.cpp +++ b/src/main_linux.cpp @@ -74,9 +74,9 @@ int main(int argc, char** argv) ImGui_ImplOpenGL2_Init(); style.FontSizeBase = 18.0f; - io.Fonts->AddFontFromFileTTF("build/Roboto-Regular.ttf"); - ui::fontBold = io.Fonts->AddFontFromFileTTF("build/Roboto-Bold.ttf"); - ui::fontBig = io.Fonts->AddFontFromFileTTF("build/Roboto-Bold.ttf", 30); + io.Fonts->AddFontFromFileTTF("/home/aldrik/Projects/open-books/build/Roboto-Regular.ttf"); + ui::fontBold = io.Fonts->AddFontFromFileTTF("/home/aldrik/Projects/open-books/build/Roboto-Bold.ttf"); + ui::fontBig = io.Fonts->AddFontFromFileTTF("/home/aldrik/Projects/open-books/build/Roboto-Bold.ttf", 30); ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); diff --git a/src/providers/MailerSend.cpp b/src/providers/MailerSend.cpp index 961e457..1a633d9 100644 --- a/src/providers/MailerSend.cpp +++ b/src/providers/MailerSend.cpp @@ -14,22 +14,58 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -#include - #define CPPHTTPLIB_OPENSSL_SUPPORT #include "httplib.h" #include "memops.hpp" #include "strops.hpp" #include "logger.hpp" -#include "importer.hpp" +#include "exporter.hpp" -bool _MailerSend_send_email(char* sender, char* recipients, u32 recipients_count, const char* subject, const char* text) +bool _MailerSend_send_email(char* sender, char* recipient, const char* subject, const char* text) { - return false; + const char *api_key = administration::get_email_service().api_key; + httplib::SSLClient cli("api.mailersend.com", 443); + cli.enable_server_certificate_verification(false); + cli.set_connection_timeout(15, 0); + + size_t body_size = 10000; + char* body = (char*)memops::alloc(body_size); + + const char* json = "{\n" + " \"from\": {\n" + " \"email\": \"%s\",\n" + " \"name\": \"%s\"\n" + " },\n" + " \"to\": [\n" + " {\n" + " \"email\": \"%s\",\n" + " \"name\": \"%s\"\n" + " }\n" + " ],\n" + " \"subject\": \"%s\",\n" + " \"text\": \"%s.\"\n" + "}"; + + strops::format(body, body_size, json, sender, sender, recipient, recipient, subject, text); + + httplib::Headers headers; + headers.insert(std::make_pair("Authorization", std::string("Bearer ") + api_key)); + headers.insert(std::make_pair("Content-Type", "application/json")); + + httplib::Result res = cli.Post("/v1/email", headers, body, "application/json"); + memops::unalloc(body); + + if (!res || (res->status != 200 && res->status != 202)) { + logger::error("Failed to send email."); + return E_ERR_FAILED_REQUEST; + } + + logger::info("Email sent."); + return E_ERR_SUCCESS; } -importer::email_provider_impl _mailersend_api_provider = { +exporter::email_provider_impl _mailersend_api_provider = { "MailerSend", _MailerSend_send_email, }; \ No newline at end of file diff --git a/src/ui/ui_invoices.cpp b/src/ui/ui_invoices.cpp index dc51fd6..6a50c7f 100644 --- a/src/ui/ui_invoices.cpp +++ b/src/ui/ui_invoices.cpp @@ -22,6 +22,8 @@ #include "memops.hpp" #include "strops.hpp" #include "locales.hpp" +#include "importer.hpp" +#include "exporter.hpp" #include "administration.hpp" #include "administration_writer.hpp" @@ -501,7 +503,7 @@ static void draw_invoice_view() if (ImGui::BeginCombo("##Send", locale::get("ui.sendAs"))) { if (ImGui::Selectable(locale::get("ui.sendAs.email"), false)) { - + exporter::send_email("test@test-vz9dlemj2564kj50.mlsender.net", "aldrikboy@gmail.com", "test", "test 123"); } // if (ImGui::Selectable(locale::get("ui.sendAs.einvoice"), false)) { diff --git a/src/ui/ui_settings.cpp b/src/ui/ui_settings.cpp index 240e376..fd0d5b9 100644 --- a/src/ui/ui_settings.cpp +++ b/src/ui/ui_settings.cpp @@ -20,6 +20,7 @@ #include "memops.hpp" #include "locales.hpp" #include "importer.hpp" +#include "exporter.hpp" #include "countries.hpp" #include "administration.hpp" #include "administration_writer.hpp" @@ -343,7 +344,7 @@ static void draw_email_service_ui() { char* email_service_names[EMAIL_PROVIDER_END]; for (u32 i = 0; i < EMAIL_PROVIDER_END; i++) { - email_service_names[i] = importer::get_email_provider_implementation((email_provider)i).provider_name; + email_service_names[i] = exporter::get_email_provider_implementation((email_provider)i).provider_name; } if (ImGui::BeginCombo(locale::get("settings.services.email_service.provider"), email_service_names[new_ai_service.provider])) -- cgit v1.2.3-70-g09d2