Skip to content

Commit b5f73ef

Browse files
authored
Merge pull request #1692 from exceptionless/feature/shadcn-forms
Use shadcn forms
2 parents e9512dd + c043b9f commit b5f73ef

File tree

94 files changed

+3169
-1914
lines changed

Some content is hidden

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

94 files changed

+3169
-1914
lines changed

.github/workflows/build.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,9 @@ jobs:
146146
- name: Lint Client
147147
run: npm run lint
148148

149+
- name: Check
150+
run: npm run check
151+
149152
- name: Build
150153
run: npm run build
151154

src/Exceptionless.Core/Bootstrapper.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
using Exceptionless.Core.Serialization;
2424
using Exceptionless.Core.Services;
2525
using Exceptionless.Core.Utility;
26+
using Exceptionless.Core.Validation;
2627
using Exceptionless.Serializer;
2728
using FluentValidation;
2829
using Foundatio.Caching;
@@ -145,6 +146,7 @@ public static void RegisterServices(IServiceCollection services, AppOptions appO
145146
services.AddSingleton<PersistentEventQueryValidator>();
146147
services.AddSingleton<StackQueryValidator>();
147148

149+
services.AddSingleton<MiniValidationValidator>();
148150
services.AddSingleton(typeof(IValidator<>), typeof(Bootstrapper).Assembly);
149151
services.AddSingleton(typeof(IPipelineAction<EventContext>), typeof(Bootstrapper).Assembly);
150152
services.AddSingleton(typeof(IPlugin), typeof(Bootstrapper).Assembly);

src/Exceptionless.Core/Exceptionless.Core.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
<PackageReference Include="FluentValidation" Version="11.10.0" />
2525
<PackageReference Include="Foundatio.Extensions.Hosting" Version="$(FoundatioVersion)" />
2626
<PackageReference Include="Foundatio.JsonNet" Version="$(FoundatioVersion)" />
27+
<PackageReference Include="MiniValidation" Version="0.9.1" />
2728
<PackageReference Include="NEST.JsonNetSerializer" Version="7.17.5" />
2829
<PackageReference Include="Handlebars.Net" Version="2.1.6" />
2930
<PackageReference Include="McSherry.SemanticVersioning" Version="1.4.1" />

src/Exceptionless.Core/Extensions/IdentityUtils.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,8 @@ public static ClaimsIdentity ToIdentity(this User user, Token? token = null)
8181
claims.Add(new Claim(ClaimTypes.Role, AuthorizationRoles.User));
8282
}
8383

84-
return new ClaimsIdentity(claims, UserAuthenticationType);
84+
string authenticationType = token is { Type: TokenType.Access } ? TokenAuthenticationType : UserAuthenticationType;
85+
return new ClaimsIdentity(claims, authenticationType);
8586
}
8687

8788
public static bool IsAuthenticated(this ClaimsPrincipal principal)
@@ -146,7 +147,7 @@ public static string[] GetOrganizationIds(this ClaimsPrincipal principal)
146147
if (String.IsNullOrEmpty(ids))
147148
return [];
148149

149-
return ids.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
150+
return ids.Split([','], StringSplitOptions.RemoveEmptyEntries);
150151
}
151152

152153
public static string? GetProjectId(this ClaimsPrincipal principal)

src/Exceptionless.Core/Extensions/UserExtensions.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using Exceptionless.Core.Models;
2+
using Exceptionless.DateTimeExtensions;
23

34
namespace Exceptionless.Core.Extensions;
45

@@ -29,7 +30,7 @@ public static void MarkEmailAddressVerified(this User user)
2930

3031
public static bool HasValidVerifyEmailAddressTokenExpiration(this User user, TimeProvider timeProvider)
3132
{
32-
return user.VerifyEmailAddressTokenExpiration != DateTime.MinValue && user.VerifyEmailAddressTokenExpiration >= timeProvider.GetUtcNow().UtcDateTime;
33+
return user.VerifyEmailAddressTokenExpiration.IsAfterOrEqual(timeProvider.GetUtcNow().UtcDateTime);
3334
}
3435

3536
public static void ResetPasswordResetToken(this User user)

src/Exceptionless.Core/Jobs/EventPostsJob.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using Exceptionless.Core.Repositories;
88
using Exceptionless.Core.Repositories.Base;
99
using Exceptionless.Core.Services;
10+
using Exceptionless.Core.Validation;
1011
using FluentValidation;
1112
using Foundatio.Jobs;
1213
using Foundatio.Queues;
@@ -208,7 +209,7 @@ protected override async Task<JobResult> ProcessQueueEntryAsync(QueueEntryContex
208209
continue;
209210

210211
if (!isInternalProject) _logger.LogError(ctx.Exception, "Error processing EventPost {QueueEntryId} {FilePath}: {Message}", entry.Id, payloadPath, ctx.ErrorMessage);
211-
if (ctx.Exception is ValidationException)
212+
if (ctx.Exception is ValidationException or MiniValidatorException)
212213
continue;
213214

214215
errorCount++;

src/Exceptionless.Core/Models/User.cs

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
using System.Collections.ObjectModel;
2+
using System.ComponentModel.DataAnnotations;
23
using Foundatio.Repositories.Models;
34

45
namespace Exceptionless.Core.Models;
56

6-
public class User : IIdentity, IHaveDates
7+
public record User : IIdentity, IHaveDates, IValidatableObject
78
{
89
/// <summary>
910
/// Unique id that identifies an user.
@@ -13,19 +14,22 @@ public class User : IIdentity, IHaveDates
1314
/// <summary>
1415
/// The organizations that the user has access to.
1516
/// </summary>
16-
public ICollection<string> OrganizationIds { get; set; } = new Collection<string>();
17+
public ICollection<string> OrganizationIds { get; } = new Collection<string>();
1718

1819
public string? Password { get; set; }
1920
public string? Salt { get; set; }
2021
public string? PasswordResetToken { get; set; }
2122
public DateTime PasswordResetTokenExpiration { get; set; }
22-
public ICollection<OAuthAccount> OAuthAccounts { get; set; } = new Collection<OAuthAccount>();
23+
public ICollection<OAuthAccount> OAuthAccounts { get; } = new Collection<OAuthAccount>();
2324

2425
/// <summary>
2526
/// Gets or sets the users Full Name.
2627
/// </summary>
28+
[Required]
2729
public string FullName { get; set; } = null!;
2830

31+
[Required]
32+
[EmailAddress]
2933
public string EmailAddress { get; set; } = null!;
3034
public bool EmailNotificationsEnabled { get; set; } = true;
3135
public bool IsEmailAddressVerified { get; set; }
@@ -35,10 +39,42 @@ public class User : IIdentity, IHaveDates
3539
/// <summary>
3640
/// Gets or sets the users active state.
3741
/// </summary>
38-
public bool IsActive { get; set; } = true;
42+
public bool IsActive { get; init; } = true;
3943

40-
public ICollection<string> Roles { get; set; } = new Collection<string>();
44+
public ICollection<string> Roles { get; init; } = new Collection<string>();
4145

4246
public DateTime CreatedUtc { get; set; }
4347
public DateTime UpdatedUtc { get; set; }
48+
49+
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
50+
{
51+
if (IsEmailAddressVerified)
52+
{
53+
if (VerifyEmailAddressToken is not null)
54+
{
55+
yield return new ValidationResult("A verify email address token cannot be set if the email address has been verified.",
56+
[nameof(VerifyEmailAddressToken)]);
57+
}
58+
59+
if (VerifyEmailAddressTokenExpiration != default)
60+
{
61+
yield return new ValidationResult("A verify email address token expiration cannot be set if the email address has been verified.",
62+
[nameof(VerifyEmailAddressTokenExpiration)]);
63+
}
64+
}
65+
else
66+
{
67+
if (String.IsNullOrWhiteSpace(VerifyEmailAddressToken))
68+
{
69+
yield return new ValidationResult("A verify email address token must be set if the email address has not been verified.",
70+
[nameof(VerifyEmailAddressToken)]);
71+
}
72+
73+
if (VerifyEmailAddressTokenExpiration == default)
74+
{
75+
yield return new ValidationResult("A verify email address token expiration must be set if the email address has not been verified.",
76+
[nameof(VerifyEmailAddressTokenExpiration)]);
77+
}
78+
}
79+
}
4480
}

src/Exceptionless.Core/Repositories/Base/RepositoryBase.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@ namespace Exceptionless.Core.Repositories;
1313

1414
public abstract class RepositoryBase<T> : ElasticRepositoryBase<T> where T : class, IIdentity, new()
1515
{
16-
protected readonly IValidator<T> _validator;
16+
protected readonly IValidator<T>? _validator;
1717
protected readonly AppOptions _options;
1818

19-
public RepositoryBase(IIndex index, IValidator<T> validator, AppOptions options) : base(index)
19+
public RepositoryBase(IIndex index, IValidator<T>? validator, AppOptions options) : base(index)
2020
{
2121
_validator = validator;
2222
_options = options;
@@ -25,6 +25,10 @@ public RepositoryBase(IIndex index, IValidator<T> validator, AppOptions options)
2525

2626
protected override Task ValidateAndThrowAsync(T document)
2727
{
28+
// TODO: Move this to MiniValidationValidator
29+
if (_validator is null)
30+
return Task.CompletedTask;
31+
2832
return _validator.ValidateAndThrowAsync(document);
2933
}
3034

src/Exceptionless.Core/Repositories/ProjectRepository.cs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,24 +31,24 @@ private void OnDocumentsChanging(object sender, DocumentsChangeEventArgs<Project
3131
if (String.IsNullOrEmpty(projectId))
3232
return null;
3333

34-
var configCacheValue = await Cache.GetAsync<Project>($"config:{projectId}");
34+
string cacheKey = ConfigCacheKey(projectId);
35+
var configCacheValue = await Cache.GetAsync<Project>(cacheKey);
3536
if (configCacheValue.HasValue)
3637
return configCacheValue.Value;
3738

3839
var project = await FindOneAsync(q => q.Id(projectId).Include(p => p.Configuration, p => p.OrganizationId));
3940
if (project?.Document is null)
4041
return null;
4142

42-
await Cache.AddAsync($"config:{projectId}", project.Document);
43+
await Cache.AddAsync(cacheKey, project.Document);
4344

4445
return project.Document;
4546
}
4647

4748
public Task<CountResult> GetCountByOrganizationIdAsync(string organizationId)
4849
{
4950
ArgumentException.ThrowIfNullOrEmpty(organizationId);
50-
51-
return CountAsync(q => q.Organization(organizationId), o => o.Cache(String.Concat("Organization:", organizationId)));
51+
return CountAsync(q => q.Organization(organizationId), o => o.Cache(OrganizationCacheKey(organizationId)));
5252
}
5353

5454
public Task<FindResults<Project>> GetByOrganizationIdsAsync(ICollection<string> organizationIds, CommandOptionsDescriptor<Project>? options = null)
@@ -91,11 +91,14 @@ public async Task IncrementNextSummaryEndOfDayTicksAsync(IReadOnlyCollection<Pro
9191
protected override async Task InvalidateCacheAsync(IReadOnlyCollection<ModifiedDocument<Project>> documents, ChangeType? changeType = null)
9292
{
9393
var organizations = documents.Select(d => d.Value.OrganizationId).Distinct().Where(id => !String.IsNullOrEmpty(id));
94-
await Cache.RemoveAllAsync(organizations.Select(id => $"count:Organization:{id}"));
94+
await Cache.RemoveAllAsync(organizations.Select(id => $"count:{OrganizationCacheKey(id)}"));
9595

96-
var configCacheKeys = documents.Select(d => $"config:{d.Value.Id}");
96+
var configCacheKeys = documents.Select(d => ConfigCacheKey(d.Value.Id));
9797
await Cache.RemoveAllAsync(configCacheKeys);
9898

9999
await base.InvalidateCacheAsync(documents, changeType);
100100
}
101+
102+
private static string ConfigCacheKey(string projectId) => String.Concat("config:", projectId);
103+
private static string OrganizationCacheKey(string organizationId) => String.Concat("Organization:", organizationId);
101104
}

src/Exceptionless.Core/Repositories/UserRepository.cs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
using Exceptionless.Core.Extensions;
22
using Exceptionless.Core.Repositories.Configuration;
3-
using FluentValidation;
3+
using Exceptionless.Core.Validation;
44
using Foundatio.Repositories;
55
using Foundatio.Repositories.Models;
66
using Foundatio.Repositories.Options;
@@ -11,13 +11,22 @@ namespace Exceptionless.Core.Repositories;
1111

1212
public class UserRepository : RepositoryBase<User>, IUserRepository
1313
{
14-
public UserRepository(ExceptionlessElasticConfiguration configuration, IValidator<User> validator, AppOptions options)
15-
: base(configuration.Users, validator, options)
14+
private readonly MiniValidationValidator _miniValidationValidator;
15+
16+
public UserRepository(ExceptionlessElasticConfiguration configuration, MiniValidationValidator validator, AppOptions options)
17+
: base(configuration.Users, null, options)
1618
{
19+
_miniValidationValidator = validator;
1720
DefaultConsistency = Consistency.Immediate;
1821
AddPropertyRequiredForRemove(u => u.EmailAddress, u => u.OrganizationIds);
1922
}
2023

24+
protected override Task ValidateAndThrowAsync(User document)
25+
{
26+
// TOOD: Deprecate this once all are converted to MiniValidationValidator.
27+
return _miniValidationValidator.ValidateAndThrowAsync(document);
28+
}
29+
2130
public async Task<User?> GetByEmailAddressAsync(string emailAddress)
2231
{
2332
if (String.IsNullOrWhiteSpace(emailAddress))

0 commit comments

Comments
 (0)