Redo controllers
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
namespace Imprink.Application.Exceptions;
|
||||
|
||||
public class DataUpdateException : BaseApplicationException
|
||||
{
|
||||
public DataUpdateException(string message) : base(message) { }
|
||||
public DataUpdateException(string message, Exception innerException) : base(message, innerException) { }
|
||||
}
|
||||
@@ -10,7 +10,8 @@ public class UserDto
|
||||
public required string Nickname { get; set; }
|
||||
public required string Email { get; set; }
|
||||
public bool EmailVerified { get; set; }
|
||||
public string? FullName { get; set; }
|
||||
public string? FirstName { get; set; }
|
||||
public string? LastName { get; set; }
|
||||
public string? PhoneNumber { get; set; }
|
||||
public required bool IsActive { get; set; }
|
||||
|
||||
|
||||
48
src/Imprink.Application/Users/SetUserFullNameHandler.cs
Normal file
48
src/Imprink.Application/Users/SetUserFullNameHandler.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
using Imprink.Application.Exceptions;
|
||||
using Imprink.Application.Service;
|
||||
using Imprink.Application.Users.Dtos;
|
||||
using MediatR;
|
||||
|
||||
namespace Imprink.Application.Users;
|
||||
|
||||
public record SetUserFullNameCommand(string FirstName, string LastName) : IRequest<UserDto?>;
|
||||
|
||||
public class SetUserFullNameHandler(IUnitOfWork uw, ICurrentUserService userService) : IRequestHandler<SetUserFullNameCommand, UserDto?>
|
||||
{
|
||||
public async Task<UserDto?> Handle(SetUserFullNameCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
await uw.BeginTransactionAsync(cancellationToken);
|
||||
|
||||
try
|
||||
{
|
||||
var currentUser = userService.GetCurrentUserId();
|
||||
if (currentUser == null)
|
||||
throw new NotFoundException("User token could not be accessed.");
|
||||
|
||||
var user = await uw.UserRepository.SetUserFullNameAsync(currentUser, request.FirstName, request.LastName, cancellationToken);
|
||||
if (user == null)
|
||||
throw new DataUpdateException("User name could not be updated.");
|
||||
|
||||
await uw.SaveAsync(cancellationToken);
|
||||
await uw.CommitTransactionAsync(cancellationToken);
|
||||
|
||||
return new UserDto
|
||||
{
|
||||
Id = user.Id,
|
||||
Name = user.Name,
|
||||
Nickname = user.Nickname,
|
||||
Email = user.Email,
|
||||
EmailVerified = user.EmailVerified,
|
||||
FirstName = user.FirstName,
|
||||
LastName = user.LastName,
|
||||
PhoneNumber = user.PhoneNumber,
|
||||
IsActive = user.IsActive
|
||||
};
|
||||
}
|
||||
catch
|
||||
{
|
||||
await uw.RollbackTransactionAsync(cancellationToken);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,48 @@
|
||||
using Imprink.Application.Exceptions;
|
||||
using Imprink.Application.Service;
|
||||
using Imprink.Application.Users.Dtos;
|
||||
using MediatR;
|
||||
|
||||
namespace Imprink.Application.Users;
|
||||
|
||||
public record SetUserPhoneCommand(string Sub, Guid RoleId) : IRequest<UserRoleDto?>;
|
||||
public record SetUserPhoneCommand(string PhoneNumber) : IRequest<UserDto?>;
|
||||
|
||||
public class SetUserPhoneHandler
|
||||
public class SetUserPhoneHandler(IUnitOfWork uw, ICurrentUserService userService) : IRequestHandler<SetUserPhoneCommand, UserDto?>
|
||||
{
|
||||
|
||||
public async Task<UserDto?> Handle(SetUserPhoneCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
await uw.BeginTransactionAsync(cancellationToken);
|
||||
|
||||
try
|
||||
{
|
||||
var currentUser = userService.GetCurrentUserId();
|
||||
if (currentUser == null)
|
||||
throw new NotFoundException("User token could not be accessed.");
|
||||
|
||||
var user = await uw.UserRepository.SetUserPhoneAsync(currentUser, request.PhoneNumber, cancellationToken);
|
||||
if (user == null)
|
||||
throw new DataUpdateException("User phone could not be updated.");
|
||||
|
||||
await uw.SaveAsync(cancellationToken);
|
||||
await uw.CommitTransactionAsync(cancellationToken);
|
||||
|
||||
return new UserDto
|
||||
{
|
||||
Id = user.Id,
|
||||
Name = user.Name,
|
||||
Nickname = user.Nickname,
|
||||
Email = user.Email,
|
||||
EmailVerified = user.EmailVerified,
|
||||
FirstName = user.FirstName,
|
||||
LastName = user.LastName,
|
||||
PhoneNumber = user.PhoneNumber,
|
||||
IsActive = user.IsActive
|
||||
};
|
||||
}
|
||||
catch
|
||||
{
|
||||
await uw.RollbackTransactionAsync(cancellationToken);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,26 +1,38 @@
|
||||
using Imprink.Application.Users.Dtos;
|
||||
using Imprink.Domain.Models;
|
||||
using MediatR;
|
||||
|
||||
namespace Imprink.Application.Users;
|
||||
|
||||
public record SyncUserCommand(Auth0User User) : IRequest<bool>;
|
||||
public record SyncUserCommand(Auth0User User) : IRequest<UserDto?>;
|
||||
|
||||
public class SyncUserHandler(IUnitOfWork uw): IRequestHandler<SyncUserCommand, bool>
|
||||
public class SyncUserHandler(IUnitOfWork uw): IRequestHandler<SyncUserCommand, UserDto?>
|
||||
{
|
||||
public async Task<bool> Handle(SyncUserCommand request, CancellationToken cancellationToken)
|
||||
public async Task<UserDto?> Handle(SyncUserCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
await uw.BeginTransactionAsync(cancellationToken);
|
||||
|
||||
try
|
||||
{
|
||||
if (!await uw.UserRepository.UpdateOrCreateUserAsync(request.User, cancellationToken))
|
||||
{
|
||||
await uw.RollbackTransactionAsync(cancellationToken);
|
||||
}
|
||||
|
||||
var user = await uw.UserRepository.UpdateOrCreateUserAsync(request.User, cancellationToken);
|
||||
|
||||
if (user == null) throw new Exception("User exists but could not be updated");
|
||||
|
||||
await uw.SaveAsync(cancellationToken);
|
||||
await uw.CommitTransactionAsync(cancellationToken);
|
||||
return true;
|
||||
|
||||
return new UserDto
|
||||
{
|
||||
Id = user.Id,
|
||||
Name = user.Name,
|
||||
Nickname = user.Nickname,
|
||||
Email = user.Email,
|
||||
EmailVerified = user.EmailVerified,
|
||||
FirstName = user.FirstName,
|
||||
LastName = user.LastName,
|
||||
PhoneNumber = user.PhoneNumber,
|
||||
IsActive = user.IsActive
|
||||
};
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
@@ -9,7 +9,9 @@ public class User
|
||||
public required string Nickname { get; set; }
|
||||
public required string Email { get; set; }
|
||||
public bool EmailVerified { get; set; }
|
||||
public string? FullName { get; set; }
|
||||
|
||||
public string? FirstName { get; set; } = null!;
|
||||
public string? LastName { get; set; } = null!;
|
||||
public string? PhoneNumber { get; set; }
|
||||
public required bool IsActive { get; set; }
|
||||
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
using Imprink.Domain.Entities.Users;
|
||||
using Imprink.Domain.Models;
|
||||
|
||||
namespace Imprink.Domain.Repositories;
|
||||
namespace Imprink.Domain.Repositories.Users;
|
||||
|
||||
public interface IUserRepository
|
||||
{
|
||||
Task<bool> UpdateOrCreateUserAsync(Auth0User user, CancellationToken cancellationToken = default);
|
||||
Task<User?> UpdateOrCreateUserAsync(Auth0User user, CancellationToken cancellationToken = default);
|
||||
Task<User?> GetUserByIdAsync(string userId, CancellationToken cancellationToken = default);
|
||||
Task<User?> GetUserByEmailAsync(string email, CancellationToken cancellationToken = default);
|
||||
Task<IEnumerable<User>> GetAllUsersAsync(CancellationToken cancellationToken = default);
|
||||
Task<IEnumerable<User>> GetActiveUsersAsync(CancellationToken cancellationToken = default);
|
||||
Task<bool> UserExistsAsync(string userId, CancellationToken cancellationToken = default);
|
||||
Task<IEnumerable<User>> GetUsersByRoleAsync(Guid roleId, CancellationToken cancellationToken = default);
|
||||
Task<User?> SetUserPhoneAsync(string userId, string phoneNumber, CancellationToken cancellationToken = default);
|
||||
Task<User?> SetUserFullNameAsync(string userId, string firstName, string lastName, CancellationToken cancellationToken = default);
|
||||
Task<User?> GetUserWithAllRelatedDataAsync(string userId, CancellationToken cancellationToken = default);
|
||||
}
|
||||
@@ -28,7 +28,10 @@ public class UserConfiguration : IEntityTypeConfiguration<User>
|
||||
.IsRequired()
|
||||
.HasMaxLength(100);
|
||||
|
||||
builder.Property(u => u.FullName)
|
||||
builder.Property(u => u.FirstName)
|
||||
.HasMaxLength(100);
|
||||
|
||||
builder.Property(u => u.LastName)
|
||||
.HasMaxLength(100);
|
||||
|
||||
builder.Property(u => u.PhoneNumber)
|
||||
|
||||
@@ -12,7 +12,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
namespace Imprink.Infrastructure.Migrations
|
||||
{
|
||||
[DbContext(typeof(ApplicationDbContext))]
|
||||
[Migration("20250609202250_InitialSetup")]
|
||||
[Migration("20250610225629_InitialSetup")]
|
||||
partial class InitialSetup
|
||||
{
|
||||
/// <inheritdoc />
|
||||
@@ -743,11 +743,6 @@ namespace Imprink.Infrastructure.Migrations
|
||||
b.ToTable("Roles");
|
||||
|
||||
b.HasData(
|
||||
new
|
||||
{
|
||||
Id = new Guid("11111111-1111-1111-1111-111111111111"),
|
||||
RoleName = "User"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("22222222-2222-2222-2222-222222222222"),
|
||||
@@ -775,7 +770,7 @@ namespace Imprink.Infrastructure.Migrations
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<string>("FullName")
|
||||
b.Property<string>("FirstName")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
@@ -784,6 +779,10 @@ namespace Imprink.Infrastructure.Migrations
|
||||
.HasColumnType("bit")
|
||||
.HasDefaultValue(true);
|
||||
|
||||
b.Property<string>("LastName")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
@@ -85,7 +85,8 @@ namespace Imprink.Infrastructure.Migrations
|
||||
Nickname = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false),
|
||||
Email = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: false),
|
||||
EmailVerified = table.Column<bool>(type: "bit", maxLength: 100, nullable: false),
|
||||
FullName = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: true),
|
||||
FirstName = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: true),
|
||||
LastName = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: true),
|
||||
PhoneNumber = table.Column<string>(type: "nvarchar(20)", maxLength: 20, nullable: true),
|
||||
IsActive = table.Column<bool>(type: "bit", nullable: false, defaultValue: true)
|
||||
},
|
||||
@@ -339,7 +340,6 @@ namespace Imprink.Infrastructure.Migrations
|
||||
columns: new[] { "Id", "RoleName" },
|
||||
values: new object[,]
|
||||
{
|
||||
{ new Guid("11111111-1111-1111-1111-111111111111"), "User" },
|
||||
{ new Guid("22222222-2222-2222-2222-222222222222"), "Merchant" },
|
||||
{ new Guid("33333333-3333-3333-3333-333333333333"), "Admin" }
|
||||
});
|
||||
@@ -740,11 +740,6 @@ namespace Imprink.Infrastructure.Migrations
|
||||
b.ToTable("Roles");
|
||||
|
||||
b.HasData(
|
||||
new
|
||||
{
|
||||
Id = new Guid("11111111-1111-1111-1111-111111111111"),
|
||||
RoleName = "User"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("22222222-2222-2222-2222-222222222222"),
|
||||
@@ -772,7 +767,7 @@ namespace Imprink.Infrastructure.Migrations
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<string>("FullName")
|
||||
b.Property<string>("FirstName")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
@@ -781,6 +776,10 @@ namespace Imprink.Infrastructure.Migrations
|
||||
.HasColumnType("bit")
|
||||
.HasDefaultValue(true);
|
||||
|
||||
b.Property<string>("LastName")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Imprink.Domain.Entities.Users;
|
||||
using Imprink.Domain.Models;
|
||||
using Imprink.Domain.Repositories;
|
||||
using Imprink.Domain.Repositories.Users;
|
||||
using Imprink.Infrastructure.Database;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
@@ -8,7 +9,7 @@ namespace Imprink.Infrastructure.Repositories.Users;
|
||||
|
||||
public class UserRepository(ApplicationDbContext context) : IUserRepository
|
||||
{
|
||||
public async Task<bool> UpdateOrCreateUserAsync(Auth0User user, CancellationToken cancellationToken = default)
|
||||
public async Task<User?> UpdateOrCreateUserAsync(Auth0User user, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var userToUpdate = await context.Users
|
||||
.Where(u => u.Id.Equals(user.Sub))
|
||||
@@ -27,16 +28,15 @@ public class UserRepository(ApplicationDbContext context) : IUserRepository
|
||||
};
|
||||
|
||||
context.Users.Add(newUser);
|
||||
return newUser;
|
||||
}
|
||||
else
|
||||
{
|
||||
userToUpdate.Email = user.Email;
|
||||
userToUpdate.Name = user.Name;
|
||||
userToUpdate.Nickname = user.Nickname;
|
||||
userToUpdate.EmailVerified = user.EmailVerified;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
userToUpdate.Email = user.Email;
|
||||
userToUpdate.Name = user.Name;
|
||||
userToUpdate.Nickname = user.Nickname;
|
||||
userToUpdate.EmailVerified = user.EmailVerified;
|
||||
|
||||
return userToUpdate;
|
||||
}
|
||||
|
||||
public async Task<User?> GetUserByIdAsync(string userId, CancellationToken cancellationToken = default)
|
||||
@@ -92,4 +92,27 @@ public class UserRepository(ApplicationDbContext context) : IUserRepository
|
||||
.Include(u => u.Orders)
|
||||
.FirstOrDefaultAsync(u => u.Id == userId, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<User?> SetUserPhoneAsync(string userId, string phoneNumber, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var user = await context.Users
|
||||
.FirstOrDefaultAsync(u => u.Id == userId, cancellationToken);
|
||||
|
||||
if (user == null) return null;
|
||||
|
||||
user.PhoneNumber = phoneNumber;
|
||||
return user;
|
||||
}
|
||||
|
||||
public async Task<User?> SetUserFullNameAsync(string userId, string firstName, string lastName, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var user = await context.Users
|
||||
.FirstOrDefaultAsync(u => u.Id == userId, cancellationToken);
|
||||
|
||||
if (user == null) return null;
|
||||
|
||||
user.FirstName = firstName;
|
||||
user.LastName = lastName;
|
||||
return user;
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
using System.Security.Claims;
|
||||
using Imprink.Application.Users;
|
||||
using Imprink.Domain.Models;
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Imprink.WebApi.Controllers.Users;
|
||||
|
||||
[ApiController]
|
||||
[Route("/api/users")]
|
||||
public class UserController(IMediator mediator) : ControllerBase
|
||||
{
|
||||
[Authorize]
|
||||
[HttpPost("sync")]
|
||||
public async Task<IActionResult> Sync()
|
||||
{
|
||||
var claims = User.Claims as Claim[] ?? User.Claims.ToArray();
|
||||
|
||||
var auth0User = new Auth0User
|
||||
{
|
||||
Sub = claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier)?.Value ?? string.Empty,
|
||||
Name = claims.FirstOrDefault(c => c.Type == "name")?.Value ?? string.Empty,
|
||||
Nickname = claims.FirstOrDefault(c => c.Type == "nickname")?.Value ?? string.Empty,
|
||||
Email = claims.FirstOrDefault(c => c.Type == ClaimTypes.Email)?.Value ?? string.Empty,
|
||||
EmailVerified = bool.TryParse(claims.FirstOrDefault(c => c.Type == "email_verified")?.Value, out var emailVerified) && emailVerified
|
||||
};
|
||||
|
||||
await mediator.Send(new SyncUserCommand(auth0User));
|
||||
return Ok("User Synced.");
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
using System.Security.Claims;
|
||||
using Imprink.Application.Users;
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Imprink.WebApi.Controllers.Users;
|
||||
|
||||
[ApiController]
|
||||
[Route("/api/users/roles")]
|
||||
public class UserRoleController(IMediator mediator) : ControllerBase
|
||||
{
|
||||
[Authorize]
|
||||
[HttpGet("me")]
|
||||
public async Task<IActionResult> GetMyRoles()
|
||||
{
|
||||
var sub = User.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? string.Empty;
|
||||
return Ok(await mediator.Send(new GetUserRolesCommand(sub)));
|
||||
}
|
||||
|
||||
[Authorize(Roles = "Admin")]
|
||||
[HttpPost("set")]
|
||||
public async Task<IActionResult> SetUserRole(SetUserRoleCommand command)
|
||||
{
|
||||
return Ok(await mediator.Send(command));
|
||||
}
|
||||
|
||||
[Authorize(Roles = "Admin")]
|
||||
[HttpPost("unset")]
|
||||
public async Task<IActionResult> UnsetUserRole(DeleteUserRoleCommand command)
|
||||
{
|
||||
return Ok(await mediator.Send(command));
|
||||
}
|
||||
}
|
||||
70
src/Imprink.WebApi/Controllers/Users/UsersController.cs
Normal file
70
src/Imprink.WebApi/Controllers/Users/UsersController.cs
Normal file
@@ -0,0 +1,70 @@
|
||||
using System.Security.Claims;
|
||||
using Imprink.Application.Users;
|
||||
using Imprink.Domain.Models;
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Imprink.WebApi.Controllers.Users;
|
||||
|
||||
[ApiController]
|
||||
[Route("/api/users")]
|
||||
public class UsersController(IMediator mediator) : ControllerBase
|
||||
{
|
||||
[Authorize]
|
||||
[HttpPost("me/sync")]
|
||||
public async Task<IActionResult> SyncMyProfile()
|
||||
{
|
||||
var claims = User.Claims as Claim[] ?? User.Claims.ToArray();
|
||||
|
||||
var auth0User = new Auth0User
|
||||
{
|
||||
Sub = claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier)?.Value ?? string.Empty,
|
||||
Name = claims.FirstOrDefault(c => c.Type == "name")?.Value ?? string.Empty,
|
||||
Nickname = claims.FirstOrDefault(c => c.Type == "nickname")?.Value ?? string.Empty,
|
||||
Email = claims.FirstOrDefault(c => c.Type == ClaimTypes.Email)?.Value ?? string.Empty,
|
||||
EmailVerified = bool.TryParse(claims.FirstOrDefault(c => c.Type == "email_verified")?.Value, out var emailVerified) && emailVerified
|
||||
};
|
||||
|
||||
await mediator.Send(new SyncUserCommand(auth0User));
|
||||
return Ok("User profile synchronized.");
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
[HttpGet("me/roles")]
|
||||
public async Task<IActionResult> GetMyRoles()
|
||||
{
|
||||
var sub = User.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? string.Empty;
|
||||
return Ok(await mediator.Send(new GetUserRolesCommand(sub)));
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
[HttpPut("me/phone")]
|
||||
public async Task<IActionResult> UpdateMyPhone([FromBody] SetUserPhoneCommand command)
|
||||
{
|
||||
return Ok(await mediator.Send(command));
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
[HttpPut("me/fullname")]
|
||||
public async Task<IActionResult> UpdateMyFullName([FromBody] SetUserFullNameCommand command)
|
||||
{
|
||||
return Ok(await mediator.Send(command));
|
||||
}
|
||||
|
||||
[Authorize(Roles = "Admin")]
|
||||
[HttpPut("{userId}/roles/{roleId:guid}")]
|
||||
public async Task<IActionResult> AddUserRole(string userId, Guid roleId)
|
||||
{
|
||||
var command = new SetUserRoleCommand(userId, roleId);
|
||||
return Ok(await mediator.Send(command));
|
||||
}
|
||||
|
||||
[Authorize(Roles = "Admin")]
|
||||
[HttpDelete("{userId}/roles/{roleId:guid}")]
|
||||
public async Task<IActionResult> RemoveUserRole(string userId, Guid roleId)
|
||||
{
|
||||
var command = new DeleteUserRoleCommand(userId, roleId);
|
||||
return Ok(await mediator.Send(command));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user