diff --git a/.vscode/launch.json b/.vscode/launch.json index 1a4bbd25..c7705a87 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -119,7 +119,7 @@ "AYANOVA_DB_CONNECTION": "Server=localhost;Username=postgres;Password=raven;Database=AyaNova;CommandTimeout=300;", "AYANOVA_DATA_PATH": "c:\\temp\\ravendata", "AYANOVA_USE_URLS": "http://*:7575;", - "AYANOVA_PERMANENTLY_ERASE_DATABASE":"true", + //"AYANOVA_PERMANENTLY_ERASE_DATABASE":"true", //"AYANOVA_REMOVE_LICENSE_FROM_DB":"true", //"AYANOVA_SET_SUPERUSER_PW": "abraxis", "AYANOVA_BACKUP_PG_DUMP_PATH": "C:\\data\\code\\postgres_14\\bin" diff --git a/docs/8.0/ayanova/docs/ay-ex-direct-smtp.md b/docs/8.0/ayanova/docs/ay-ex-direct-smtp.md index c26da12b..1b7102e8 100644 --- a/docs/8.0/ayanova/docs/ay-ex-direct-smtp.md +++ b/docs/8.0/ayanova/docs/ay-ex-direct-smtp.md @@ -2,7 +2,7 @@ ![send email extension](img/ay-ex-direct-smtp.png) -The Send email extension can be used to send an email to the selected objects email address. +The Send email extension can be used to send an email to the selected (Active) object's email address. ## Authorization Roles required @@ -18,6 +18,6 @@ The Send email extension is accessed from the [extensions](ay-extensions.md) men ## How the Send email extension works -Compose a message and start the job. AyaNova will attempt to send the message to the email address set for all selected objects with a small delay between each to avoid overloading your email server. Objects without an email address will be ignored. +Compose a message and start the job. AyaNova will attempt to send the message to the email address set for all selected objects with a small delay between each to avoid overloading your email server. Objects that are not set to Active or objects without an email address will be ignored. **WARNING**: This extension allows you to mass email multiple objects at a time. AyaNova will automatically add a small delay between sending each message to prevent potentially overwhelming the email server, however, there may be limits on how many emails can be sent within a given time period so be sure to check with your email service provider to avoid potentially violating anti-spam policies. diff --git a/server/AyaNova/Controllers/JobOperationsController.cs b/server/AyaNova/Controllers/JobOperationsController.cs index efceb9aa..032862f4 100644 --- a/server/AyaNova/Controllers/JobOperationsController.cs +++ b/server/AyaNova/Controllers/JobOperationsController.cs @@ -281,6 +281,18 @@ namespace AyaNova.Api.Controllers if (batchDirectSMTPParams.SelectedRequest == null) return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_REQUIRED, null, "batchDirectSMTPParams.DataListSelectedRequest is required")); + switch (batchDirectSMTPParams.SelectedRequest.AType) + { + case AyaType.Customer: + case AyaType.HeadOffice: + case AyaType.Vendor: + case AyaType.User: + break; + default: + return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, "AType", "Specified Type not supported")); + } + + if (!Authorized.HasModifyRole(HttpContext.Items, batchDirectSMTPParams.SelectedRequest.AType)) return StatusCode(403, new ApiNotAuthorizedResponse()); @@ -295,10 +307,12 @@ namespace AyaNova.Api.Controllers UserIdFromContext.Id(HttpContext.Items), UserTranslationIdFromContext.Id(HttpContext.Items)); - var JobName = $"LT:BatchDirectSMTP - LT:{batchDirectSMTPParams.SelectedRequest.AType} ({batchDirectSMTPParams.SelectedRequest.SelectedRowIds.LongLength}) LT:User {UserNameFromContext.Name(HttpContext.Items)}"; + var JobName = $"LT:BatchDirectSMTP - LT:{batchDirectSMTPParams.SelectedRequest.AType} ({batchDirectSMTPParams.SelectedRequest.SelectedRowIds.LongLength}), LT:User {UserNameFromContext.Name(HttpContext.Items)}, LT:MemoSubject '{batchDirectSMTPParams.Subject}'"; JObject o = JObject.FromObject(new { - idList = batchDirectSMTPParams.SelectedRequest.SelectedRowIds + idList = batchDirectSMTPParams.SelectedRequest.SelectedRowIds, + subject = batchDirectSMTPParams.Subject, + message = batchDirectSMTPParams.TextBody }); OpsJob j = new OpsJob(); diff --git a/server/AyaNova/biz/CustomerBiz.cs b/server/AyaNova/biz/CustomerBiz.cs index 9de4cea6..f997a69f 100644 --- a/server/AyaNova/biz/CustomerBiz.cs +++ b/server/AyaNova/biz/CustomerBiz.cs @@ -647,9 +647,6 @@ namespace AyaNova.Biz SaveIt = false; ClearErrors(); Customer o = null; - //save a fetch if it's a delete - if (job.SubType != JobSubType.Delete) - o = await GetAsync(id, false); switch (job.SubType) { case JobSubType.TagAddAny: @@ -658,6 +655,7 @@ namespace AyaNova.Biz case JobSubType.TagRemove: case JobSubType.TagReplaceAny: case JobSubType.TagReplace: + o = await GetAsync(id, false); SaveIt = TagBiz.ProcessBatchTagOperation(o.Tags, (string)jobData["tag"], jobData.ContainsKey("toTag") ? (string)jobData["toTag"] : null, job.SubType); break; case JobSubType.Delete: @@ -667,6 +665,23 @@ namespace AyaNova.Biz FailedObjectCount++; } break; + case JobSubType.DirectSMTP: + o = await GetAsync(id, false); + if (o != null && o.Active && !string.IsNullOrWhiteSpace(o.EmailAddress)) + { + IMailer m = AyaNova.Util.ServiceProviderProvider.Mailer; + try + { + await m.SendEmailAsync(o.EmailAddress, (string)jobData["subject"], (string)jobData["message"], ServerGlobalOpsSettingsCache.Notify, null, null, null); + await Task.Delay(AyaNova.Util.ServerBootConfig.JOB_OBJECT_EMAIL_LOOP_DELAY);//a small delay to not overwhelm the mail server + } + catch (Exception ex) + { + FailedObjectCount++; + await NotifyEventHelper.AddOpsProblemEvent("SMTP direct message failed", ex); + } + } + break; default: throw new System.ArgumentOutOfRangeException($"ProcessBatchJobAsync -> Invalid job Subtype{job.SubType}"); } diff --git a/server/AyaNova/biz/HeadOfficeBiz.cs b/server/AyaNova/biz/HeadOfficeBiz.cs index 80d927e4..19f779b7 100644 --- a/server/AyaNova/biz/HeadOfficeBiz.cs +++ b/server/AyaNova/biz/HeadOfficeBiz.cs @@ -218,7 +218,7 @@ namespace AyaNova.Biz .AddText(obj.Address) .AddText(obj.City) .AddText(obj.Region) - .AddText(obj.Country) + .AddText(obj.Country) .AddText(obj.AddressPostal) .AddCustomFields(obj.CustomFields); } @@ -525,9 +525,6 @@ namespace AyaNova.Biz SaveIt = false; ClearErrors(); HeadOffice o = null; - //save a fetch if it's a delete - if (job.SubType != JobSubType.Delete) - o = await GetAsync(id, false); switch (job.SubType) { case JobSubType.TagAddAny: @@ -536,6 +533,7 @@ namespace AyaNova.Biz case JobSubType.TagRemove: case JobSubType.TagReplaceAny: case JobSubType.TagReplace: + o = await GetAsync(id, false); SaveIt = TagBiz.ProcessBatchTagOperation(o.Tags, (string)jobData["tag"], jobData.ContainsKey("toTag") ? (string)jobData["toTag"] : null, job.SubType); break; case JobSubType.Delete: @@ -545,6 +543,23 @@ namespace AyaNova.Biz FailedObjectCount++; } break; + case JobSubType.DirectSMTP: + o = await GetAsync(id, false); + if (o != null && o.Active && !string.IsNullOrWhiteSpace(o.EmailAddress)) + { + IMailer m = AyaNova.Util.ServiceProviderProvider.Mailer; + try + { + await m.SendEmailAsync(o.EmailAddress, (string)jobData["subject"], (string)jobData["message"], ServerGlobalOpsSettingsCache.Notify, null, null, null); + await Task.Delay(AyaNova.Util.ServerBootConfig.JOB_OBJECT_EMAIL_LOOP_DELAY);//a small delay to not overwhelm the mail server + } + catch (Exception ex) + { + FailedObjectCount++; + await NotifyEventHelper.AddOpsProblemEvent("SMTP direct message failed", ex); + } + } + break; default: throw new System.ArgumentOutOfRangeException($"ProcessBatchJobAsync -> Invalid job Subtype{job.SubType}"); } diff --git a/server/AyaNova/biz/UserBiz.cs b/server/AyaNova/biz/UserBiz.cs index 2a8bbd08..391717c6 100644 --- a/server/AyaNova/biz/UserBiz.cs +++ b/server/AyaNova/biz/UserBiz.cs @@ -1262,9 +1262,6 @@ namespace AyaNova.Biz //a little different than normal here because the built in getasync doesn't return //a full User object normally User o = null; - //save a fetch if it's a delete - if (job.SubType != JobSubType.Delete) - o = await GetAsync(id, false); switch (job.SubType) { case JobSubType.TagAddAny: @@ -1273,6 +1270,7 @@ namespace AyaNova.Biz case JobSubType.TagRemove: case JobSubType.TagReplaceAny: case JobSubType.TagReplace: + o = await GetAsync(id, false); SaveIt = TagBiz.ProcessBatchTagOperation(o.Tags, (string)jobData["tag"], jobData.ContainsKey("toTag") ? (string)jobData["toTag"] : null, job.SubType); break; case JobSubType.Delete: @@ -1282,6 +1280,23 @@ namespace AyaNova.Biz FailedObjectCount++; } break; + case JobSubType.DirectSMTP: + o = await ct.User.AsNoTracking().Include(o => o.UserOptions).FirstOrDefaultAsync(z => z.Id == id); + if (o != null && o.Active && !string.IsNullOrWhiteSpace(o.UserOptions.EmailAddress)) + { + IMailer m = AyaNova.Util.ServiceProviderProvider.Mailer; + try + { + await m.SendEmailAsync(o.UserOptions.EmailAddress, (string)jobData["subject"], (string)jobData["message"], ServerGlobalOpsSettingsCache.Notify, null, null, null); + await Task.Delay(AyaNova.Util.ServerBootConfig.JOB_OBJECT_EMAIL_LOOP_DELAY);//a small delay to not overwhelm the mail server + } + catch (Exception ex) + { + FailedObjectCount++; + await NotifyEventHelper.AddOpsProblemEvent("SMTP direct message failed", ex); + } + } + break; default: throw new System.ArgumentOutOfRangeException($"ProcessBatchJobAsync -> Invalid job Subtype{job.SubType}"); } diff --git a/server/AyaNova/biz/VendorBiz.cs b/server/AyaNova/biz/VendorBiz.cs index 40d443da..3f8b185a 100644 --- a/server/AyaNova/biz/VendorBiz.cs +++ b/server/AyaNova/biz/VendorBiz.cs @@ -457,9 +457,6 @@ namespace AyaNova.Biz SaveIt = false; ClearErrors(); Vendor o = null; - //save a fetch if it's a delete - if (job.SubType != JobSubType.Delete) - o = await GetAsync(id, false); switch (job.SubType) { case JobSubType.TagAddAny: @@ -468,6 +465,7 @@ namespace AyaNova.Biz case JobSubType.TagRemove: case JobSubType.TagReplaceAny: case JobSubType.TagReplace: + o = await GetAsync(id, false); SaveIt = TagBiz.ProcessBatchTagOperation(o.Tags, (string)jobData["tag"], jobData.ContainsKey("toTag") ? (string)jobData["toTag"] : null, job.SubType); break; case JobSubType.Delete: @@ -477,6 +475,23 @@ namespace AyaNova.Biz FailedObjectCount++; } break; + case JobSubType.DirectSMTP: + o = await GetAsync(id, false); + if (o != null && o.Active && !string.IsNullOrWhiteSpace(o.EmailAddress)) + { + IMailer m = AyaNova.Util.ServiceProviderProvider.Mailer; + try + { + await m.SendEmailAsync(o.EmailAddress, (string)jobData["subject"], (string)jobData["message"], ServerGlobalOpsSettingsCache.Notify, null, null, null); + await Task.Delay(AyaNova.Util.ServerBootConfig.JOB_OBJECT_EMAIL_LOOP_DELAY);//a small delay to not overwhelm the mail server + } + catch (Exception ex) + { + FailedObjectCount++; + await NotifyEventHelper.AddOpsProblemEvent("SMTP direct message failed", ex); + } + } + break; default: throw new System.ArgumentOutOfRangeException($"ProcessBatchJobAsync -> Invalid job Subtype{job.SubType}"); } diff --git a/server/AyaNova/util/ServerBootConfig.cs b/server/AyaNova/util/ServerBootConfig.cs index 329d2287..721ea6d3 100644 --- a/server/AyaNova/util/ServerBootConfig.cs +++ b/server/AyaNova/util/ServerBootConfig.cs @@ -17,6 +17,7 @@ namespace AyaNova.Util internal const int FAILED_AUTH_DELAY = 3000;//ms internal const int JOB_OBJECT_HANDLE_BATCH_JOB_LOOP_DELAY = 200;//ms this delay is a temporary measure to ensure super big time consuming batch jobs don't use all server CPU resources internal const int JOB_PROGRESS_UPDATE_AND_CANCEL_CHECK_SECONDS = 5;//seconds between progress updates and checks for cancellation of long running jobs + internal const int JOB_OBJECT_EMAIL_LOOP_DELAY = 500;//ms this delay ensures multiple email sendings in a job don't overwhelm the mail server //UPLOAD LIMITS 1048576 = 1MiB for testing 10737420000 10737418240 10,737,418,240 internal const long MAX_ATTACHMENT_UPLOAD_BYTES = 10737420000;//slight bit of overage as 10737418241=10GiB