fleshout backend
This commit is contained in:
@@ -1,5 +1,22 @@
|
||||
namespace Backend.Data;
|
||||
|
||||
/// <summary>
|
||||
/// Storage-agnostic access to <see cref="User"/>. Implementations are selected by the
|
||||
/// <c>Persistence:Provider</c> setting; see <see cref="PersistenceServiceCollectionExtensions"/>.
|
||||
/// </summary>
|
||||
public interface IUserRepository
|
||||
{
|
||||
Task<IReadOnlyList<User>> GetAllAsync(CancellationToken cancellationToken = default);
|
||||
|
||||
Task<User?> GetByIdAsync(Guid id, CancellationToken cancellationToken = default);
|
||||
|
||||
Task<User?> GetByEmailAsync(string email, CancellationToken cancellationToken = default);
|
||||
|
||||
Task<User> AddAsync(User user, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <returns>The updated user, or <c>null</c> when no user has the given id.</returns>
|
||||
Task<User?> UpdateAsync(User user, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <returns><c>true</c> when a user was removed.</returns>
|
||||
Task<bool> DeleteAsync(Guid id, CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
58
backend/Data/InMemory/InMemoryUserRepository.cs
Normal file
58
backend/Data/InMemory/InMemoryUserRepository.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace Backend.Data.InMemory;
|
||||
|
||||
/// <summary>
|
||||
/// Default implementation so the skeleton runs with no database. State is per-process
|
||||
/// and lost on restart.
|
||||
/// </summary>
|
||||
public sealed class InMemoryUserRepository : IUserRepository
|
||||
{
|
||||
private readonly ConcurrentDictionary<Guid, User> _users = new();
|
||||
|
||||
public Task<IReadOnlyList<User>> GetAllAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
IReadOnlyList<User> users = _users.Values
|
||||
.OrderBy(user => user.CreatedAt)
|
||||
.ToList();
|
||||
|
||||
return Task.FromResult(users);
|
||||
}
|
||||
|
||||
public Task<User?> GetByIdAsync(Guid id, CancellationToken cancellationToken = default)
|
||||
{
|
||||
_users.TryGetValue(id, out var user);
|
||||
|
||||
return Task.FromResult(user);
|
||||
}
|
||||
|
||||
public Task<User?> GetByEmailAsync(string email, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var user = _users.Values
|
||||
.FirstOrDefault(candidate => string.Equals(candidate.Email, email, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
return Task.FromResult(user);
|
||||
}
|
||||
|
||||
public Task<User> AddAsync(User user, CancellationToken cancellationToken = default)
|
||||
{
|
||||
_users[user.Id] = user;
|
||||
|
||||
return Task.FromResult(user);
|
||||
}
|
||||
|
||||
public Task<User?> UpdateAsync(User user, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (!_users.ContainsKey(user.Id))
|
||||
{
|
||||
return Task.FromResult<User?>(null);
|
||||
}
|
||||
|
||||
_users[user.Id] = user;
|
||||
|
||||
return Task.FromResult<User?>(user);
|
||||
}
|
||||
|
||||
public Task<bool> DeleteAsync(Guid id, CancellationToken cancellationToken = default)
|
||||
=> Task.FromResult(_users.TryRemove(id, out _));
|
||||
}
|
||||
13
backend/Data/PersistenceOptions.cs
Normal file
13
backend/Data/PersistenceOptions.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
namespace Backend.Data;
|
||||
|
||||
public sealed class PersistenceOptions
|
||||
{
|
||||
public const string SectionName = "Persistence";
|
||||
|
||||
public string Provider { get; set; } = PersistenceProviders.InMemory;
|
||||
}
|
||||
|
||||
public static class PersistenceProviders
|
||||
{
|
||||
public const string InMemory = "InMemory";
|
||||
}
|
||||
37
backend/Data/PersistenceServiceCollectionExtensions.cs
Normal file
37
backend/Data/PersistenceServiceCollectionExtensions.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using Backend.Data.InMemory;
|
||||
|
||||
namespace Backend.Data;
|
||||
|
||||
/// <summary>
|
||||
/// Single place where a storage backend is chosen. To add a real database, implement
|
||||
/// <see cref="IUserRepository"/> and register it under a new provider name here;
|
||||
/// controllers stay untouched.
|
||||
/// </summary>
|
||||
public static class PersistenceServiceCollectionExtensions
|
||||
{
|
||||
public static IServiceCollection AddPersistence(
|
||||
this IServiceCollection services,
|
||||
IConfiguration configuration)
|
||||
{
|
||||
services.Configure<PersistenceOptions>(configuration.GetSection(PersistenceOptions.SectionName));
|
||||
|
||||
var provider = configuration.GetSection(PersistenceOptions.SectionName).Get<PersistenceOptions>()?.Provider
|
||||
?? PersistenceProviders.InMemory;
|
||||
|
||||
switch (provider)
|
||||
{
|
||||
case PersistenceProviders.InMemory:
|
||||
// Singleton because the store itself holds the data.
|
||||
services.AddSingleton<IUserRepository, InMemoryUserRepository>();
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new InvalidOperationException(
|
||||
$"Unknown persistence provider '{provider}'. Supported providers: " +
|
||||
$"'{PersistenceProviders.InMemory}'. Register additional providers in " +
|
||||
$"{nameof(PersistenceServiceCollectionExtensions)}.");
|
||||
}
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
15
backend/Data/User.cs
Normal file
15
backend/Data/User.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
namespace Backend.Data;
|
||||
|
||||
/// <summary>
|
||||
/// Placeholder domain entity. Replace with the real model once the project has one.
|
||||
/// </summary>
|
||||
public sealed class User
|
||||
{
|
||||
public Guid Id { get; init; } = Guid.NewGuid();
|
||||
|
||||
public required string Email { get; set; }
|
||||
|
||||
public required string DisplayName { get; set; }
|
||||
|
||||
public DateTime CreatedAt { get; init; } = DateTime.UtcNow;
|
||||
}
|
||||
Reference in New Issue
Block a user