private async Task <JobStatus> SendBulkListAsync(int userId, int jobId, IProgress <JobStatus> progress, JobDetailsSendBulkEmails jobDetails, CancellationToken token) { var sw = System.Diagnostics.Stopwatch.StartNew(); int emailsSent = 0; int emailsSkipped = 0; int userCounter = 0; int addSentCounter = 0; var problemEmails = new HashSet <string>(); var filter = new EmailReminderFilter { MailingList = jobDetails.MailingList, Take = 30 }; var subscribers = await _emailReminderService.GetSubscribersWithCountAsync(filter); var subscribedCount = subscribers.Count; var subscribed = subscribers.Data; _logger.LogInformation("Email job {JobId}: found {Count} subscribed users, processing first batch of {BatchCount}", jobId, subscribedCount, subscribed.Count); token.Register(() => { _logger.LogWarning("Email job {JobId} for user {UserId} was cancelled after {EmailsSent} sent, {EmailsSkipped} skipped of {SubscribedUsersCount} total in {TimeElapsed}.", jobId, userId, emailsSent, emailsSkipped, subscribedCount, sw.Elapsed.ToString(SpanFormat, CultureInfo.InvariantCulture)); }); if (subscribed.Count > 0) { var site = await _siteLookupService.GetByIdAsync(GetCurrentSiteId()); var emailDetails = new DirectEmailDetails(site.Name) { IsBulk = true, SendingUserId = userId, DirectEmailTemplateId = jobDetails.EmailTemplateId }; var elapsedStatus = sw.Elapsed; var elapsedUpdateDbStatus = sw.Elapsed; var elapsedLogInfoStatus = sw.Elapsed; var elapsedLogInfoPercent = 0; progress.Report(new JobStatus { PercentComplete = 0, Title = $"Sending email...", Status = $"Preparing to email {subscribed.Count} participants...", Error = false }); while (subscribed.Count > 0) { foreach (var emailReminder in subscribed) { if (problemEmails.Contains(emailReminder.Email)) { emailsSkipped++; continue; } bool clearToSend = true; var isParticipant = await _userService .IsEmailSubscribedAsync(emailReminder.Email); if (isParticipant) { clearToSend = false; } if (emailReminder.SentAt != null || !clearToSend) { // send email _logger.LogTrace("Email job {JobId}: skipping email {Count}/{Total} to {Email}: {Message}", jobId, userCounter + 1, subscribedCount, emailReminder.Email, emailReminder.SentAt != null ? " already sent at " + emailReminder.SentAt : " is a subscribed participant"); emailsSkipped++; } else { // send email _logger.LogTrace("Email job {JobId}: sending email {Count}/{Total} to {Email} with template {EmailTemplate}", jobId, userCounter + 1, subscribedCount, emailReminder.Email, jobDetails.EmailTemplateId); // send email to user try { emailDetails.ToAddress = emailReminder.Email; emailDetails.LanguageId = emailReminder.LanguageId; emailDetails.ClearTags(); emailDetails.SetTag("Email", emailReminder.Email); DirectEmailHistory result = null; try { result = await _emailService.SendDirectAsync(emailDetails); await _emailReminderService.UpdateSentDateAsync(emailReminder.Id); } catch (Exception ex) { _logger.LogWarning("Unable to email {ToAddress}: {ErrorMessage}", emailDetails.ToAddress, ex.Message); } if (result?.Successful == true) { addSentCounter++; emailsSent++; } else { problemEmails.Add(emailReminder.Email); } } catch (Exception ex) { _logger.LogError(ex, "Email job {JobId}: Send failed to {UserId} at {Email}: {ErrorMessage}", jobId, emailReminder.Id, emailReminder.Email, ex.Message); problemEmails.Add(emailReminder.Email); } } userCounter++; if (token.IsCancellationRequested) { break; } if (sw.Elapsed.TotalSeconds - elapsedStatus.TotalSeconds > 5 || userCounter == 1) { elapsedStatus = sw.Elapsed; var remaining = TimeSpan .FromMilliseconds(elapsedStatus.TotalMilliseconds / userCounter * (subscribedCount - userCounter)) .ToString(SpanFormat, CultureInfo.InvariantCulture); var status = new JobStatus { PercentComplete = userCounter * 100 / subscribedCount, Status = $"Sent {emailsSent}, skipped {emailsSkipped} of {subscribedCount}; {elapsedStatus.ToString(SpanFormat, CultureInfo.InvariantCulture)}, remaining: {remaining}, problems: {problemEmails.Count}", Error = false }; progress.Report(status); if (sw.Elapsed.TotalSeconds - elapsedUpdateDbStatus.TotalSeconds > 60 || userCounter == 1) { elapsedUpdateDbStatus = sw.Elapsed; if (addSentCounter > 0) { await _emailService.IncrementSentCountAsync( jobDetails.EmailTemplateId, addSentCounter); addSentCounter = 0; } var dbStatusText = string.Format(CultureInfo.InvariantCulture, "{0}%: {1}", status.PercentComplete, status.Status); await _jobRepository.UpdateStatusAsync(jobId, dbStatusText[..Math.Min(dbStatusText.Length, 255)]);
public async Task <Models.ServiceResult> EmailAllUsernames(string email) { var site = await _siteLookupService.GetByIdAsync(GetCurrentSiteId()); var lookupEmail = email.Trim(); var usernames = await _userRepository.GetUserIdAndUsernames(lookupEmail); if (usernames?.Data.Any() != true) { return(new Models.ServiceResult { Status = Models.ServiceResultStatus.Error, Message = "There are no usernames associated with the email address: {0}.", Arguments = new[] { lookupEmail } }); } var sb = new StringBuilder(); foreach (string username in usernames.Data) { sb.Append("- ").AppendLine(username); } var directEmailDetails = new DirectEmailDetails(site.Name) { DirectEmailSystemId = "UsernameRecovery", LanguageId = await _languageService .GetLanguageIdAsync(CultureInfo.CurrentUICulture.Name), SendingUserId = await _userRepository.GetSystemUserId(), ToUserId = usernames.Id }; directEmailDetails.Tags.Add("Email", lookupEmail); directEmailDetails.Tags.Add("Content", sb.ToString()); var siteLink = await _siteLookupService.GetSiteLinkAsync(site.Id); directEmailDetails.Tags.Add("Sitelink", siteLink.AbsoluteUri); var result = new Models.ServiceResult(); try { var history = await _emailService.SendDirectAsync(directEmailDetails); result.Status = history?.Successful == true ? Models.ServiceResultStatus.Success : Models.ServiceResultStatus.Error; } catch (GraException ex) { if (ex?.InnerException is MimeKit.ParseException) { result.Status = Models.ServiceResultStatus.Error; result.Message = Annotations.Validate.EmailAddressInvalid; result.Arguments = new[] { email }; } } return(result); }
public async Task <User> Update(User userToUpdate) { int requestingUserId = GetActiveUserId(); if (requestingUserId == userToUpdate.Id) { // users can only update some of their own fields var currentEntity = await _userRepository.GetByIdAsync(userToUpdate.Id); currentEntity.IsAdmin = await UserHasRoles(userToUpdate.Id); currentEntity.Age = userToUpdate.Age; currentEntity.AvatarId = userToUpdate.AvatarId; currentEntity.BranchName = null; currentEntity.CardNumber = userToUpdate.CardNumber?.Trim(); currentEntity.DailyPersonalGoal = userToUpdate.DailyPersonalGoal; currentEntity.Email = userToUpdate.Email?.Trim(); currentEntity.FirstName = userToUpdate.FirstName?.Trim(); currentEntity.IsHomeschooled = userToUpdate.IsHomeschooled; currentEntity.LastName = userToUpdate.LastName?.Trim(); currentEntity.PhoneNumber = userToUpdate.PhoneNumber?.Trim(); currentEntity.PostalCode = userToUpdate.PostalCode?.Trim(); currentEntity.ProgramId = userToUpdate.ProgramId; currentEntity.ProgramName = null; currentEntity.SchoolId = userToUpdate.SchoolId; currentEntity.SchoolNotListed = userToUpdate.SchoolNotListed; currentEntity.SystemName = null; //currentEntity.Username = userToUpdate.Username; bool restrictChangingSystemBranch = await _siteLookupService .GetSiteSettingBoolAsync(currentEntity.SiteId, SiteSettingKey.Users.RestrictChangingSystemBranch); if (!restrictChangingSystemBranch) { currentEntity.SystemId = userToUpdate.SystemId; currentEntity.BranchId = userToUpdate.BranchId; } var askEmailReminder = await _siteLookupService.GetSiteSettingBoolAsync( currentEntity.SiteId, SiteSettingKey.Users.AskPreregistrationReminder); if (askEmailReminder) { var site = await _siteLookupService.GetByIdAsync(currentEntity.SiteId); if (_siteLookupService.GetSiteStage(site) == SiteStage.RegistrationOpen) { currentEntity.PreregistrationReminderRequested = userToUpdate.PreregistrationReminderRequested; } } await ValidateUserFields(currentEntity); var updatedUser = await _userRepository .UpdateSaveAsync(requestingUserId, currentEntity); return(updatedUser); } else { _logger.LogError($"User {requestingUserId} doesn't have permission to update user {userToUpdate.Id}."); throw new GraException("Permission denied."); } }
public async Task <DirectEmailHistory> SendDirectAsync(DirectEmailDetails directEmailDetails) { if (directEmailDetails == null) { throw new ArgumentNullException(nameof(directEmailDetails)); } string toAddress; string toName; int languageId; Site site; if (directEmailDetails.ToUserId.HasValue) { var user = await _userRepository.GetByIdAsync(directEmailDetails.ToUserId ?? directEmailDetails.SendingUserId); if (string.IsNullOrEmpty(user.Email)) { _logger.LogError("Unable to send email to user id {UserId}: no email address configured.", directEmailDetails.ToUserId); throw new GraException($"User id {directEmailDetails.ToUserId} does not have an email address configured."); } site = await _siteLookupService.GetByIdAsync(user.SiteId); toAddress = user.Email; toName = user.FullName; languageId = directEmailDetails.LanguageId ?? (string.IsNullOrEmpty(user.Culture) ? await _languageService.GetDefaultLanguageIdAsync() : await _languageService.GetLanguageIdAsync(user.Culture)); } else { var user = await _userRepository.GetByIdAsync(directEmailDetails.SendingUserId); site = await _siteLookupService.GetByIdAsync(user.SiteId); toAddress = directEmailDetails.ToAddress; toName = directEmailDetails.ToName; languageId = directEmailDetails.LanguageId ?? await _languageService.GetLanguageIdAsync(CultureInfo.CurrentCulture.Name); } if (!SiteCanSendMail(site)) { throw new GraException("Unable to send mail, please ensure from name, from email, and outgoing mail server are configured in Site Management -> Configuration."); } var history = new DirectEmailHistory { CreatedBy = directEmailDetails.SendingUserId, FromEmailAddress = site.FromEmailAddress, FromName = site.FromEmailName, IsBulk = directEmailDetails.IsBulk, LanguageId = languageId, OverrideToEmailAddress = string .IsNullOrWhiteSpace(_config[ConfigurationKey.EmailOverride]) ? null : _config[ConfigurationKey.EmailOverride], ToEmailAddress = toAddress, ToName = toName }; if (directEmailDetails.ToUserId.HasValue) { history.UserId = directEmailDetails.ToUserId.Value; } DirectEmailTemplate directEmailTemplate = await GetDirectEmailTemplateAsync(directEmailDetails.DirectEmailSystemId, directEmailDetails.DirectEmailTemplateId, history.LanguageId); if (directEmailTemplate == null || directEmailTemplate.DirectEmailTemplateText == null) { // not available in the requested language, use the default language history.LanguageId = await _languageService.GetDefaultLanguageIdAsync(); directEmailTemplate = await GetDirectEmailTemplateAsync(directEmailDetails.DirectEmailSystemId, directEmailDetails.DirectEmailTemplateId, history.LanguageId); } history.DirectEmailTemplateId = directEmailTemplate.Id; history.EmailBaseId = directEmailTemplate.EmailBaseId; var stubble = new StubbleBuilder().Build(); history.Subject = await stubble .RenderAsync(directEmailTemplate.DirectEmailTemplateText.Subject, directEmailDetails.Tags); history.BodyText = await stubble .RenderAsync(directEmailTemplate.DirectEmailTemplateText.BodyCommonMark, directEmailDetails.Tags); history.BodyHtml = CommonMark.CommonMarkConverter.Convert(history.BodyText); string preview = await stubble .RenderAsync(directEmailTemplate.DirectEmailTemplateText.Preview, directEmailDetails.Tags); string title = await stubble .RenderAsync(directEmailTemplate.DirectEmailTemplateText.Title, directEmailDetails.Tags); string footer = CommonMark.CommonMarkConverter.Convert(await stubble .RenderAsync(directEmailTemplate.DirectEmailTemplateText.Footer, directEmailDetails.Tags)); history = await InternalSendDirectAsync(site, history, new Dictionary <string, string> { { "Footer", footer }, { "Preview", preview }, { "Title", title }, { "BodyHtml", history.BodyHtml }, { "BodyText", history.BodyText } }); if (directEmailDetails.IsBulk && !directEmailDetails.IsTest) { if (directEmailDetails.ToUserId.HasValue) { history.BodyHtml = null; history.BodyText = null; await _directEmailHistoryRepository.AddSaveNoAuditAsync(history); } if (!directEmailTemplate.SentBulk) { await _directEmailTemplateRepository .UpdateSentBulkAsync(directEmailTemplate.Id); } } else { if (!directEmailDetails.IsTest) { await _directEmailHistoryRepository.AddSaveNoAuditAsync(history); } await IncrementSentCountAsync(directEmailTemplate.Id); } return(history); }