add account management

This commit is contained in:
cuqmbr 2025-05-28 15:40:30 +03:00
parent 2d7d23d26b
commit 7229a10ad5
Signed by: cuqmbr
GPG Key ID: 0AA446880C766199
24 changed files with 813 additions and 165 deletions

View File

@ -1,10 +1,13 @@
using cuqmbr.TravelGuide.Domain.Enums;
using MediatR;
namespace cuqmbr.TravelGuide.Application.Identity.Accounts.Commands.AddAccount;
namespace cuqmbr.TravelGuide.Application.Identity
.Accounts.Commands.AddAccount;
public record AddAccountCommand : IRequest<AccountDto>
{
public string Username { get; set; }
public string Email { get; set; }
public string Password { get; set; }

View File

@ -3,7 +3,8 @@ using cuqmbr.TravelGuide.Application.Common.Services;
using cuqmbr.TravelGuide.Domain.Enums;
using MediatR.Behaviors.Authorization;
namespace cuqmbr.TravelGuide.Application.Identity.Accounts.Commands.AddAccount;
namespace cuqmbr.TravelGuide.Application
.Identity.Accounts.Commands.AddAccount;
public class AddAccountCommandAuthorizer :
AbstractRequestAuthorizer<AddAccountCommand>

View File

@ -7,34 +7,33 @@ using cuqmbr.TravelGuide.Application.Common.Services;
using System.Security.Cryptography;
using System.Text;
namespace cuqmbr.TravelGuide.Application.Identity.Accounts.Commands.AddAccount;
namespace cuqmbr.TravelGuide.Application
.Identity.Accounts.Commands.AddAccount;
public class AddAccountCommandHandler :
IRequestHandler<AddAccountCommand, AccountDto>
{
private readonly UnitOfWork _unitOfWork;
private readonly IMapper _mapper;
private readonly PasswordHasherService _passwordHasherService;
private readonly PasswordHasherService _passwordHasher;
public AddAccountCommandHandler(
UnitOfWork unitOfWork,
IMapper mapper,
PasswordHasherService passwordHasherService)
public AddAccountCommandHandler(UnitOfWork unitOfWork,
IMapper mapper, PasswordHasherService passwordHasher)
{
_unitOfWork = unitOfWork;
_mapper = mapper;
_passwordHasherService = passwordHasherService;
_passwordHasher = passwordHasher;
}
public async Task<AccountDto> Handle(
AddAccountCommand request,
CancellationToken cancellationToken)
{
var user = await _unitOfWork.AccountRepository.GetOneAsync(
var account = await _unitOfWork.AccountRepository.GetOneAsync(
e => e.Email == request.Email,
cancellationToken);
if (user != null)
if (account != null)
{
throw new DuplicateEntityException();
}
@ -47,15 +46,16 @@ public class AddAccountCommandHandler :
.Items;
var salt = RandomNumberGenerator.GetBytes(128 / 8);
var hash = await _passwordHasherService.HashAsync(
var hash = await _passwordHasher.HashAsync(
Encoding.UTF8.GetBytes(request.Password),
salt, cancellationToken);
var saltBase64 = Convert.ToBase64String(salt);
var hashBase64 = Convert.ToBase64String(hash);
user = new Account()
account = new Account()
{
Username = request.Username,
Email = request.Email,
PasswordHash = hashBase64,
PasswordSalt = saltBase64,
@ -66,12 +66,12 @@ public class AddAccountCommandHandler :
.ToArray()
};
user = await _unitOfWork.AccountRepository.AddOneAsync(
user, cancellationToken);
account = await _unitOfWork.AccountRepository.AddOneAsync(
account, cancellationToken);
await _unitOfWork.SaveAsync(cancellationToken);
_unitOfWork.Dispose();
return _mapper.Map<AccountDto>(user);
return _mapper.Map<AccountDto>(account);
}
}

View File

@ -1,16 +1,37 @@
using cuqmbr.TravelGuide.Application.Common.FluentValidation;
using cuqmbr.TravelGuide.Application.Common.Services;
using cuqmbr.TravelGuide.Domain.Enums;
using FluentValidation;
using Microsoft.Extensions.Localization;
namespace cuqmbr.TravelGuide.Application.Identity.Accounts.Commands.AddAccount;
namespace cuqmbr.TravelGuide.Application
.Identity.Accounts.Commands.AddAccount;
public class AddAccountCommandValidator : AbstractValidator<AddAccountCommand>
public class AddAccountCommandValidator :
AbstractValidator<AddAccountCommand>
{
public AddAccountCommandValidator(
IStringLocalizer localizer,
SessionCultureService cultureService)
{
RuleFor(v => v.Username)
.NotEmpty()
.WithMessage(localizer["FluentValidation.NotEmpty"])
.MinimumLength(1)
.WithMessage(
String.Format(
cultureService.Culture,
localizer["FluentValidation.MinimumLength"],
1))
.MaximumLength(32)
.WithMessage(
String.Format(
cultureService.Culture,
localizer["FluentValidation.MaximumLength"],
32))
.IsUsername()
.WithMessage(localizer["FluentValidation.IsUsername"]);
RuleFor(v => v.Email)
.NotEmpty()
.WithMessage(localizer["FluentValidation.NotEmpty"])
@ -32,5 +53,18 @@ public class AddAccountCommandValidator : AbstractValidator<AddAccountCommand>
cultureService.Culture,
localizer["FluentValidation.MaximumLength"],
64));
RuleFor(v => v.Roles ?? new IdentityRole[0])
.IsUnique(r => r)
.WithMessage(localizer["FluentValidation.IsUnique"]);
RuleForEach(v => v.Roles)
.Must(r => IdentityRole.Enumerations.ContainsValue(r))
.WithMessage(
String.Format(
localizer["FluentValidation.MustBeInEnum"],
String.Join(
", ",
IdentityRole.Enumerations.Values.Select(e => e.Name))));
}
}

View File

@ -0,0 +1,9 @@
using MediatR;
namespace cuqmbr.TravelGuide.Application.Identity
.Accounts.Commands.DeleteAccount;
public record DeleteAccountCommand : IRequest
{
public Guid Guid { get; set; }
}

View File

@ -0,0 +1,32 @@
using cuqmbr.TravelGuide.Application.Common.Authorization;
using cuqmbr.TravelGuide.Application.Common.Services;
using cuqmbr.TravelGuide.Domain.Enums;
using MediatR.Behaviors.Authorization;
namespace cuqmbr.TravelGuide.Application.Identity
.Accounts.Commands.DeleteAccount;
public class DeleteAccountCommandAuthorizer :
AbstractRequestAuthorizer<DeleteAccountCommand>
{
private readonly SessionUserService _sessionUserService;
public DeleteAccountCommandAuthorizer(SessionUserService sessionUserService)
{
_sessionUserService = sessionUserService;
}
public override void BuildPolicy(DeleteAccountCommand request)
{
UseRequirement(new MustBeAuthenticatedRequirement
{
IsAuthenticated= _sessionUserService.IsAuthenticated
});
UseRequirement(new MustBeInRolesRequirement
{
RequiredRoles = [IdentityRole.Administrator],
UserRoles = _sessionUserService.Roles
});
}
}

View File

@ -0,0 +1,34 @@
using MediatR;
using cuqmbr.TravelGuide.Application.Common.Persistence;
using cuqmbr.TravelGuide.Application.Common.Exceptions;
namespace cuqmbr.TravelGuide.Application.Identity.Accounts.Commands.DeleteAccount;
public class DeleteAccountCommandHandler : IRequestHandler<DeleteAccountCommand>
{
private readonly UnitOfWork _unitOfWork;
public DeleteAccountCommandHandler(UnitOfWork unitOfWork)
{
_unitOfWork = unitOfWork;
}
public async Task Handle(
DeleteAccountCommand request,
CancellationToken cancellationToken)
{
var account = await _unitOfWork.AccountRepository.GetOneAsync(
e => e.Guid == request.Guid, cancellationToken);
if (account == null)
{
throw new NotFoundException();
}
await _unitOfWork.AccountRepository.DeleteOneAsync(
account, cancellationToken);
await _unitOfWork.SaveAsync(cancellationToken);
_unitOfWork.Dispose();
}
}

View File

@ -0,0 +1,14 @@
using FluentValidation;
using Microsoft.Extensions.Localization;
namespace cuqmbr.TravelGuide.Application.Identity.Accounts.Commands.DeleteAccount;
public class DeleteAccountCommandValidator : AbstractValidator<DeleteAccountCommand>
{
public DeleteAccountCommandValidator(IStringLocalizer localizer)
{
RuleFor(v => v.Guid)
.NotEmpty()
.WithMessage(localizer["FluentValidation.NotEmpty"]);
}
}

View File

@ -0,0 +1,18 @@
using MediatR;
using cuqmbr.TravelGuide.Domain.Enums;
namespace cuqmbr.TravelGuide.Application
.Identity.Accounts.Commands.UpdateAccount;
public record UpdateAccountCommand : IRequest<AccountDto>
{
public Guid Guid { get; set; }
public string? Username { get; set; }
public string? Email { get; set; }
public string? Password { get; set; }
public ICollection<IdentityRole>? Roles { get; set; }
}

View File

@ -0,0 +1,31 @@
using cuqmbr.TravelGuide.Application.Common.Authorization;
using cuqmbr.TravelGuide.Application.Common.Services;
using cuqmbr.TravelGuide.Domain.Enums;
using MediatR.Behaviors.Authorization;
namespace cuqmbr.TravelGuide.Application.Identity.Accounts.Commands.UpdateAccount;
public class UpdateAccountCommandAuthorizer :
AbstractRequestAuthorizer<UpdateAccountCommand>
{
private readonly SessionUserService _sessionUserService;
public UpdateAccountCommandAuthorizer(SessionUserService sessionUserService)
{
_sessionUserService = sessionUserService;
}
public override void BuildPolicy(UpdateAccountCommand request)
{
UseRequirement(new MustBeAuthenticatedRequirement
{
IsAuthenticated= _sessionUserService.IsAuthenticated
});
UseRequirement(new MustBeInRolesRequirement
{
RequiredRoles = [IdentityRole.Administrator],
UserRoles = _sessionUserService.Roles
});
}
}

View File

@ -0,0 +1,109 @@
using MediatR;
using cuqmbr.TravelGuide.Application.Common.Persistence;
using AutoMapper;
using cuqmbr.TravelGuide.Application.Common.Exceptions;
using System.Security.Cryptography;
using System.Text;
using cuqmbr.TravelGuide.Application.Common.Services;
using cuqmbr.TravelGuide.Domain.Entities;
namespace cuqmbr.TravelGuide.Application
.Identity.Accounts.Commands.UpdateAccount;
public class UpdateAccountCommandHandler :
IRequestHandler<UpdateAccountCommand, AccountDto>
{
private readonly UnitOfWork _unitOfWork;
private readonly IMapper _mapper;
private readonly PasswordHasherService _passwordHasher;
public UpdateAccountCommandHandler(UnitOfWork unitOfWork,
IMapper mapper, PasswordHasherService passwordHasher)
{
_unitOfWork = unitOfWork;
_mapper = mapper;
_passwordHasher = passwordHasher;
}
public async Task<AccountDto> Handle(
UpdateAccountCommand request,
CancellationToken cancellationToken)
{
var account = await _unitOfWork.AccountRepository
.GetOneAsync(e => e.Guid == request.Guid,
e => e.AccountRoles, cancellationToken);
if (account == null)
{
throw new NotFoundException();
}
account.Username = request.Username ?? account.Username;
account.Email = request.Email ?? account.Email;
if (request.Password != null)
{
var salt = RandomNumberGenerator.GetBytes(128 / 8);
var hash = await _passwordHasher.HashAsync(
Encoding.UTF8.GetBytes(request.Password),
salt, cancellationToken);
var saltBase64 = Convert.ToBase64String(salt);
var hashBase64 = Convert.ToBase64String(hash);
account.PasswordHash = hashBase64;
account.PasswordSalt = saltBase64;
}
if (request.Roles != null)
{
var requestRoleIds = (await _unitOfWork.RoleRepository
.GetPageAsync(
r => request.Roles.Contains(r.Value),
1, request.Roles.Count, cancellationToken))
.Items
.Select(r => r.Id);
var accountRoles = account.AccountRoles;
var accountRoleIds = accountRoles.Select(ar => ar.RoleId);
var commonRoleIds = requestRoleIds.Intersect(accountRoleIds);
var newRoleIds = requestRoleIds.Except(accountRoleIds);
var combinedRoleIds = commonRoleIds.Union(newRoleIds);
account.AccountRoles = combinedRoleIds.Select(rId =>
new AccountRole()
{
Id = accountRoles.FirstOrDefault(ar =>
ar.RoleId == rId)?.Id ?? default,
RoleId = rId
})
.ToList();
}
else
{
var accountRoleIds = account.AccountRoles.Select(ar => ar.RoleId);
var accountRoles = (await _unitOfWork.AccountRoleRepository
.GetPageAsync(
ar => accountRoleIds.Contains(ar.RoleId),
ar => ar.Role,
1, accountRoleIds.Count(), cancellationToken))
.Items;
account.AccountRoles = accountRoles.ToList();
}
account = await _unitOfWork.AccountRepository.UpdateOneAsync(
account, cancellationToken);
await _unitOfWork.SaveAsync(cancellationToken);
_unitOfWork.Dispose();
return _mapper.Map<AccountDto>(account);
}
}

View File

@ -0,0 +1,68 @@
using cuqmbr.TravelGuide.Application.Common.FluentValidation;
using cuqmbr.TravelGuide.Application.Common.Services;
using cuqmbr.TravelGuide.Domain.Enums;
using FluentValidation;
using Microsoft.Extensions.Localization;
namespace cuqmbr.TravelGuide.Application
.Identity.Accounts.Commands.UpdateAccount;
public class UpdateAccountCommandValidator :
AbstractValidator<UpdateAccountCommand>
{
public UpdateAccountCommandValidator(
IStringLocalizer localizer,
SessionCultureService cultureService)
{
RuleFor(v => v.Guid)
.NotEmpty()
.WithMessage(localizer["FluentValidation.NotEmpty"]);
RuleFor(v => v.Username)
.MinimumLength(1)
.WithMessage(
String.Format(
cultureService.Culture,
localizer["FluentValidation.MinimumLength"],
1))
.MaximumLength(32)
.WithMessage(
String.Format(
cultureService.Culture,
localizer["FluentValidation.MaximumLength"],
32))
.IsUsername()
.WithMessage(localizer["FluentValidation.IsUsername"]);
RuleFor(v => v.Email)
.IsEmail()
.WithMessage(localizer["FluentValidation.IsEmail"]);
RuleFor(v => v.Password)
.MinimumLength(8)
.WithMessage(
String.Format(
cultureService.Culture,
localizer["FluentValidation.MinimumLength"],
8))
.MaximumLength(64)
.WithMessage(
String.Format(
cultureService.Culture,
localizer["FluentValidation.MaximumLength"],
64));
RuleFor(v => v.Roles ?? new IdentityRole[0])
.IsUnique(r => r)
.WithMessage(localizer["FluentValidation.IsUnique"]);
RuleForEach(v => v.Roles)
.Must(r => IdentityRole.Enumerations.ContainsValue(r))
.WithMessage(
String.Format(
localizer["FluentValidation.MustBeInEnum"],
String.Join(
", ",
IdentityRole.Enumerations.Values.Select(e => e.Name))));
}
}

View File

@ -0,0 +1,8 @@
using MediatR;
namespace cuqmbr.TravelGuide.Application.Identity.Accounts.Queries.GetAccount;
public record GetAccountQuery : IRequest<AccountDto>
{
public Guid Guid { get; set; }
}

View File

@ -0,0 +1,31 @@
using cuqmbr.TravelGuide.Application.Common.Authorization;
using cuqmbr.TravelGuide.Application.Common.Services;
using cuqmbr.TravelGuide.Domain.Enums;
using MediatR.Behaviors.Authorization;
namespace cuqmbr.TravelGuide.Application.Identity.Accounts.Queries.GetAccount;
public class GetAccountQueryAuthorizer :
AbstractRequestAuthorizer<GetAccountQuery>
{
private readonly SessionUserService _sessionUserService;
public GetAccountQueryAuthorizer(SessionUserService sessionUserService)
{
_sessionUserService = sessionUserService;
}
public override void BuildPolicy(GetAccountQuery request)
{
UseRequirement(new MustBeAuthenticatedRequirement
{
IsAuthenticated= _sessionUserService.IsAuthenticated
});
UseRequirement(new MustBeInRolesRequirement
{
RequiredRoles = [IdentityRole.Administrator],
UserRoles = _sessionUserService.Roles
});
}
}

View File

@ -0,0 +1,51 @@
using MediatR;
using cuqmbr.TravelGuide.Application.Common.Persistence;
using cuqmbr.TravelGuide.Application.Common.Exceptions;
using AutoMapper;
namespace cuqmbr.TravelGuide.Application.Identity.Accounts.Queries.GetAccount;
public class GetAccountQueryHandler :
IRequestHandler<GetAccountQuery, AccountDto>
{
private readonly UnitOfWork _unitOfWork;
private readonly IMapper _mapper;
public GetAccountQueryHandler(
UnitOfWork unitOfWork,
IMapper mapper)
{
_unitOfWork = unitOfWork;
_mapper = mapper;
}
public async Task<AccountDto> Handle(
GetAccountQuery request,
CancellationToken cancellationToken)
{
var account = await _unitOfWork.AccountRepository.GetOneAsync(
e => e.Guid == request.Guid, e => e.AccountRoles,
cancellationToken);
if (account == null)
{
throw new NotFoundException();
}
var accountRoleIds = account.AccountRoles.Select(ar => ar.RoleId);
var accountRoles = (await _unitOfWork.AccountRoleRepository
.GetPageAsync(
ar => accountRoleIds.Contains(ar.RoleId),
ar => ar.Role,
1, accountRoleIds.Count(), cancellationToken))
.Items;
account.AccountRoles = accountRoles.ToList();
_unitOfWork.Dispose();
return _mapper.Map<AccountDto>(account);
}
}

View File

@ -0,0 +1,14 @@
using FluentValidation;
using Microsoft.Extensions.Localization;
namespace cuqmbr.TravelGuide.Application.Identity.Accounts.Queries.GetAccount;
public class GetAccountQueryValidator : AbstractValidator<GetAccountQuery>
{
public GetAccountQueryValidator(IStringLocalizer localizer)
{
RuleFor(v => v.Guid)
.NotEmpty()
.WithMessage(localizer["FluentValidation.NotEmpty"]);
}
}

View File

@ -0,0 +1,18 @@
using cuqmbr.TravelGuide.Application.Common.Models;
using cuqmbr.TravelGuide.Domain.Enums;
using MediatR;
namespace cuqmbr.TravelGuide.Application.Identity.Accounts.Queries.GetAccountsPage;
public record GetAccountsPageQuery : IRequest<PaginatedList<AccountDto>>
{
public int PageNumber { get; set; } = 1;
public int PageSize { get; set; } = 10;
public string Search { get; set; } = String.Empty;
public string Sort { get; set; } = String.Empty;
public ICollection<IdentityRole>? Roles { get; set; }
}

View File

@ -0,0 +1,31 @@
using cuqmbr.TravelGuide.Application.Common.Authorization;
using cuqmbr.TravelGuide.Application.Common.Services;
using cuqmbr.TravelGuide.Domain.Enums;
using MediatR.Behaviors.Authorization;
namespace cuqmbr.TravelGuide.Application.Identity.Accounts.Queries.GetAccountsPage;
public class GetAccountsPageQueryAuthorizer :
AbstractRequestAuthorizer<GetAccountsPageQuery>
{
private readonly SessionUserService _sessionUserService;
public GetAccountsPageQueryAuthorizer(SessionUserService sessionUserService)
{
_sessionUserService = sessionUserService;
}
public override void BuildPolicy(GetAccountsPageQuery request)
{
UseRequirement(new MustBeAuthenticatedRequirement
{
IsAuthenticated= _sessionUserService.IsAuthenticated
});
UseRequirement(new MustBeInRolesRequirement
{
RequiredRoles = [IdentityRole.Administrator],
UserRoles = _sessionUserService.Roles
});
}
}

View File

@ -0,0 +1,81 @@
using MediatR;
using cuqmbr.TravelGuide.Application.Common.Persistence;
using AutoMapper;
using cuqmbr.TravelGuide.Application.Common.Models;
using cuqmbr.TravelGuide.Application.Common.Extensions;
using cuqmbr.TravelGuide.Domain.Entities;
namespace cuqmbr.TravelGuide.Application.Identity.Accounts.Queries.GetAccountsPage;
public class GetAccountsPageQueryHandler :
IRequestHandler<GetAccountsPageQuery, PaginatedList<AccountDto>>
{
private readonly UnitOfWork _unitOfWork;
private readonly IMapper _mapper;
public GetAccountsPageQueryHandler(
UnitOfWork unitOfWork,
IMapper mapper)
{
_unitOfWork = unitOfWork;
_mapper = mapper;
}
public async Task<PaginatedList<AccountDto>> Handle(
GetAccountsPageQuery request,
CancellationToken cancellationToken)
{
var paginatedList = await _unitOfWork.AccountRepository.GetPageAsync(
a =>
(a.Username.ToLower().Contains(request.Search.ToLower()) ||
a.Email.ToLower().Contains(request.Search.ToLower())) &&
(request.Roles != null
? request.Roles.All(r => a.AccountRoles.Any(ar => ar.Role.Value == r))
: true),
a => a.AccountRoles,
request.PageNumber, request.PageSize, cancellationToken);
var accounts = paginatedList.Items;
var accountsRoleIds = accounts
.SelectMany(a => a.AccountRoles)
.Select(ar => ar.RoleId)
.Distinct();
var roles = (await _unitOfWork.RoleRepository
.GetPageAsync(
r => accountsRoleIds.Contains(r.Id),
1, accountsRoleIds.Count(), cancellationToken))
.Items;
foreach (var account in accounts)
{
account.AccountRoles = account.AccountRoles.Select(ar =>
new AccountRole()
{
RoleId = ar.RoleId,
Role = roles.Single(r => r.Id == ar.RoleId),
AccountId = account.Id,
Account = account
})
.ToArray();
}
var mappedItems = _mapper
.ProjectTo<AccountDto>(accounts.AsQueryable());
mappedItems = QueryableExtension<AccountDto>
.ApplySort(mappedItems, request.Sort);
_unitOfWork.Dispose();
return new PaginatedList<AccountDto>(
mappedItems.ToList(),
paginatedList.TotalCount, request.PageNumber,
request.PageSize);
throw new NotImplementedException();
}
}

View File

@ -0,0 +1,43 @@
using cuqmbr.TravelGuide.Application.Common.Services;
using FluentValidation;
using Microsoft.Extensions.Localization;
namespace cuqmbr.TravelGuide.Application.Identity.Accounts.Queries.GetAccountsPage;
public class GetAccountsPageQueryValidator : AbstractValidator<GetAccountsPageQuery>
{
public GetAccountsPageQueryValidator(
IStringLocalizer localizer,
SessionCultureService cultureService)
{
RuleFor(v => v.PageNumber)
.GreaterThanOrEqualTo(1)
.WithMessage(
String.Format(
cultureService.Culture,
localizer["FluentValidation.GreaterThanOrEqualTo"],
1));
RuleFor(v => v.PageSize)
.GreaterThanOrEqualTo(1)
.WithMessage(
String.Format(
cultureService.Culture,
localizer["FluentValidation.GreaterThanOrEqualTo"],
1))
.LessThanOrEqualTo(50)
.WithMessage(
String.Format(
cultureService.Culture,
localizer["FluentValidation.LessThanOrEqualTo"],
50));
RuleFor(v => v.Search)
.MaximumLength(64)
.WithMessage(
String.Format(
cultureService.Culture,
localizer["FluentValidation.MaximumLength"],
64));
}
}

View File

@ -2,6 +2,8 @@ namespace cuqmbr.TravelGuide.Application.Identity.Accounts.ViewModels;
public sealed class AddAccountViewModel
{
public string Username { get; set; }
public string Email { get; set; }
public string Password { get; set; }

View File

@ -0,0 +1,6 @@
namespace cuqmbr.TravelGuide.Application.Identity.Accounts.ViewModels;
public sealed class GetAccountsPageFilterViewModel
{
public ICollection<string>? Roles { get; set; }
}

View File

@ -0,0 +1,14 @@
namespace cuqmbr.TravelGuide.Application.Identity.Accounts.ViewModels;
public sealed class UpdateAccountViewModel
{
public Guid Uuid { get; set; }
public string? Username { get; set; }
public string? Email { get; set; }
public string? Password { get; set; }
public ICollection<string>? Roles { get; set; }
}

View File

@ -7,12 +7,10 @@ using cuqmbr.TravelGuide.Application.Identity.Roles.Queries.GetRolesPage;
using cuqmbr.TravelGuide.Application.Identity.Accounts;
using cuqmbr.TravelGuide.Application.Identity.Accounts.ViewModels;
using cuqmbr.TravelGuide.Application.Identity.Accounts.Commands.AddAccount;
// using cuqmbr.TravelGuide.Application.Identity.Commands.AddIdentity;
// using cuqmbr.TravelGuide.Application.Identity.Queries.GetIdentityPage;
// using cuqmbr.TravelGuide.Application.Identity.Queries.GetIdentity;
// using cuqmbr.TravelGuide.Application.Identity.Commands.UpdateIdentity;
// using cuqmbr.TravelGuide.Application.Identity.Commands.DeleteIdentity;
// using cuqmbr.TravelGuide.Application.Identity.ViewModels;
using cuqmbr.TravelGuide.Application.Identity.Accounts.Queries.GetAccountsPage;
using cuqmbr.TravelGuide.Application.Identity.Accounts.Queries.GetAccount;
using cuqmbr.TravelGuide.Application.Identity.Accounts.Commands.UpdateAccount;
using cuqmbr.TravelGuide.Application.Identity.Accounts.Commands.DeleteAccount;
namespace cuqmbr.TravelGuide.HttpApi.Controllers;
@ -78,7 +76,7 @@ public class IdentityController : ControllerBase
[SwaggerResponse(
StatusCodes.Status500InternalServerError, "Internal server error",
typeof(ProblemDetails))]
public async Task<ActionResult<AccountDto>> Add(
public async Task<ActionResult<AccountDto>> AddAccount(
[FromBody] AddAccountViewModel viewModel,
CancellationToken cancellationToken)
{
@ -87,6 +85,7 @@ public class IdentityController : ControllerBase
await Mediator.Send(
new AddAccountCommand()
{
Username = viewModel.Username,
Email = viewModel.Email,
Password = viewModel.Password,
Roles = viewModel.Roles
@ -96,147 +95,144 @@ public class IdentityController : ControllerBase
cancellationToken));
}
// [HttpPost]
// [SwaggerOperation("Add an identity")]
// [SwaggerResponse(
// StatusCodes.Status201Created, "Object successfuly created",
// typeof(IdentityDto))]
// [SwaggerResponse(
// StatusCodes.Status400BadRequest, "Object already exists",
// typeof(ProblemDetails))]
// [SwaggerResponse(
// StatusCodes.Status400BadRequest, "Input data validation error",
// typeof(HttpValidationProblemDetails))]
// [SwaggerResponse(
// StatusCodes.Status401Unauthorized, "Unauthorized to perform an action",
// typeof(ProblemDetails))]
// [SwaggerResponse(
// StatusCodes.Status403Forbidden,
// "Not enough privileges to perform an action",
// typeof(ProblemDetails))]
// [SwaggerResponse(
// StatusCodes.Status404NotFound, "Parent object not found",
// typeof(ProblemDetails))]
// [SwaggerResponse(
// StatusCodes.Status500InternalServerError, "Internal server error",
// typeof(ProblemDetails))]
// public async Task<ActionResult<IdentityDto>> Add(
// [FromBody] AddIdentityViewModel viewModel,
// CancellationToken cancellationToken)
// {
// return StatusCode(
// StatusCodes.Status201Created,
// await Mediator.Send(
// new AddIdentityCommand()
// {
// Name = viewModel.Name,
// Longitude = viewModel.Longitude,
// Latitude = viewModel.Latitude,
// VehicleType = VehicleType.FromName(viewModel.VehicleType),
// CityGuid = viewModel.CityUuid
// },
// cancellationToken));
// }
//
// [HttpGet("{uuid:guid}")]
// [SwaggerOperation("Get an identity by uuid")]
// [SwaggerResponse(
// StatusCodes.Status200OK, "Request successful", typeof(IdentityDto))]
// [SwaggerResponse(
// StatusCodes.Status400BadRequest, "Input data validation error",
// typeof(HttpValidationProblemDetails))]
// [SwaggerResponse(
// StatusCodes.Status401Unauthorized, "Unauthorized to perform an action",
// typeof(ProblemDetails))]
// [SwaggerResponse(
// StatusCodes.Status403Forbidden,
// "Not enough privileges to perform an action",
// typeof(ProblemDetails))]
// [SwaggerResponse(
// StatusCodes.Status404NotFound, "Object not found", typeof(IdentityDto))]
// [SwaggerResponse(
// StatusCodes.Status500InternalServerError, "Internal server error",
// typeof(ProblemDetails))]
// public async Task<IdentityDto> Get(
// [FromRoute] Guid uuid,
// CancellationToken cancellationToken)
// {
// return await Mediator.Send(new GetIdentityQuery() { Guid = uuid },
// cancellationToken);
// }
//
// [HttpPut("{uuid:guid}")]
// [SwaggerOperation("Update an identity")]
// [SwaggerResponse(
// StatusCodes.Status200OK, "Request successful", typeof(IdentityDto))]
// [SwaggerResponse(
// StatusCodes.Status400BadRequest, "Object already exists",
// typeof(ProblemDetails))]
// [SwaggerResponse(
// StatusCodes.Status400BadRequest, "Input data validation error",
// typeof(HttpValidationProblemDetails))]
// [SwaggerResponse(
// StatusCodes.Status401Unauthorized, "Unauthorized to perform an action",
// typeof(ProblemDetails))]
// [SwaggerResponse(
// StatusCodes.Status403Forbidden,
// "Not enough privileges to perform an action",
// typeof(ProblemDetails))]
// [SwaggerResponse(
// StatusCodes.Status404NotFound, "Object not found", typeof(IdentityDto))]
// [SwaggerResponse(
// StatusCodes.Status404NotFound, "Parent object not found",
// typeof(ProblemDetails))]
// [SwaggerResponse(
// StatusCodes.Status500InternalServerError, "Internal server error",
// typeof(ProblemDetails))]
// public async Task<IdentityDto> Update(
// [FromRoute] Guid uuid,
// [FromBody] UpdateIdentityViewModel viewModel,
// CancellationToken cancellationToken)
// {
// return await Mediator.Send(
// new UpdateIdentityCommand()
// {
// Guid = uuid,
// Name = viewModel.Name,
// Longitude = viewModel.Longitude,
// Latitude = viewModel.Latitude,
// VehicleType = VehicleType.FromName(viewModel.VehicleType),
// CityGuid = viewModel.CityUuid
// },
// cancellationToken);
// }
//
// [HttpDelete("{uuid:guid}")]
// [SwaggerOperation("Delete an identity")]
// [SwaggerResponse(StatusCodes.Status204NoContent, "Request successful")]
// [SwaggerResponse(
// StatusCodes.Status400BadRequest, "Input data validation error",
// typeof(HttpValidationProblemDetails))]
// [SwaggerResponse(
// StatusCodes.Status401Unauthorized, "Unauthorized to perform an action",
// typeof(ProblemDetails))]
// [SwaggerResponse(
// StatusCodes.Status403Forbidden,
// "Not enough privileges to perform an action",
// typeof(ProblemDetails))]
// [SwaggerResponse(
// StatusCodes.Status404NotFound, "Object not found",
// typeof(ProblemDetails))]
// [SwaggerResponse(
// StatusCodes.Status500InternalServerError, "Internal server error",
// typeof(ProblemDetails))]
// public async Task<IActionResult> Delete(
// [FromRoute] Guid uuid,
// CancellationToken cancellationToken)
// {
// await Mediator.Send(
// new DeleteIdentityCommand() { Guid = uuid },
// cancellationToken);
// return StatusCode(StatusCodes.Status204NoContent);
// }
[HttpGet("accounts")]
[SwaggerOperation("Get a list of all accounts")]
[SwaggerResponse(
StatusCodes.Status200OK, "Request successful",
typeof(PaginatedList<AccountDto>))]
[SwaggerResponse(
StatusCodes.Status400BadRequest, "Input data validation error",
typeof(HttpValidationProblemDetails))]
[SwaggerResponse(
StatusCodes.Status401Unauthorized, "Unauthorized to perform an action",
typeof(ProblemDetails))]
[SwaggerResponse(
StatusCodes.Status403Forbidden,
"Not enough privileges to perform an action",
typeof(ProblemDetails))]
[SwaggerResponse(
StatusCodes.Status500InternalServerError, "Internal server error",
typeof(ProblemDetails))]
public async Task<PaginatedList<AccountDto>> GetAccountsPage(
[FromQuery] PageQuery pageQuery, [FromQuery] SearchQuery searchQuery,
[FromQuery] SortQuery sortQuery,
[FromQuery] GetAccountsPageFilterViewModel filterQuery,
CancellationToken cancellationToken)
{
return await Mediator.Send(
new GetAccountsPageQuery()
{
PageNumber = pageQuery.PageNumber,
PageSize = pageQuery.PageSize,
Search = searchQuery.Search,
Sort = sortQuery.Sort,
Roles = filterQuery.Roles == null ? null :
filterQuery.Roles
.Select(s => IdentityRole.FromName(s))
.ToArray()
},
cancellationToken);
}
[HttpGet("accounts/{uuid:guid}")]
[SwaggerOperation("Get an account by uuid")]
[SwaggerResponse(
StatusCodes.Status200OK, "Request successful", typeof(AccountDto))]
[SwaggerResponse(
StatusCodes.Status400BadRequest, "Input data validation error",
typeof(HttpValidationProblemDetails))]
[SwaggerResponse(
StatusCodes.Status401Unauthorized, "Unauthorized to perform an action",
typeof(ProblemDetails))]
[SwaggerResponse(
StatusCodes.Status403Forbidden,
"Not enough privileges to perform an action",
typeof(ProblemDetails))]
[SwaggerResponse(
StatusCodes.Status404NotFound, "Object not found", typeof(ProblemDetails))]
[SwaggerResponse(
StatusCodes.Status500InternalServerError, "Internal server error",
typeof(ProblemDetails))]
public async Task<AccountDto> GetAccount(
[FromRoute] Guid uuid,
CancellationToken cancellationToken)
{
return await Mediator.Send(new GetAccountQuery() { Guid = uuid },
cancellationToken);
}
[HttpPut("accounts/{uuid:guid}")]
[SwaggerOperation("Update an account")]
[SwaggerResponse(
StatusCodes.Status200OK, "Request successful", typeof(AccountDto))]
[SwaggerResponse(
StatusCodes.Status400BadRequest, "Object already exists",
typeof(ProblemDetails))]
[SwaggerResponse(
StatusCodes.Status400BadRequest, "Input data validation error",
typeof(HttpValidationProblemDetails))]
[SwaggerResponse(
StatusCodes.Status401Unauthorized, "Unauthorized to perform an action",
typeof(ProblemDetails))]
[SwaggerResponse(
StatusCodes.Status403Forbidden,
"Not enough privileges to perform an action",
typeof(ProblemDetails))]
[SwaggerResponse(
StatusCodes.Status404NotFound, "Object not found",
typeof(ProblemDetails))]
[SwaggerResponse(
StatusCodes.Status404NotFound, "Parent object not found",
typeof(ProblemDetails))]
[SwaggerResponse(
StatusCodes.Status500InternalServerError, "Internal server error",
typeof(ProblemDetails))]
public async Task<AccountDto> UpdateAccount(
[FromRoute] Guid uuid,
[FromBody] UpdateAccountViewModel viewModel,
CancellationToken cancellationToken)
{
return await Mediator.Send(
new UpdateAccountCommand()
{
Guid = uuid,
Username = viewModel.Username,
Email = viewModel.Email,
Password = viewModel.Password,
Roles = viewModel.Roles == null ? null :
viewModel.Roles
.Select(s => IdentityRole.FromName(s))
.ToArray()
},
cancellationToken);
}
[HttpDelete("accounts/{uuid:guid}")]
[SwaggerOperation("Delete an account")]
[SwaggerResponse(StatusCodes.Status204NoContent, "Request successful")]
[SwaggerResponse(
StatusCodes.Status400BadRequest, "Input data validation error",
typeof(HttpValidationProblemDetails))]
[SwaggerResponse(
StatusCodes.Status401Unauthorized, "Unauthorized to perform an action",
typeof(ProblemDetails))]
[SwaggerResponse(
StatusCodes.Status403Forbidden,
"Not enough privileges to perform an action",
typeof(ProblemDetails))]
[SwaggerResponse(
StatusCodes.Status404NotFound, "Object not found",
typeof(ProblemDetails))]
[SwaggerResponse(
StatusCodes.Status500InternalServerError, "Internal server error",
typeof(ProblemDetails))]
public async Task<IActionResult> DeleteAccount(
[FromRoute] Guid uuid,
CancellationToken cancellationToken)
{
await Mediator.Send(
new DeleteAccountCommand() { Guid = uuid },
cancellationToken);
return StatusCode(StatusCodes.Status204NoContent);
}
}