public async Task DistributeScheduledAsync(UserEventMessage userEvent, bool isLastAttempt)
        {
            await userNotificationsStore.TrackAttemptAsync(userEvent);

            try
            {
                var user = await userStore.GetCachedAsync(userEvent.AppId, userEvent.UserId);

                if (user == null)
                {
                    throw new DomainException(Texts.Notification_NoApp);
                }

                var app = await appStore.GetCachedAsync(userEvent.AppId, default);

                if (app == null)
                {
                    throw new DomainException(Texts.Notification_NoUser);
                }

                var(notification, targets) = await CreateUserNotificationAsync(userEvent, user, app);

                try
                {
                    await userNotificationsStore.InsertAsync(notification);
                }
                catch (UniqueConstraintException)
                {
                    throw new DomainException(Texts.Notification_AlreadyProcessed);
                }

                foreach (var channel in targets)
                {
                    if (notification.Settings.TryGetValue(channel.Name, out var preference))
                    {
                        await channel.SendAsync(notification, preference, user, app, false);
                    }
                }
            }
            catch (Exception ex)
            {
                if (isLastAttempt)
                {
                    await userNotificationsStore.TrackFailedAsync(userEvent);
                }

                if (ex is DomainException domainException)
                {
                    await logStore.LogAsync(userEvent.AppId, domainException.Message);
                }
                else
                {
                    throw;
                }
            }
        }
예제 #2
0
        public async Task <IActionResult> Confirm(string id, [FromQuery] string?channel = null, [FromQuery] string?deviceIdentifier = null)
        {
            var token = TrackingToken.Parse(id, channel, deviceIdentifier);

            await userNotificationService.TrackConfirmedAsync(token);

            var notification = await userNotificationStore.FindAsync(token.Id, HttpContext.RequestAborted);

            if (notification == null)
            {
                return(View());
            }

            var app = await appStore.GetCachedAsync(notification.AppId, HttpContext.RequestAborted);

            if (app?.ConfirmUrl != null && Uri.IsWellFormedUriString(app.ConfirmUrl, UriKind.Absolute))
            {
                var url = app.ConfirmUrl !;

                if (url.Contains('?', StringComparison.OrdinalIgnoreCase))
                {
                    url += $"&id={id:notEmpty}";
                }
                else
                {
                    url += $"?id={id:notEmpty}";
                }

                return(Redirect(url));
            }

            return(View());
        }
예제 #3
0
        public Task SendAsync(List <UserNotification> notifications, bool isLastAttempt, CancellationToken ct)
        {
            return(log.ProfileAsync("SendEmail", async() =>
            {
                await UpdateAsync(notifications, ProcessStatus.Attempt);

                var first = notifications[0];

                var app = await appStore.GetCachedAsync(first.AppId, ct);

                if (app == null)
                {
                    log.LogWarning(w => w
                                   .WriteProperty("action", "SendEmail")
                                   .WriteProperty("status", "Failed")
                                   .WriteProperty("reason", "App not found"));

                    return;
                }

                try
                {
                    var user = await userStore.GetCachedAsync(first.AppId, first.UserId, ct);

                    if (user == null)
                    {
                        throw new DomainException(Texts.Email_UserNoEmail);
                    }

                    if (string.IsNullOrWhiteSpace(user.EmailAddress))
                    {
                        throw new DomainException(Texts.Email_UserNoEmail);
                    }

                    await SendAsync(notifications, app, user, ct);

                    await UpdateAsync(notifications, ProcessStatus.Handled);
                }
                catch (Exception ex)
                {
                    if (isLastAttempt)
                    {
                        await UpdateAsync(notifications, ProcessStatus.Failed);
                    }

                    if (ex is DomainException domainException)
                    {
                        await logStore.LogAsync(app.Id, domainException.Message);
                    }
                    else
                    {
                        throw;
                    }
                }
            }));
예제 #4
0
        private async Task SendJobAsync(MessagingJob job,
                                        CancellationToken ct)
        {
            using (Telemetry.Activities.StartActivity("Send"))
            {
                var app = await appStore.GetCachedAsync(job.Notification.AppId, ct);

                if (app == null)
                {
                    log.LogWarning("Cannot send message: App not found.");

                    await UpdateAsync(job.Notification, ProcessStatus.Handled);

                    return;
                }

                try
                {
                    await UpdateAsync(job.Notification, ProcessStatus.Attempt);

                    var senders = integrationManager.Resolve <IMessagingSender>(app, job.Notification).Select(x => x.Target).ToList();

                    if (senders.Count == 0)
                    {
                        await SkipAsync(job, Texts.Messaging_ConfigReset);

                        return;
                    }

                    var(skip, template) = await GetTemplateAsync(
                        job.Notification.AppId,
                        job.Notification.UserLanguage,
                        job.NotificationTemplate,
                        ct);

                    if (skip != null)
                    {
                        await SkipAsync(job, skip);

                        return;
                    }

                    var text = messagingFormatter.Format(template, job.Notification);

                    await SendCoreAsync(job, text, senders, ct);
                }
                catch (DomainException ex)
                {
                    await logStore.LogAsync(job.Notification.AppId, Name, ex.Message);

                    throw;
                }
            }
        }
예제 #5
0
        public Task SendAsync(MobilePushJob job, bool isLastAttempt, CancellationToken ct)
        {
            return(log.ProfileAsync("SendMobilePush", async() =>
            {
                var notification = job.Notification;

                var app = await appStore.GetCachedAsync(notification.AppId, ct);

                if (app == null)
                {
                    log.LogWarning(w => w
                                   .WriteProperty("action", "SendMobilePush")
                                   .WriteProperty("status", "Failed")
                                   .WriteProperty("reason", "App not found"));

                    return;
                }

                try
                {
                    await UpdateAsync(notification, ProcessStatus.Attempt);

                    if (!IsFirebaseConfigured(app))
                    {
                        throw new DomainException(Texts.MobilePush_ConfigReset);
                    }

                    var messaging = pool.GetMessaging(app);

                    await SendAnyAsync(job, messaging, ct);

                    await UpdateAsync(notification, ProcessStatus.Handled);
                }
                catch (Exception ex)
                {
                    if (isLastAttempt)
                    {
                        await UpdateAsync(notification, ProcessStatus.Failed);
                    }

                    if (ex is DomainException domainException)
                    {
                        await logStore.LogAsync(app.Id, domainException.Message);
                    }
                    else
                    {
                        throw;
                    }
                }
            }));
예제 #6
0
        private async Task SendJobAsync(MobilePushJob job,
                                        CancellationToken ct)
        {
            using (Telemetry.Activities.StartActivity("SendMobilePush"))
            {
                var notification = job.Notification;

                var app = await appStore.GetCachedAsync(notification.AppId, ct);

                if (app == null)
                {
                    log.LogWarning("Cannot send email: App not found.");

                    await UpdateAsync(job, ProcessStatus.Handled);

                    return;
                }

                try
                {
                    await UpdateAsync(job, ProcessStatus.Attempt);

                    var senders = integrationManager.Resolve <IMobilePushSender>(app, notification).Select(x => x.Target).ToList();

                    if (senders.Count == 0)
                    {
                        await SkipAsync(job, Texts.Sms_ConfigReset);

                        return;
                    }

                    await SendCoreAsync(job, app, senders, ct);

                    await UpdateAsync(job, ProcessStatus.Handled);
                }
                catch (DomainException ex)
                {
                    await logStore.LogAsync(app.Id, Name, ex.Message);

                    throw;
                }
            }
        }
예제 #7
0
        public async Task SendJobsAsync(List <EmailJob> jobs,
                                        CancellationToken ct)
        {
            using (Telemetry.Activities.StartActivity("Send"))
            {
                var first = jobs[0];

                var commonEmail = first.EmailAddress;
                var commonApp   = first.Notification.AppId;
                var commonUser  = first.Notification.UserId;

                await UpdateAsync(first.Notification, commonEmail, ProcessStatus.Attempt);

                var app = await appStore.GetCachedAsync(first.Notification.AppId, ct);

                if (app == null)
                {
                    log.LogWarning("Cannot send email: App not found.");

                    await UpdateAsync(jobs, commonEmail, ProcessStatus.Handled);

                    return;
                }

                try
                {
                    var user = await userStore.GetCachedAsync(commonApp, commonUser, ct);

                    if (user == null)
                    {
                        await SkipAsync(jobs, commonEmail, Texts.Email_UserDeleted);

                        return;
                    }

                    if (string.IsNullOrWhiteSpace(user.EmailAddress))
                    {
                        await SkipAsync(jobs, commonEmail, Texts.Email_UserNoEmail);

                        return;
                    }

                    var senders = integrationManager.Resolve <IEmailSender>(app, first.Notification).Select(x => x.Target).ToList();

                    if (senders.Count == 0)
                    {
                        await SkipAsync(jobs, commonEmail, Texts.Email_ConfigReset);

                        return;
                    }

                    EmailMessage?message;

                    using (Telemetry.Activities.StartActivity("Format"))
                    {
                        var(skip, template) = await GetTemplateAsync(
                            first.Notification.AppId,
                            first.Notification.UserLanguage,
                            first.EmailTemplate,
                            ct);

                        if (skip != null)
                        {
                            await SkipAsync(jobs, commonEmail, skip !);

                            return;
                        }

                        if (template == null)
                        {
                            return;
                        }

                        var(result, errors) = await emailFormatter.FormatAsync(jobs, template, app, user, false, ct);

                        if (errors.Count > 0 || result == null)
                        {
                            throw new EmailFormattingException(errors);
                        }

                        message = result;
                    }

                    await SendCoreAsync(message, app.Id, senders, ct);

                    await UpdateAsync(jobs, commonEmail, ProcessStatus.Handled);
                }
                catch (DomainException ex)
                {
                    await logStore.LogAsync(app.Id, Name, ex.Message);

                    throw;
                }
            }
        }
예제 #8
0
        public async Task DistributeScheduledAsync(UserEventMessage userEvent, bool isLastAttempt)
        {
            var links = userEvent.Links();

            var parentContext = Activity.Current?.Context ?? default;

            using (var activity = Telemetry.Activities.StartActivity("DistributeUserEventScheduled", ActivityKind.Internal, parentContext, links: links))
            {
                await userNotificationsStore.TrackAttemptAsync(userEvent);

                try
                {
                    var user = await userStore.GetCachedAsync(userEvent.AppId, userEvent.UserId);

                    if (user == null)
                    {
                        throw new DomainException(Texts.Notification_NoApp);
                    }

                    var app = await appStore.GetCachedAsync(userEvent.AppId);

                    if (app == null)
                    {
                        throw new DomainException(Texts.Notification_NoUser);
                    }

                    var options = new SendOptions {
                        App = app, User = user
                    };

                    var notification = await CreateUserNotificationAsync(userEvent, options);

                    notification.NotificationActivity = activity?.Context ?? default;

                    try
                    {
                        await userNotificationsStore.InsertAsync(notification);
                    }
                    catch (UniqueConstraintException)
                    {
                        throw new DomainException(Texts.Notification_AlreadyProcessed);
                    }

                    foreach (var channel in channels)
                    {
                        if (notification.Channels.TryGetValue(channel.Name, out var notificationChannel))
                        {
                            foreach (var configuration in notificationChannel.Status.Keys)
                            {
                                await channel.SendAsync(notification, notificationChannel.Setting, configuration, options, default);
                            }
                        }
                    }

                    log.LogInformation("Processed user event for app {appId} with ID {id} to topic {topic}.",
                                       userEvent.AppId,
                                       userEvent.EventId,
                                       userEvent.Topic);
                }
                catch (Exception ex)
                {
                    if (isLastAttempt)
                    {
                        await userNotificationsStore.TrackFailedAsync(userEvent);
                    }

                    if (ex is DomainException domainException)
                    {
                        await logStore.LogAsync(userEvent.AppId, domainException.Message);
                    }
                    else
                    {
                        log.LogError(ex, "Failed to process user event for app {appId} with ID {id} to topic {topic}.",
                                     userEvent.AppId,
                                     userEvent.EventId,
                                     userEvent.Topic);
                        throw;
                    }
                }
            }
        }