From fe9edd891b46234597fc8849733386a10463a8e5 Mon Sep 17 00:00:00 2001 From: Sergey Nazarov Date: Sat, 23 Mar 2024 01:11:25 +0400 Subject: [PATCH] Add user --- .../Users/Dto/CreateUserRequest.cs | 4 +- .../Users/IUsersController.cs | 4 ++ .../Users/UserData.cs | 8 ++++ src/Nocr.Users.Api.Contracts/WebRoutes.cs | 2 + .../Nocr.Users.AppServices.csproj | 1 + .../ServiceCollectionExtensions.cs | 5 +- .../Users/IUsersRepository.cs | 8 ++++ .../Users/IUsersService.cs | 11 +++++ .../Users/InMemoryUsersRepository.cs | 29 ++++++++++++ src/Nocr.Users.AppServices/Users/User.cs | 20 ++++++-- .../Users/UserIdentity.cs | 34 ++++++++++++++ .../Users/UserIdentityType.cs | 8 ++++ .../Users/UsersService.cs | 46 +++++++++++++++++++ .../Controllers/UsersController.cs | 30 ++++++++++++ src/Nocr.Users.Host/Nocr.Users.Host.csproj | 3 +- 15 files changed, 206 insertions(+), 7 deletions(-) create mode 100644 src/Nocr.Users.Api.Contracts/Users/UserData.cs create mode 100644 src/Nocr.Users.AppServices/Users/IUsersRepository.cs create mode 100644 src/Nocr.Users.AppServices/Users/IUsersService.cs create mode 100644 src/Nocr.Users.AppServices/Users/InMemoryUsersRepository.cs create mode 100644 src/Nocr.Users.AppServices/Users/UserIdentity.cs create mode 100644 src/Nocr.Users.AppServices/Users/UserIdentityType.cs create mode 100644 src/Nocr.Users.AppServices/Users/UsersService.cs create mode 100644 src/Nocr.Users.Host/Controllers/UsersController.cs diff --git a/src/Nocr.Users.Api.Contracts/Users/Dto/CreateUserRequest.cs b/src/Nocr.Users.Api.Contracts/Users/Dto/CreateUserRequest.cs index 2d72be4..3db4d0e 100644 --- a/src/Nocr.Users.Api.Contracts/Users/Dto/CreateUserRequest.cs +++ b/src/Nocr.Users.Api.Contracts/Users/Dto/CreateUserRequest.cs @@ -1,4 +1,4 @@ -namespace Nocr.Users.Api.Contracts.Users; +namespace Nocr.Users.Api.Contracts.Users.Dto; public sealed class CreateUserRequest { @@ -7,4 +7,6 @@ public sealed class CreateUserRequest public string? Email { get; set; } public long? TelegramId { get; set; } + + public string? TelegramUsername { get; set; } } \ No newline at end of file diff --git a/src/Nocr.Users.Api.Contracts/Users/IUsersController.cs b/src/Nocr.Users.Api.Contracts/Users/IUsersController.cs index 37fa074..ac25179 100644 --- a/src/Nocr.Users.Api.Contracts/Users/IUsersController.cs +++ b/src/Nocr.Users.Api.Contracts/Users/IUsersController.cs @@ -1,3 +1,4 @@ +using Nocr.Users.Api.Contracts.Users.Dto; using RestEase; namespace Nocr.Users.Api.Contracts.Users; @@ -7,4 +8,7 @@ public interface IUsersController { [Post] Task Create([Body] CreateUserRequest request, CancellationToken cancellationToken = default); + + [Get(WebRoutes.Users.ById)] + Task GetById([Path] long id, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/src/Nocr.Users.Api.Contracts/Users/UserData.cs b/src/Nocr.Users.Api.Contracts/Users/UserData.cs new file mode 100644 index 0000000..bb2de16 --- /dev/null +++ b/src/Nocr.Users.Api.Contracts/Users/UserData.cs @@ -0,0 +1,8 @@ +namespace Nocr.Users.Api.Contracts.Users; + +public sealed class UserData +{ + public long Id { get; set; } + + public string Username { get; set; } +} \ No newline at end of file diff --git a/src/Nocr.Users.Api.Contracts/WebRoutes.cs b/src/Nocr.Users.Api.Contracts/WebRoutes.cs index 211af22..ffbcafb 100644 --- a/src/Nocr.Users.Api.Contracts/WebRoutes.cs +++ b/src/Nocr.Users.Api.Contracts/WebRoutes.cs @@ -7,5 +7,7 @@ public static class WebRoutes public static class Users { public const string Path = BasePath + "/" + "users"; + + public const string ById = "{id}"; } } \ No newline at end of file diff --git a/src/Nocr.Users.AppServices/Nocr.Users.AppServices.csproj b/src/Nocr.Users.AppServices/Nocr.Users.AppServices.csproj index 97bc233..b7146b5 100644 --- a/src/Nocr.Users.AppServices/Nocr.Users.AppServices.csproj +++ b/src/Nocr.Users.AppServices/Nocr.Users.AppServices.csproj @@ -9,6 +9,7 @@ + diff --git a/src/Nocr.Users.AppServices/ServiceCollectionExtensions.cs b/src/Nocr.Users.AppServices/ServiceCollectionExtensions.cs index bbfa6ab..87ea7f6 100644 --- a/src/Nocr.Users.AppServices/ServiceCollectionExtensions.cs +++ b/src/Nocr.Users.AppServices/ServiceCollectionExtensions.cs @@ -1,4 +1,5 @@ using Microsoft.Extensions.DependencyInjection; +using Nocr.Users.AppServices.Users; namespace Nocr.Users.AppServices; @@ -10,7 +11,9 @@ public static class ServiceCollectionExtensions throw new ArgumentNullException(nameof(services)); // Add registrations here - + services.AddScoped(); + services.AddScoped(); + return services; } } \ No newline at end of file diff --git a/src/Nocr.Users.AppServices/Users/IUsersRepository.cs b/src/Nocr.Users.AppServices/Users/IUsersRepository.cs new file mode 100644 index 0000000..b898ca2 --- /dev/null +++ b/src/Nocr.Users.AppServices/Users/IUsersRepository.cs @@ -0,0 +1,8 @@ +namespace Nocr.Users.AppServices.Users; + +public interface IUsersRepository +{ + Task Create(User user, CancellationToken cancellationToken = default); + + Task GetUserById(long id, CancellationToken cancellationToken = default); +} \ No newline at end of file diff --git a/src/Nocr.Users.AppServices/Users/IUsersService.cs b/src/Nocr.Users.AppServices/Users/IUsersService.cs new file mode 100644 index 0000000..f63c715 --- /dev/null +++ b/src/Nocr.Users.AppServices/Users/IUsersService.cs @@ -0,0 +1,11 @@ +using Nocr.Users.Api.Contracts.Users; + +namespace Nocr.Users.AppServices.Users; + +public interface IUsersService +{ + Task Create(string username, string? telegramUsername, string? email, long? telegramId, + CancellationToken cancellationToken = default); + + Task GetUserById(long id, CancellationToken cancellationToken = default); +} \ No newline at end of file diff --git a/src/Nocr.Users.AppServices/Users/InMemoryUsersRepository.cs b/src/Nocr.Users.AppServices/Users/InMemoryUsersRepository.cs new file mode 100644 index 0000000..808e54c --- /dev/null +++ b/src/Nocr.Users.AppServices/Users/InMemoryUsersRepository.cs @@ -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 _users = new(); + + public Task 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 GetUserById(long id, CancellationToken cancellationToken = default) + { + return Task.FromResult(_users.FirstOrDefault(x => x.Id == id)); + } +} \ No newline at end of file diff --git a/src/Nocr.Users.AppServices/Users/User.cs b/src/Nocr.Users.AppServices/Users/User.cs index acfb92e..c8501e7 100644 --- a/src/Nocr.Users.AppServices/Users/User.cs +++ b/src/Nocr.Users.AppServices/Users/User.cs @@ -6,18 +6,30 @@ public sealed class User public string Username { get; private set; } - private User(string username) + private List _identities = new List(); + + public IReadOnlyCollection Identities => _identities; + + private User(string username, params UserIdentity[] identities) { 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)) { 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); } } \ No newline at end of file diff --git a/src/Nocr.Users.AppServices/Users/UserIdentity.cs b/src/Nocr.Users.AppServices/Users/UserIdentity.cs new file mode 100644 index 0000000..f51b46a --- /dev/null +++ b/src/Nocr.Users.AppServices/Users/UserIdentity.cs @@ -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); + } +} \ No newline at end of file diff --git a/src/Nocr.Users.AppServices/Users/UserIdentityType.cs b/src/Nocr.Users.AppServices/Users/UserIdentityType.cs new file mode 100644 index 0000000..01b6fec --- /dev/null +++ b/src/Nocr.Users.AppServices/Users/UserIdentityType.cs @@ -0,0 +1,8 @@ +namespace Nocr.Users.AppServices.Users; + +public enum UserIdentityType +{ + Email = 1, + TelegramId = 2, + TelegramUsername = 3, +} \ No newline at end of file diff --git a/src/Nocr.Users.AppServices/Users/UsersService.cs b/src/Nocr.Users.AppServices/Users/UsersService.cs new file mode 100644 index 0000000..4226286 --- /dev/null +++ b/src/Nocr.Users.AppServices/Users/UsersService.cs @@ -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 Create(string username, string? telegramUsername, string? email, long? telegramId, + CancellationToken cancellationToken = default) + { + var identities = new List(); + + 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 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, + }; + } +} \ No newline at end of file diff --git a/src/Nocr.Users.Host/Controllers/UsersController.cs b/src/Nocr.Users.Host/Controllers/UsersController.cs new file mode 100644 index 0000000..d36b63a --- /dev/null +++ b/src/Nocr.Users.Host/Controllers/UsersController.cs @@ -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 Create([FromBody] CreateUserRequest request, CancellationToken cancellationToken = default) + { + return _usersService.Create(request.Username, request.TelegramUsername, request.Email, request.TelegramId, cancellationToken); + } + [HttpGet("{id}")] + public Task GetById([FromRoute] long id, CancellationToken cancellationToken = default) + { + return _usersService.GetUserById(id, cancellationToken); + } +} \ No newline at end of file diff --git a/src/Nocr.Users.Host/Nocr.Users.Host.csproj b/src/Nocr.Users.Host/Nocr.Users.Host.csproj index 061dccb..d5bc9be 100644 --- a/src/Nocr.Users.Host/Nocr.Users.Host.csproj +++ b/src/Nocr.Users.Host/Nocr.Users.Host.csproj @@ -12,7 +12,8 @@ - + +