This commit is contained in:
Sergey Nazarov 2024-03-23 01:11:25 +04:00
parent f406854dae
commit fe9edd891b
15 changed files with 206 additions and 7 deletions

View File

@ -1,4 +1,4 @@
namespace Nocr.Users.Api.Contracts.Users; namespace Nocr.Users.Api.Contracts.Users.Dto;
public sealed class CreateUserRequest public sealed class CreateUserRequest
{ {
@ -7,4 +7,6 @@ public sealed class CreateUserRequest
public string? Email { get; set; } public string? Email { get; set; }
public long? TelegramId { get; set; } public long? TelegramId { get; set; }
public string? TelegramUsername { get; set; }
} }

View File

@ -1,3 +1,4 @@
using Nocr.Users.Api.Contracts.Users.Dto;
using RestEase; using RestEase;
namespace Nocr.Users.Api.Contracts.Users; namespace Nocr.Users.Api.Contracts.Users;
@ -7,4 +8,7 @@ public interface IUsersController
{ {
[Post] [Post]
Task<long> Create([Body] CreateUserRequest request, CancellationToken cancellationToken = default); Task<long> Create([Body] CreateUserRequest request, CancellationToken cancellationToken = default);
[Get(WebRoutes.Users.ById)]
Task<UserData?> GetById([Path] long id, CancellationToken cancellationToken = default);
} }

View File

@ -0,0 +1,8 @@
namespace Nocr.Users.Api.Contracts.Users;
public sealed class UserData
{
public long Id { get; set; }
public string Username { get; set; }
}

View File

@ -7,5 +7,7 @@ public static class WebRoutes
public static class Users public static class Users
{ {
public const string Path = BasePath + "/" + "users"; public const string Path = BasePath + "/" + "users";
public const string ById = "{id}";
} }
} }

View File

@ -9,6 +9,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Nocr.Users.Api.Contracts\Nocr.Users.Api.Contracts.csproj" />
<ProjectReference Include="..\Nocr.Users.Core\Nocr.Users.Core.csproj" /> <ProjectReference Include="..\Nocr.Users.Core\Nocr.Users.Core.csproj" />
</ItemGroup> </ItemGroup>

View File

@ -1,4 +1,5 @@
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Nocr.Users.AppServices.Users;
namespace Nocr.Users.AppServices; namespace Nocr.Users.AppServices;
@ -10,7 +11,9 @@ public static class ServiceCollectionExtensions
throw new ArgumentNullException(nameof(services)); throw new ArgumentNullException(nameof(services));
// Add registrations here // Add registrations here
services.AddScoped<IUsersRepository, InMemoryUsersRepository>();
services.AddScoped<IUsersService, UsersService>();
return services; return services;
} }
} }

View File

@ -0,0 +1,8 @@
namespace Nocr.Users.AppServices.Users;
public interface IUsersRepository
{
Task<long> Create(User user, CancellationToken cancellationToken = default);
Task<User?> GetUserById(long id, CancellationToken cancellationToken = default);
}

View File

@ -0,0 +1,11 @@
using Nocr.Users.Api.Contracts.Users;
namespace Nocr.Users.AppServices.Users;
public interface IUsersService
{
Task<long> Create(string username, string? telegramUsername, string? email, long? telegramId,
CancellationToken cancellationToken = default);
Task<UserData?> GetUserById(long id, CancellationToken cancellationToken = default);
}

View File

@ -0,0 +1,29 @@
namespace Nocr.Users.AppServices.Users;
public sealed class InMemoryUsersRepository : IUsersRepository
{
private long _id = 0;
private long _userIdentityId = 0;
private readonly List<User> _users = new();
public Task<long> Create(User user, CancellationToken cancellationToken = default)
{
var id = Interlocked.Increment(ref _id);
user.Id = id;
_users.Add(user);
foreach (var identity in user.Identities)
{
identity.Id = Interlocked.Increment(ref _userIdentityId);
}
return Task.FromResult(id);
}
public Task<User?> GetUserById(long id, CancellationToken cancellationToken = default)
{
return Task.FromResult(_users.FirstOrDefault(x => x.Id == id));
}
}

View File

@ -6,18 +6,30 @@ public sealed class User
public string Username { get; private set; } public string Username { get; private set; }
private User(string username) private List<UserIdentity> _identities = new List<UserIdentity>();
public IReadOnlyCollection<UserIdentity> Identities => _identities;
private User(string username, params UserIdentity[] identities)
{ {
Username = username; Username = username;
// TODO: Check that there is no repeating identity types
_identities.AddRange(identities);
} }
public static User Initialize(string username) public static User Initialize(string username, params UserIdentity[] identities)
{ {
if (string.IsNullOrWhiteSpace(username)) if (string.IsNullOrWhiteSpace(username))
{ {
throw new ArgumentNullException(nameof(username)); throw new ArgumentNullException(nameof(username));
} }
return new User(username); if (username.Length is < 5 or > 32)
{
throw new ArgumentException("Username length should be between 5 and 32 symbols", username);
}
return new User(username, identities);
} }
} }

View File

@ -0,0 +1,34 @@
namespace Nocr.Users.AppServices.Users;
public sealed class UserIdentity
{
public long Id { get; set; }
public string Identity { get; private set; }
public UserIdentityType IdentityType { get; private set; }
private UserIdentity(string identity, UserIdentityType identityType)
{
if (string.IsNullOrWhiteSpace(identity))
throw new ArgumentNullException(nameof(identity));
Identity = identity;
IdentityType = identityType;
}
public static UserIdentity TelegramId(string identity)
{
return new UserIdentity(identity, UserIdentityType.TelegramId);
}
public static UserIdentity TelegramUsername(string identity)
{
return new UserIdentity(identity, UserIdentityType.TelegramUsername);
}
public static UserIdentity Email(string identity)
{
return new UserIdentity(identity, UserIdentityType.TelegramUsername);
}
}

View File

@ -0,0 +1,8 @@
namespace Nocr.Users.AppServices.Users;
public enum UserIdentityType
{
Email = 1,
TelegramId = 2,
TelegramUsername = 3,
}

View File

@ -0,0 +1,46 @@
using Nocr.Users.Api.Contracts.Users;
namespace Nocr.Users.AppServices.Users;
public sealed class UsersService : IUsersService
{
private readonly IUsersRepository _repository;
public UsersService(IUsersRepository repository)
{
_repository = repository ?? throw new ArgumentNullException(nameof(repository));
}
public async Task<long> Create(string username, string? telegramUsername, string? email, long? telegramId,
CancellationToken cancellationToken = default)
{
var identities = new List<UserIdentity>();
if (!string.IsNullOrWhiteSpace(email))
identities.Add(UserIdentity.Email(email));
if (!string.IsNullOrWhiteSpace(telegramUsername))
identities.Add(UserIdentity.TelegramUsername(telegramUsername));
if (telegramId.HasValue)
identities.Add(UserIdentity.TelegramId(telegramId.Value.ToString()));
// TODO: ToArray = грубо
var user = User.Initialize(username, identities.ToArray());
return await _repository.Create(user, cancellationToken);
}
public async Task<UserData?> GetUserById(long id, CancellationToken cancellationToken = default)
{
var user = await _repository.GetUserById(id, cancellationToken);
if (user == null)
return null;
return new UserData
{
Id = user.Id,
Username = user.Username,
};
}
}

View File

@ -0,0 +1,30 @@
using Microsoft.AspNetCore.Mvc;
using Nocr.Users.Api.Contracts;
using Nocr.Users.Api.Contracts.Users;
using Nocr.Users.Api.Contracts.Users.Dto;
using Nocr.Users.AppServices.Users;
namespace Nocr.Users.Host;
[ApiController]
[Route(WebRoutes.Users.Path)]
public class UsersController : ControllerBase
{
private readonly IUsersService _usersService;
public UsersController(IUsersService usersService)
{
_usersService = usersService ?? throw new ArgumentNullException(nameof(usersService));
}
[HttpPost]
public Task<long> Create([FromBody] CreateUserRequest request, CancellationToken cancellationToken = default)
{
return _usersService.Create(request.Username, request.TelegramUsername, request.Email, request.TelegramId, cancellationToken);
}
[HttpGet("{id}")]
public Task<UserData?> GetById([FromRoute] long id, CancellationToken cancellationToken = default)
{
return _usersService.GetUserById(id, cancellationToken);
}
}

View File

@ -12,7 +12,8 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Nocr.Users.AppServices\Nocr.Users.AppServices.csproj"/> <ProjectReference Include="..\Nocr.Users.Api.Contracts\Nocr.Users.Api.Contracts.csproj" />
<ProjectReference Include="..\Nocr.Users.AppServices\Nocr.Users.AppServices.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>