Exemplo n.º 1
0
        public async Task DistributeAsync(UserEventMessage userEvent)
        {
            using (Telemetry.Activities.StartActivity("HandleUserEvent"))
            {
                try
                {
                    var user = await userStore.GetCachedAsync(userEvent.AppId, userEvent.UserId);

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

                    var dueTime = Scheduling.CalculateScheduleTime(userEvent.Scheduling, clock, user.PreferredTimezone);

                    await userEventQueue.ScheduleAsync(ScheduleKey(userEvent), userEvent, dueTime, true);
                }
                catch (DomainException ex)
                {
                    await logStore.LogAsync(userEvent.AppId, ex.Message);

                    await userNotificationsStore.TrackFailedAsync(userEvent);
                }
            }
        }
        public void Should_use_language_from_user_if_supported()
        {
            var userEvent = CreateMinimumUserEvent();

            user.PreferredLanguage = "de";

            var notification = sut.Create(app, user, userEvent) !;

            Assert.Equal("deText", notification.Formatting.Subject);
            Assert.Equal("de", notification.UserLanguage);

            A.CallTo(() => logStore.LogAsync(app.Id, A <string> ._, A <CancellationToken> ._))
            .MustNotHaveHappened();
        }
Exemplo n.º 3
0
        public async Task Should_not_produce_message_if_app_id_not_set()
        {
            var @event = CreateMinimumEvent();

            @event.AppId = null !;

            await sut.PublishAsync(@event, default);

            A.CallTo(() => subscriptionStore.QueryAsync(@event.AppId, A <TopicId> ._, @event.CreatorId, A <CancellationToken> ._))
            .MustNotHaveHappened();

            A.CallTo(() => logStore.LogAsync(A <string> ._, A <string> ._, A <CancellationToken> ._))
            .MustNotHaveHappened();
        }
Exemplo n.º 4
0
        public Task SendAsync(WebhookJob job, bool isLastAttempt, CancellationToken ct)
        {
            return(log.ProfileAsync("SendWebhook", async() =>
            {
                var notification = job.Notification;

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

                    using (var client = httpClientFactory.CreateClient())
                    {
                        await client.PostAsJsonAsync(job.Url, job.Notification, ct);
                    }

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

                    await logStore.LogAsync(notification.AppId, $"Webhook: {ex.Message}");

                    throw;
                }
            }));
        }
Exemplo n.º 5
0
        public Task SendAsync(SmsJob job, bool isLastAttempt, CancellationToken ct)
        {
            return(log.ProfileAsync("SendSms", async() =>
            {
                try
                {
                    await UpdateAsync(job, ProcessStatus.Attempt);

                    var result = await smsSender.SendAsync(job.PhoneNumber, job.Text, job.Id.ToString(), ct);

                    if (result == SmsResult.Delivered)
                    {
                        await UpdateAsync(job, ProcessStatus.Handled);
                    }
                }
                catch (Exception ex)
                {
                    if (isLastAttempt)
                    {
                        await UpdateAsync(job, ProcessStatus.Failed);
                    }

                    if (ex is DomainException domainException)
                    {
                        await logStore.LogAsync(job.AppId, domainException.Message);
                    }
                    else
                    {
                        throw;
                    }
                }
            }));
Exemplo n.º 6
0
        public Task SendAsync(WebPushJob job, bool isLastAttempt, CancellationToken ct)
        {
            return(log.ProfileAsync("SendWebPush", async() =>
            {
                try
                {
                    await UpdateAsync(job, ProcessStatus.Attempt);

                    await SendAnyAsync(job, ct);

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

                    if (ex is DomainException domainException)
                    {
                        await logStore.LogAsync(job.AppId, domainException.Message);
                    }
                    else
                    {
                        throw;
                    }
                }
            }));
Exemplo n.º 7
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;
                    }
                }
            }));
Exemplo n.º 8
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;
                }
            }
        }
Exemplo n.º 9
0
        private async Task SendAsync(WebPushJob job,
                                     CancellationToken ct)
        {
            using (Telemetry.Activities.StartActivity("Send"))
            {
                try
                {
                    await UpdateAsync(job, ProcessStatus.Attempt);

                    await SendCoreAsync(job, ct);

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

                    throw;
                }
            }
        }
Exemplo n.º 10
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;
                    }
                }
            }));
Exemplo n.º 11
0
        public async Task PublishAsync(EventMessage message,
                                       CancellationToken ct = default)
        {
            Guard.NotNull(message);

            using (var activity = Telemetry.Activities.StartActivity("PublishEvent"))
            {
                message.Validate();

                var now = clock.GetCurrentInstant();

                if (message.Created == default)
                {
                    message.Created = now;
                }
                else
                {
                    var age = now - message.Created;

                    if (age > MaxAge)
                    {
                        await logStore.LogAsync(message.AppId, Texts.Events_TooOld);

                        return;
                    }
                }

                if (string.IsNullOrWhiteSpace(message.Id))
                {
                    message.Id = Guid.NewGuid().ToString();
                }

                if (activity != null)
                {
                    message.EventActivity = activity.Context;
                }

                await ProduceAsync(message);

                log.LogInformation("Received event for app {appId} with ID {id} to topic {topic}.",
                                   message.AppId,
                                   message.Id,
                                   message.Topic);
            }
        }
Exemplo n.º 12
0
        public UserNotification?Create(App app, User user, UserEventMessage userEvent)
        {
            Guard.NotNull(user, nameof(user));
            Guard.NotNull(userEvent, nameof(userEvent));

            if (userEvent.Formatting == null ||
                string.IsNullOrWhiteSpace(userEvent.AppId) ||
                string.IsNullOrWhiteSpace(userEvent.EventId) ||
                string.IsNullOrWhiteSpace(userEvent.Topic) ||
                string.IsNullOrWhiteSpace(userEvent.UserId))
            {
                return(null);
            }

            var language = user.PreferredLanguage;

            if (!app.Languages.Contains(language))
            {
                logstore.LogAsync(app.Id, string.Format(Texts.UserLanguage_NotValid, language, app.Language));

                language = app.Language;
            }

            var formatting = userEvent.Formatting.SelectText(language);

            if (!formatting.HasSubject())
            {
                return(null);
            }

            var notification = SimpleMapper.Map(userEvent, new UserNotification
            {
                Id = Guid.NewGuid()
            });

            notification.Formatting   = formatting;
            notification.UserLanguage = language;

            ConfigureTracking(notification, language, userEvent);
            ConfigureSettings(notification, user, userEvent);

            notification.Updated = clock.GetCurrentInstant();

            return(notification);
        }
Exemplo n.º 13
0
        private async Task SendJobAsync(WebhookJob job,
                                        CancellationToken ct)
        {
            try
            {
                await UpdateAsync(job, ProcessStatus.Attempt);

                await SendCoreAsync(job, ct);

                await UpdateAsync(job, ProcessStatus.Handled);
            }
            catch (Exception ex)
            {
                await logStore.LogAsync(job.Notification.AppId, Name, ex.Message);

                throw;
            }
        }
Exemplo n.º 14
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;
                }
            }
        }
Exemplo n.º 15
0
        public async Task PublishAsync(EventMessage message, CancellationToken ct = default)
        {
            if (string.IsNullOrWhiteSpace(message.AppId))
            {
                return;
            }

            if (string.IsNullOrWhiteSpace(message.Topic))
            {
                await logStore.LogAsync(message.AppId, Texts.Events_NoTopic, ct);

                return;
            }

            if (string.IsNullOrWhiteSpace(message.TemplateCode) && message.Formatting?.HasSubject() != true)
            {
                await logStore.LogAsync(message.AppId, Texts.Events_NoSubjectOrTemplateCode, ct);

                return;
            }

            var count = 0;

            await foreach (var subscription in GetSubscriptions(message).WithCancellation(ct))
            {
                if (count == 0)
                {
                    if (!string.IsNullOrWhiteSpace(message.TemplateCode))
                    {
                        var template = await templateStore.GetAsync(message.AppId, message.TemplateCode, ct);

                        if (template != null && !template.IsAutoCreated)
                        {
                            message.Formatting = template.Formatting;

                            if (template.Settings != null && template.Settings.Count > 0)
                            {
                                var settings = new NotificationSettings();

                                settings.OverrideBy(template.Settings);
                                settings.OverrideBy(message.Settings);

                                message.Settings = settings;
                            }
                        }
                    }

                    if (message.Formatting?.HasSubject() != true)
                    {
                        await logStore.LogAsync(message.AppId, string.Format(Texts.Template_NoSubject, message.TemplateCode), ct);

                        return;
                    }

                    message.Formatting = message.Formatting.Format(message.Properties);

                    try
                    {
                        await eventStore.InsertAsync(message, ct);
                    }
                    catch (UniqueConstraintException)
                    {
                        await logStore.LogAsync(message.AppId, Texts.Events_AlreadyProcessed, ct);

                        break;
                    }
                }

                var userEventMessage = CreateUserEventMessage(message, subscription);

                await userEventProducer.ProduceAsync(subscription.UserId, userEventMessage);

                count++;
            }

            if (count > 0)
            {
                var counterMap = CounterMap.ForNotification(ProcessStatus.Attempt, count);
                var counterKey = CounterKey.ForEvent(message);

                await counters.CollectAsync(counterKey, counterMap, ct);
            }
            else
            {
                await logStore.LogAsync(message.AppId, Texts.Events_NoSubscriber, ct);
            }
        }
Exemplo n.º 16
0
        public async Task PublishAsync(EventMessage @event,
                                       CancellationToken ct)
        {
            using (var activity = Telemetry.Activities.StartActivity("HandleUserEvent"))
            {
                log.LogInformation("Received event for app {appId} with ID {id} to topic {topic}.",
                                   @event.AppId,
                                   @event.Id,
                                   @event.Topic);

                if (string.IsNullOrWhiteSpace(@event.AppId))
                {
                    log.LogInformation("Received invalid event with ID {id} to topic {topic}: No app id found.",
                                       @event.Id,
                                       @event.Topic);
                    return;
                }

                if (string.IsNullOrWhiteSpace(@event.Topic))
                {
                    await logStore.LogAsync(@event.AppId, Texts.Events_NoTopic);

                    return;
                }

                if (string.IsNullOrWhiteSpace(@event.TemplateCode) && @event.Formatting?.HasSubject() != true)
                {
                    await logStore.LogAsync(@event.AppId, Texts.Events_NoSubjectOrTemplateCode);

                    return;
                }

                var count = 0;

                await foreach (var subscription in GetSubscriptions(@event, ct))
                {
                    ct.ThrowIfCancellationRequested();

                    if (count == 0)
                    {
                        var templateCode = (string?)null;

                        if (@event.TemplateVariants?.Count > 0)
                        {
                            var random = randomizer.NextDouble();

                            var propability = 0d;

                            foreach (var(key, value) in @event.TemplateVariants)
                            {
                                propability += value;

                                if (random <= propability)
                                {
                                    templateCode = key;
                                    break;
                                }
                            }
                        }

                        if (string.IsNullOrWhiteSpace(templateCode))
                        {
                            templateCode = @event.TemplateCode;
                        }

                        if (!string.IsNullOrWhiteSpace(templateCode))
                        {
                            var template = await templateStore.GetAsync(@event.AppId, templateCode, ct);

                            if (template?.IsAutoCreated == false)
                            {
                                if (@event.Formatting != null)
                                {
                                    @event.Formatting = template.Formatting.MergedWith(@event.Formatting);
                                }
                                else
                                {
                                    @event.Formatting = template.Formatting;
                                }

                                @event.Settings = ChannelSettings.Merged(template.Settings, @event.Settings);
                            }
                        }

                        if (@event.Formatting?.HasSubject() != true)
                        {
                            await logStore.LogAsync(@event.AppId, string.Format(CultureInfo.InvariantCulture, Texts.Template_NoSubject, templateCode));

                            return;
                        }

                        if (@event.Properties != null)
                        {
                            @event.Formatting = @event.Formatting.Format(@event.Properties);
                        }

                        try
                        {
                            await eventStore.InsertAsync(@event, ct);
                        }
                        catch (UniqueConstraintException)
                        {
                            await logStore.LogAsync(@event.AppId, Texts.Events_AlreadyProcessed);

                            break;
                        }
                    }

                    var userEventMessage = CreateUserEventMessage(@event, subscription);

                    if (activity != null)
                    {
                        userEventMessage.UserEventActivity = activity.Context;
                    }

                    await ProduceAsync(subscription, userEventMessage);

                    count++;
                }

                if (count > 0)
                {
                    var counterMap = CounterMap.ForNotification(ProcessStatus.Attempt, count);
                    var counterKey = CounterKey.ForEvent(@event);

                    await counters.CollectAsync(counterKey, counterMap, ct);
                }
                else
                {
                    await logStore.LogAsync(@event.AppId, Texts.Events_NoSubscriber);
                }

                log.LogInformation("Processed event for app {appId} with ID {id} to topic {topic}.",
                                   @event.AppId,
                                   @event.Id,
                                   @event.Topic);
            }
        }
Exemplo n.º 17
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;
                }
            }
        }