Skip to content

Commit d9cd98c

Browse files
authored
Merge pull request #100 from deveel/60-auto-populate-ihavetimestamp-and-ihaveowner-properties-on-create-and-update
2 parents ae895da + 063c7dd commit d9cd98c

18 files changed

Lines changed: 114 additions & 78 deletions

samples/Kista.SampleApp.Owners/src/Kista.SampleApp.Owners/Program.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,4 @@
3838
app.MapNoteEndpoints();
3939
app.MapTaskEndpoints();
4040

41-
app.Run();
41+
await app.RunAsync();

src/Kista.Owners/ClaimUserIdentifierStrategy.cs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ public ClaimUserIdentifierStrategy(string claimType = "sub")
5656
return ConvertValue(value);
5757
}
5858

59-
private TKey? ConvertValue(string value)
59+
private static TKey? ConvertValue(string value)
6060
{
6161
try
6262
{
@@ -66,7 +66,19 @@ public ClaimUserIdentifierStrategy(string claimType = "sub")
6666
var converter = TypeDescriptor.GetConverter(typeof(TKey));
6767
return (TKey?)converter.ConvertFromInvariantString(value);
6868
}
69-
catch
69+
catch (FormatException)
70+
{
71+
return default;
72+
}
73+
catch (NotSupportedException)
74+
{
75+
return default;
76+
}
77+
catch (ArgumentException)
78+
{
79+
return default;
80+
}
81+
catch (InvalidCastException)
7082
{
7183
return default;
7284
}

src/Kista.Owners/CompositeUserIdentifierStrategy.cs

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,9 @@ public CompositeUserIdentifierStrategy<TKey> Add(IUserIdentifierStrategy<TKey> s
4343
/// <inheritdoc/>
4444
public TKey? GetUserId(IServiceProvider? serviceProvider = null)
4545
{
46-
foreach (var strategy in strategies)
47-
{
48-
var userId = strategy.GetUserId(serviceProvider);
49-
if (userId != null)
50-
return userId;
51-
}
52-
53-
return default;
46+
return strategies
47+
.Select(strategy => strategy.GetUserId(serviceProvider))
48+
.FirstOrDefault(userId => !EqualityComparer<TKey>.Default.Equals(userId, default));
5449
}
5550
}
5651

src/Kista.Owners/IUserIdentifierStrategy.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ namespace Kista
1818
/// Defines a strategy for resolving the current user identifier.
1919
/// </summary>
2020
/// <typeparam name="TKey">The type of the user identifier key.</typeparam>
21-
public interface IUserIdentifierStrategy<TKey>
21+
public interface IUserIdentifierStrategy<out TKey>
2222
{
2323
/// <summary>
2424
/// Resolves the current user identifier.

src/Kista.Owners/QueryStringUserIdentifierStrategy.cs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ public QueryStringUserIdentifierStrategy(string parameter = "user_id")
5454
return ConvertValue(value);
5555
}
5656

57-
private TKey? ConvertValue(string value)
57+
private static TKey? ConvertValue(string value)
5858
{
5959
try
6060
{
@@ -64,7 +64,19 @@ public QueryStringUserIdentifierStrategy(string parameter = "user_id")
6464
var converter = TypeDescriptor.GetConverter(typeof(TKey));
6565
return (TKey?)converter.ConvertFromInvariantString(value);
6666
}
67-
catch
67+
catch (FormatException)
68+
{
69+
return default;
70+
}
71+
catch (NotSupportedException)
72+
{
73+
return default;
74+
}
75+
catch (ArgumentException)
76+
{
77+
return default;
78+
}
79+
catch (InvalidCastException)
6880
{
6981
return default;
7082
}

src/Kista.Owners/RepositoryBuilderExtensions.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System.Linq;
12
using Microsoft.Extensions.DependencyInjection;
23

34
namespace Kista
@@ -53,10 +54,8 @@ public static RepositoryBuilder WithOwnerScoping(
5354

5455
private static Type? FindUserKeyType(Type entityType)
5556
{
56-
foreach (var iface in entityType.GetInterfaces())
57+
foreach (var iface in entityType.GetInterfaces().Where(iface => iface.IsGenericType))
5758
{
58-
if (!iface.IsGenericType) continue;
59-
6059
var genericDef = iface.GetGenericTypeDefinition();
6160
if (genericDef == typeof(IHaveOwner<>) ||
6261
genericDef.FullName == "Kista.IHaveOwner`1")

src/Kista.Owners/RouteUserIdentifierStrategy.cs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ public RouteUserIdentifierStrategy(string key = "userId")
5454
return ConvertValue(value);
5555
}
5656

57-
private TKey? ConvertValue(string value)
57+
private static TKey? ConvertValue(string value)
5858
{
5959
try
6060
{
@@ -64,7 +64,19 @@ public RouteUserIdentifierStrategy(string key = "userId")
6464
var converter = TypeDescriptor.GetConverter(typeof(TKey));
6565
return (TKey?)converter.ConvertFromInvariantString(value);
6666
}
67-
catch
67+
catch (FormatException)
68+
{
69+
return default;
70+
}
71+
catch (NotSupportedException)
72+
{
73+
return default;
74+
}
75+
catch (ArgumentException)
76+
{
77+
return default;
78+
}
79+
catch (InvalidCastException)
6880
{
6981
return default;
7082
}

src/Kista.Owners/UserScopedRepositoryDecorator.cs

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System.Collections.Generic;
12
using System.Linq.Expressions;
23
using System.Reflection;
34

@@ -36,6 +37,7 @@ public class UserScopedRepositoryDecorator<TEntity, TKey, TUserKey>
3637
where TEntity : class, IHaveOwner<TUserKey>
3738
where TKey : notnull
3839
{
40+
private const string UserContextNotSetMessage = "User context is not set";
3941
private static readonly Lazy<PropertyInfo> _ownerProperty = new(DiscoverOwnerProperty);
4042

4143
private readonly IRepository<TEntity, TKey> _inner;
@@ -72,9 +74,9 @@ public UserScopedRepositoryDecorator(
7274
public ValueTask<TEntity?> FindAsync(TKey key, CancellationToken cancellationToken = default)
7375
{
7476
var userId = _userAccessor.GetUserId();
75-
if (userId == null)
77+
if (EqualityComparer<TUserKey>.Default.Equals(userId, default))
7678
return Options.ThrowWhenUserNotSet
77-
? throw new System.InvalidOperationException("User context is not set")
79+
? throw new System.InvalidOperationException(UserContextNotSetMessage)
7880
: new ValueTask<TEntity?>(default(TEntity));
7981

8082
return FindScopedAsync(key, userId, cancellationToken);
@@ -102,7 +104,7 @@ public ValueTask AddRangeAsync(IEnumerable<TEntity> entities, CancellationToken
102104
System.ArgumentNullException.ThrowIfNull(entities);
103105

104106
var userId = _userAccessor.GetUserId();
105-
if (userId != null)
107+
if (!EqualityComparer<TUserKey>.Default.Equals(userId, default))
106108
{
107109
foreach (var entity in entities)
108110
{
@@ -111,7 +113,7 @@ public ValueTask AddRangeAsync(IEnumerable<TEntity> entities, CancellationToken
111113
}
112114
else if (Options.ThrowWhenUserNotSet)
113115
{
114-
throw new System.InvalidOperationException("User context is not set");
116+
throw new System.InvalidOperationException(UserContextNotSetMessage);
115117
}
116118

117119
return _inner.AddRangeAsync(entities, cancellationToken);
@@ -162,13 +164,13 @@ private ValueTask ApplyOwnerAndCallAsync(TEntity entity, Func<ValueTask> action)
162164
System.ArgumentNullException.ThrowIfNull(entity);
163165

164166
var userId = _userAccessor.GetUserId();
165-
if (userId != null)
167+
if (!EqualityComparer<TUserKey>.Default.Equals(userId, default))
166168
{
167169
_ownerProperty.Value.SetValue(entity, userId);
168170
}
169171
else if (Options.ThrowWhenUserNotSet)
170172
{
171-
throw new System.InvalidOperationException("User context is not set");
173+
throw new System.InvalidOperationException(UserContextNotSetMessage);
172174
}
173175

174176
return action();
@@ -178,9 +180,9 @@ private async ValueTask<IList<TEntity>> ApplyOwnerFilterAndCallAsync(
178180
IQuery query, Func<IQuery, ValueTask<IList<TEntity>>> action)
179181
{
180182
var userId = _userAccessor.GetUserId();
181-
if (userId == null)
183+
if (EqualityComparer<TUserKey>.Default.Equals(userId, default))
182184
return Options.ThrowWhenUserNotSet
183-
? throw new System.InvalidOperationException("User context is not set")
185+
? throw new System.InvalidOperationException(UserContextNotSetMessage)
184186
: Array.Empty<TEntity>();
185187

186188
var scopedQuery = ApplyOwnerToQuery(query, userId);
@@ -191,7 +193,7 @@ private async ValueTask<IList<TEntity>> ApplyOwnerFilterAndCallAsync(
191193
IQuery query, Func<IQuery, ValueTask<TEntity?>> action)
192194
{
193195
var userId = _userAccessor.GetUserId();
194-
if (userId == null)
196+
if (EqualityComparer<TUserKey>.Default.Equals(userId, default))
195197
return null;
196198

197199
var scopedQuery = ApplyOwnerToQuery(query, userId);
@@ -202,7 +204,7 @@ private async ValueTask<long> ApplyOwnerFilterAndCallAsync(
202204
IQueryFilter filter, Func<IQueryFilter, ValueTask<long>> action)
203205
{
204206
var userId = _userAccessor.GetUserId();
205-
if (userId == null)
207+
if (EqualityComparer<TUserKey>.Default.Equals(userId, default))
206208
return 0;
207209

208210
var ownerFilter = BuildOwnerFilter(userId);
@@ -214,7 +216,7 @@ private async ValueTask<bool> ApplyOwnerFilterAndCallAsync(
214216
IQueryFilter filter, Func<IQueryFilter, ValueTask<bool>> action)
215217
{
216218
var userId = _userAccessor.GetUserId();
217-
if (userId == null)
219+
if (EqualityComparer<TUserKey>.Default.Equals(userId, default))
218220
return false;
219221

220222
var ownerFilter = BuildOwnerFilter(userId);
@@ -226,9 +228,9 @@ private async ValueTask<PageResult<TEntity>> ApplyOwnerFilterAndCallAsync(
226228
PageQuery<TEntity> request, Func<PageQuery<TEntity>, ValueTask<PageResult<TEntity>>> action)
227229
{
228230
var userId = _userAccessor.GetUserId();
229-
if (userId == null)
231+
if (EqualityComparer<TUserKey>.Default.Equals(userId, default))
230232
return Options.ThrowWhenUserNotSet
231-
? throw new System.InvalidOperationException("User context is not set")
233+
? throw new System.InvalidOperationException(UserContextNotSetMessage)
232234
: new PageResult<TEntity>(request, 0, Array.Empty<TEntity>());
233235

234236
var scopedRequest = ApplyOwnerToRequest(request, userId);
@@ -261,9 +263,8 @@ private static PropertyInfo DiscoverOwnerProperty()
261263

262264
foreach (var prop in entityType.GetProperties(BindingFlags.Public | BindingFlags.Instance))
263265
{
264-
foreach (var attr in prop.GetCustomAttributes())
266+
foreach (var attrType in prop.GetCustomAttributes().Select(attr => attr.GetType()))
265267
{
266-
var attrType = attr.GetType();
267268
if (attrType.Name == "DataOwnerAttribute" &&
268269
(attrType.Namespace == "Kista" || attrType.Namespace == "Kista.Owners"))
269270
{
@@ -287,7 +288,7 @@ private static PropertyInfo DiscoverOwnerProperty()
287288

288289
// === FILTER BUILDING ===
289290

290-
private IQueryFilter BuildOwnerFilter(TUserKey userId)
291+
private static IQueryFilter BuildOwnerFilter(TUserKey userId)
291292
{
292293
return new ExpressionQueryFilter<TEntity>(BuildOwnerExpression(userId));
293294
}

src/Kista/RepositoryContextBuilder.cs

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -97,10 +97,14 @@ internal void TrackRepositoryType(Type repositoryType) {
9797
/// further type-specific configuration (e.g. owner scoping).
9898
/// </summary>
9999
public RepositoryBuilder AddRepository<TRepository>(ServiceLifetime lifetime = ServiceLifetime.Scoped) where TRepository : class {
100-
_services.AddRepository(typeof(TRepository), lifetime);
100+
var repoInterface = FindRepositoryInterface(typeof(TRepository));
101+
var serviceTypes = RepositoryRegistrationUtil.GetRepositoryServiceTypes(typeof(TRepository));
102+
foreach (var serviceType in serviceTypes) {
103+
_services.TryAdd(new ServiceDescriptor(serviceType, typeof(TRepository), lifetime));
104+
}
105+
_services.TryAdd(new ServiceDescriptor(typeof(TRepository), typeof(TRepository), lifetime));
101106
TrackRepositoryType(typeof(TRepository));
102107

103-
var repoInterface = FindRepositoryInterface(typeof(TRepository));
104108
var entityType = repoInterface.GetGenericArguments()[0];
105109
var keyType = repoInterface.GetGenericArguments()[1];
106110

@@ -117,6 +121,19 @@ public RepositoryContextBuilder AddRepository<TRepository>(Action<RepositoryBuil
117121
return this;
118122
}
119123

124+
/// <summary>
125+
/// Registers a repository type in the service collection and tracks it.
126+
/// </summary>
127+
public RepositoryContextBuilder AddRepository(Type repositoryType, ServiceLifetime lifetime = ServiceLifetime.Scoped) {
128+
if (repositoryType.IsGenericTypeDefinition) {
129+
RegisterOpenGenericRepository(repositoryType, lifetime);
130+
} else {
131+
_services.AddRepository(repositoryType, lifetime);
132+
}
133+
TrackRepositoryType(repositoryType);
134+
return this;
135+
}
136+
120137
private static Type FindRepositoryInterface(Type repositoryType) {
121138
foreach (var iface in repositoryType.GetInterfaces()) {
122139
if (iface.IsGenericType &&
@@ -129,19 +146,6 @@ private static Type FindRepositoryInterface(Type repositoryType) {
129146
$"The type '{repositoryType}' does not implement IRepository<,>");
130147
}
131148

132-
/// <summary>
133-
/// Registers a repository type in the service collection and tracks it.
134-
/// </summary>
135-
public RepositoryContextBuilder AddRepository(Type repositoryType, ServiceLifetime lifetime = ServiceLifetime.Scoped) {
136-
if (repositoryType.IsGenericTypeDefinition) {
137-
RegisterOpenGenericRepository(repositoryType, lifetime);
138-
} else {
139-
_services.AddRepository(repositoryType, lifetime);
140-
}
141-
TrackRepositoryType(repositoryType);
142-
return this;
143-
}
144-
145149
/// <summary>
146150
/// Registers an open generic repository type with the service collection.
147151
/// </summary>

test/Kista.Manager.XUnit/Unit/UserAccessorTests.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public void StaticUserIdentifierStrategy_Should_ReturnUserId_RegardlessOfService
2525

2626
var userId = strategy.GetUserId(null);
2727

28-
Assert.NotEqual(default(Guid), userId);
28+
Assert.NotEqual(Guid.Empty, userId);
2929
}
3030

3131
#endregion
@@ -170,7 +170,7 @@ public void ClaimUserIdentifierStrategy_Should_ReturnDefault_WhenConversionFails
170170

171171
var userId = strategy.GetUserId(services);
172172

173-
Assert.Equal(default(Guid), userId);
173+
Assert.Equal(Guid.Empty, userId);
174174
}
175175

176176
#endregion
@@ -456,7 +456,7 @@ private static IServiceProvider CreateServicesWithEmptyContext() {
456456
return services.BuildServiceProvider();
457457
}
458458

459-
private class NullReturningStrategy<TKey> : IUserIdentifierStrategy<TKey> {
459+
private sealed class NullReturningStrategy<TKey> : IUserIdentifierStrategy<TKey> {
460460
public TKey? GetUserId(IServiceProvider? serviceProvider = null) => default;
461461
}
462462

0 commit comments

Comments
 (0)