Skip to content

Commit 6f71f40

Browse files
Minor UI fixes.
2 parents 9d2cbd7 + 61e02d1 commit 6f71f40

File tree

210 files changed

+5092
-2396
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

210 files changed

+5092
-2396
lines changed

backend/src/Notifo.Domain.Integrations/Notifo.Domain.Integrations.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
</PropertyGroup>
1010

1111
<ItemGroup>
12-
<PackageReference Include="AWSSDK.SimpleEmail" Version="3.7.300.59" />
12+
<PackageReference Include="AWSSDK.SimpleEmail" Version="3.7.300.60" />
1313
<PackageReference Include="Confluent.Kafka" Version="2.3.0" />
1414
<PackageReference Include="FirebaseAdmin" Version="2.4.0" />
1515
<PackageReference Include="FluentValidation" Version="11.9.0" />

backend/src/Notifo.Domain/Apps/App.cs

+2
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ public sealed record App(string Id, Instant Created)
2626

2727
public Instant LastUpdate { get; init; }
2828

29+
public AppAuthScheme? AuthScheme { get; init; }
30+
2931
public ReadonlyList<string> Languages { get; init; } = DefaultLanguages;
3032

3133
public ReadonlyDictionary<string, string> ApiKeys { get; init; } = ReadonlyDictionary.Empty<string, string>();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// ==========================================================================
2+
// Notifo.io
3+
// ==========================================================================
4+
// Copyright (c) Sebastian Stehle
5+
// All rights reserved. Licensed under the MIT license.
6+
// ==========================================================================
7+
8+
namespace Notifo.Domain.Apps;
9+
10+
public sealed class AppAuthScheme
11+
{
12+
public string Domain { get; init; }
13+
14+
public string DisplayName { get; init; }
15+
16+
public string ClientId { get; init; }
17+
18+
public string ClientSecret { get; init; }
19+
20+
public string Authority { get; init; }
21+
22+
public string? SignoutRedirectUrl { get; init; }
23+
}

backend/src/Notifo.Domain/Apps/AppStore.cs

+18
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,12 @@ public async Task CollectAsync(TrackingKey key, CounterMap counters,
4545
}
4646
}
4747

48+
public Task<bool> AnyAuthDomainAsync(
49+
CancellationToken ct = default)
50+
{
51+
return repository.AnyAuthDomainAsync(ct);
52+
}
53+
4854
public IAsyncEnumerable<App> QueryAllAsync(
4955
CancellationToken ct = default)
5056
{
@@ -116,6 +122,18 @@ public async Task<List<App>> QueryAsync(string contributorId,
116122
return app;
117123
}
118124

125+
public async Task<App?> GetByAuthDomainAsync(string domain,
126+
CancellationToken ct = default)
127+
{
128+
Guard.NotNullOrEmpty(domain);
129+
130+
var (app, _) = await repository.GetByAuthDomainAsync(domain, ct);
131+
132+
await DeliverAsync(app);
133+
134+
return app;
135+
}
136+
119137
public async ValueTask<App?> HandleAsync(AppCommand command,
120138
CancellationToken ct)
121139
{

backend/src/Notifo.Domain/Apps/IAppRepository.cs

+6
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,15 @@ Task<List<App>> QueryAsync(string contributorId,
2626
Task<(App? App, string? Etag)> GetAsync(string id,
2727
CancellationToken ct = default);
2828

29+
Task<(App? App, string? Etag)> GetByAuthDomainAsync(string domain,
30+
CancellationToken ct = default);
31+
2932
Task UpsertAsync(App app, string? oldEtag = null,
3033
CancellationToken ct = default);
3134

3235
Task DeleteAsync(string id,
3336
CancellationToken ct = default);
37+
38+
Task<bool> AnyAuthDomainAsync(
39+
CancellationToken ct = default);
3440
}

backend/src/Notifo.Domain/Apps/IAppStore.cs

+6
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@ Task<List<App>> QueryAsync(string contributorId,
2424
Task<App?> GetAsync(string id,
2525
CancellationToken ct = default);
2626

27+
Task<App?> GetByAuthDomainAsync(string domain,
28+
CancellationToken ct = default);
29+
2730
Task<App?> GetCachedAsync(string id,
2831
CancellationToken ct = default);
32+
33+
Task<bool> AnyAuthDomainAsync(
34+
CancellationToken ct = default);
2935
}

backend/src/Notifo.Domain/Apps/MongoDb/MongoDbAppRepository.cs

+22
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,19 @@ await Collection.Find(x => x.ContributorIds.Contains(contributorId))
109109
}
110110
}
111111

112+
public async Task<(App? App, string? Etag)> GetByAuthDomainAsync(string domain,
113+
CancellationToken ct = default)
114+
{
115+
using (Telemetry.Activities.StartActivity("MongoDbAppRepository/GetByAuthDomainAsync"))
116+
{
117+
var document = await
118+
Collection.Find(x => x.Doc.AuthScheme!.Domain == domain)
119+
.FirstOrDefaultAsync(ct);
120+
121+
return (document?.ToApp(), document?.Etag);
122+
}
123+
}
124+
112125
public async Task<(App? App, string? Etag)> GetAsync(string id,
113126
CancellationToken ct = default)
114127
{
@@ -131,6 +144,15 @@ public async Task UpsertAsync(App app, string? oldEtag = null,
131144
}
132145
}
133146

147+
public async Task<bool> AnyAuthDomainAsync(
148+
CancellationToken ct = default)
149+
{
150+
using (Telemetry.Activities.StartActivity("MongoDbAppRepository/AnyAuthDomainAsync"))
151+
{
152+
return await Collection.Find(x => x.Doc.AuthScheme != null).AnyAsync(ct);
153+
}
154+
}
155+
134156
public async Task BatchWriteAsync(List<(string Key, CounterMap Counters)> counters,
135157
CancellationToken ct)
136158
{
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// ==========================================================================
2+
// Notifo.io
3+
// ==========================================================================
4+
// Copyright (c) Sebastian Stehle
5+
// All rights reserved. Licensed under the MIT license.
6+
// ==========================================================================
7+
8+
using FluentValidation;
9+
using Notifo.Infrastructure.Validation;
10+
11+
namespace Notifo.Domain.Apps;
12+
13+
public sealed class UpsertAppAuthScheme : AppCommand
14+
{
15+
public AppAuthScheme? Scheme { get; set; }
16+
17+
private sealed class Validator : AbstractValidator<UpsertAppAuthScheme>
18+
{
19+
public Validator()
20+
{
21+
When(x => x.Scheme != null, () =>
22+
{
23+
RuleFor(x => x.Scheme).SetValidator(new SchemeValidator()!);
24+
});
25+
}
26+
}
27+
28+
private sealed class SchemeValidator : AbstractValidator<AppAuthScheme>
29+
{
30+
public SchemeValidator()
31+
{
32+
RuleFor(x => x.Domain).NotNull().NotEmpty().Domain();
33+
RuleFor(x => x.DisplayName).NotNull().NotEmpty();
34+
RuleFor(x => x.ClientId).NotNull().NotEmpty();
35+
RuleFor(x => x.ClientSecret).NotNull().NotEmpty();
36+
RuleFor(x => x.Authority).NotNull().NotEmpty().Url();
37+
RuleFor(x => x.SignoutRedirectUrl).Url();
38+
}
39+
}
40+
41+
public override ValueTask<App?> ExecuteAsync(App target, IServiceProvider serviceProvider,
42+
CancellationToken ct)
43+
{
44+
Validate<Validator>.It(this);
45+
46+
if (!Equals(target.AuthScheme, Scheme))
47+
{
48+
target = target with { AuthScheme = Scheme };
49+
}
50+
51+
return new ValueTask<App?>(target);
52+
}
53+
}

backend/src/Notifo.Domain/Events/EventsServiceExtensions.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ public static void AddMyEvents(this IServiceCollection services, IConfiguration
2222

2323
services.ConfigureAndValidate<EventsOptions>(config, "events");
2424

25-
services.AddMessaging(new ChannelName(options.ChannelName), true);
25+
services.AddMessaging()
26+
.AddChannel(new ChannelName(options.ChannelName), true);
2627

2728
services.Configure<MessagingOptions>(messaging =>
2829
{

backend/src/Notifo.Domain/Liquid/LiquidApp.cs

+5-4
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,15 @@
1010

1111
namespace Notifo.Domain.Liquid;
1212

13-
public sealed class LiquidApp
13+
public sealed class LiquidApp(App app)
1414
{
15-
private readonly App app;
15+
private readonly App app = app;
1616

1717
public string? Name => app.Name.OrNull();
1818

19-
public LiquidApp(App app)
19+
public static void Describe(LiquidProperties properties)
2020
{
21-
this.app = app;
21+
properties.AddString("name",
22+
"The name of the app. Cannot be null or undefined.");
2223
}
2324
}

backend/src/Notifo.Domain/Liquid/LiquidContext.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,9 @@ public static LiquidContext Create(IEnumerable<(BaseUserNotification Notificatio
6363
}
6464

6565
context.SetValue("app", new LiquidApp(app));
66+
context.SetValue("user", new LiquidUser(user));
6667
context.SetValue("notification", notifications[0]);
6768
context.SetValue("notifications", notifications);
68-
context.SetValue("user", new LiquidUser(user));
6969

7070
return context;
7171
}

backend/src/Notifo.Domain/Liquid/LiquidNotification.cs

+25-5
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,6 @@ public sealed class LiquidNotification : LiquidNotificationBase
2424
private string? trackSeenUrl;
2525
private LiquidChildNotification[]? children;
2626

27-
public string? ConfirmText
28-
{
29-
get => notification.Formatting.ConfirmText.OrNull();
30-
}
31-
3227
public string? TrackSeenUrl
3328
{
3429
get => trackSeenUrl ??= notification.ComputeTrackSeenUrl(channel, configurationId);
@@ -39,6 +34,11 @@ public string? TrackDeliveredUrl
3934
get => trackDeliveredUrl ??= notification.ComputeTrackDeliveredUrl(channel, configurationId);
4035
}
4136

37+
public string? ConfirmText
38+
{
39+
get => notification.Formatting.ConfirmText.OrNull();
40+
}
41+
4242
public string? ConfirmUrl
4343
{
4444
get => confirmUrl ??= notification.ComputeConfirmUrl(channel, configurationId);
@@ -72,4 +72,24 @@ public LiquidNotification(
7272
this.imageFormatter = imageFormatter;
7373
this.configurationId = configurationId;
7474
}
75+
76+
public static void Describe(LiquidProperties properties)
77+
{
78+
DescribeBase(properties);
79+
80+
properties.AddString("trackSeenUrl",
81+
"The tracking URL to mark the notification as seen.");
82+
83+
properties.AddString("trackDeliveredUrl",
84+
"The tracking URL to mark the notification as delivered.");
85+
86+
properties.AddString("confirmUrl",
87+
"The tracking URL to mark the notification as confirmed.");
88+
89+
properties.AddString("confirmText",
90+
"The text for confirmation buttons.");
91+
92+
properties.AddArray("children",
93+
"The child notifications if the notifications have been grouped together.");
94+
}
7595
}

backend/src/Notifo.Domain/Liquid/LiquidNotificationBase.cs

+21
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,25 @@ protected LiquidNotificationBase(
4848
this.imagePresetLarge = imagePresetLarge;
4949
this.formatting = formatting;
5050
}
51+
52+
protected static void DescribeBase(LiquidProperties properties)
53+
{
54+
properties.AddString("subject",
55+
"The notification subject. Cannot be null or undefined.");
56+
57+
properties.AddString("body",
58+
"The notification body. Can be null or undefined.");
59+
60+
properties.AddString("linkUrl",
61+
"The link URL. Can be null or undefined.");
62+
63+
properties.AddString("linkText",
64+
"The link text that can be set when a linkUrl is set. Can be null or undefined.");
65+
66+
properties.AddString("imageSmall",
67+
"The URL to the small image. Optimized for the current use case (e.g. emails). Can be null or undefined.");
68+
69+
properties.AddString("imageLarge",
70+
"The URL to the large image. Optimized for the current use case (e.g. emails). Can be null or undefined.");
71+
}
5172
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// ==========================================================================
2+
// Notifo.io
3+
// ==========================================================================
4+
// Copyright (c) Sebastian Stehle
5+
// All rights reserved. Licensed under the MIT license.
6+
// ==========================================================================
7+
8+
#pragma warning disable CA1822 // Mark members as static
9+
10+
namespace Notifo.Domain.Liquid;
11+
12+
public sealed class LiquidPropertiesProvider
13+
{
14+
public LiquidProperties GetProperties()
15+
{
16+
var properties = new LiquidProperties();
17+
18+
properties.AddObject("app", () =>
19+
{
20+
LiquidApp.Describe(properties);
21+
}, "The current app.");
22+
23+
properties.AddObject("user", () =>
24+
{
25+
LiquidUser.Describe(properties);
26+
}, "The current user.");
27+
28+
properties.AddObject("notification", () =>
29+
{
30+
LiquidNotification.Describe(properties);
31+
}, "The first and usually single notifications. For emails multiple notifications can be grouped in one template.");
32+
33+
properties.AddArray("notifications",
34+
"The list of notifications. Usually it is only one, but for emails multiple notifications can be grouped in one template.");
35+
36+
return properties;
37+
}
38+
}

0 commit comments

Comments
 (0)