Beispiel #1
0
        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)]);
Beispiel #2
0
        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);
        }
Beispiel #3
0
        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.");
            }
        }
Beispiel #4
0
        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);
        }