Merge pull request #1 from bytegrip/dev
Dev
This commit was merged in pull request #1.
This commit is contained in:
@@ -0,0 +1,7 @@
|
|||||||
|
namespace Imprink.Application.Exceptions;
|
||||||
|
|
||||||
|
public abstract class BaseApplicationException : Exception
|
||||||
|
{
|
||||||
|
protected BaseApplicationException(string message) : base(message) { }
|
||||||
|
protected BaseApplicationException(string message, Exception innerException) : base(message, innerException) { }
|
||||||
|
}
|
||||||
@@ -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) { }
|
||||||
|
}
|
||||||
7
src/Imprink.Application/Exceptions/NotFoundException.cs
Normal file
7
src/Imprink.Application/Exceptions/NotFoundException.cs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
namespace Imprink.Application.Exceptions;
|
||||||
|
|
||||||
|
public class NotFoundException : BaseApplicationException
|
||||||
|
{
|
||||||
|
public NotFoundException(string message) : base(message) { }
|
||||||
|
public NotFoundException(string message, Exception innerException) : base(message, innerException) { }
|
||||||
|
}
|
||||||
6
src/Imprink.Application/Service/ICurrentUserService.cs
Normal file
6
src/Imprink.Application/Service/ICurrentUserService.cs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
namespace Imprink.Application.Service;
|
||||||
|
|
||||||
|
public interface ICurrentUserService
|
||||||
|
{
|
||||||
|
string? GetCurrentUserId();
|
||||||
|
}
|
||||||
44
src/Imprink.Application/Users/DeleteUserRoleHandler.cs
Normal file
44
src/Imprink.Application/Users/DeleteUserRoleHandler.cs
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
using Imprink.Application.Exceptions;
|
||||||
|
using Imprink.Application.Users.Dtos;
|
||||||
|
using Imprink.Domain.Entities.Users;
|
||||||
|
using MediatR;
|
||||||
|
|
||||||
|
namespace Imprink.Application.Users;
|
||||||
|
|
||||||
|
public record DeleteUserRoleCommand(string Sub, Guid RoleId) : IRequest<UserRoleDto?>;
|
||||||
|
|
||||||
|
public class DeleteUserRoleHandler(IUnitOfWork uw) : IRequestHandler<DeleteUserRoleCommand, UserRoleDto?>
|
||||||
|
{
|
||||||
|
public async Task<UserRoleDto?> Handle(DeleteUserRoleCommand request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
await uw.BeginTransactionAsync(cancellationToken);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!await uw.UserRepository.UserExistsAsync(request.Sub, cancellationToken))
|
||||||
|
throw new NotFoundException("User with ID: " + request.Sub + " does not exist.");
|
||||||
|
|
||||||
|
var existingUserRole = await uw.UserRoleRepository.GetUserRoleAsync(request.Sub, request.RoleId, cancellationToken);
|
||||||
|
|
||||||
|
if (existingUserRole == null)
|
||||||
|
throw new NotFoundException($"User role not found for user {request.Sub} and role {request.RoleId}");
|
||||||
|
|
||||||
|
var removedRole = await uw.UserRoleRepository.RemoveUserRoleAsync(existingUserRole, cancellationToken);
|
||||||
|
|
||||||
|
await uw.SaveAsync(cancellationToken);
|
||||||
|
await uw.CommitTransactionAsync(cancellationToken);
|
||||||
|
|
||||||
|
return new UserRoleDto
|
||||||
|
{
|
||||||
|
UserId = removedRole.UserId,
|
||||||
|
RoleId = removedRole.RoleId
|
||||||
|
};
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
await uw.RollbackTransactionAsync(cancellationToken);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
24
src/Imprink.Application/Users/Dtos/UserDto.cs
Normal file
24
src/Imprink.Application/Users/Dtos/UserDto.cs
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
using Imprink.Domain.Entities.Orders;
|
||||||
|
using Imprink.Domain.Entities.Users;
|
||||||
|
|
||||||
|
namespace Imprink.Application.Users.Dtos;
|
||||||
|
|
||||||
|
public class UserDto
|
||||||
|
{
|
||||||
|
public string Id { get; set; } = null!;
|
||||||
|
public required string Name { get; set; }
|
||||||
|
public required string Nickname { get; set; }
|
||||||
|
public required string Email { get; set; }
|
||||||
|
public bool EmailVerified { get; set; }
|
||||||
|
public string? FirstName { get; set; }
|
||||||
|
public string? LastName { get; set; }
|
||||||
|
public string? PhoneNumber { get; set; }
|
||||||
|
public required bool IsActive { get; set; }
|
||||||
|
|
||||||
|
public virtual ICollection<Address> Addresses { get; set; } = new List<Address>();
|
||||||
|
public virtual ICollection<UserRole> UserRoles { get; set; } = new List<UserRole>();
|
||||||
|
public virtual ICollection<Order> Orders { get; set; } = new List<Order>();
|
||||||
|
|
||||||
|
public Address? DefaultAddress => Addresses.FirstOrDefault(a => a is { IsDefault: true, IsActive: true });
|
||||||
|
public IEnumerable<Role> Roles => UserRoles.Select(ur => ur.Role);
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using Imprink.Application.Exceptions;
|
||||||
using Imprink.Application.Users.Dtos;
|
using Imprink.Application.Users.Dtos;
|
||||||
using MediatR;
|
using MediatR;
|
||||||
|
|
||||||
@@ -9,7 +10,8 @@ public class GetUserRolesHandler(IUnitOfWork uw): IRequestHandler<GetUserRolesCo
|
|||||||
{
|
{
|
||||||
public async Task<IEnumerable<RoleDto>> Handle(GetUserRolesCommand request, CancellationToken cancellationToken)
|
public async Task<IEnumerable<RoleDto>> Handle(GetUserRolesCommand request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (!await uw.UserRepository.UserExistsAsync(request.Sub, cancellationToken)) return [];
|
if (!await uw.UserRepository.UserExistsAsync(request.Sub, cancellationToken))
|
||||||
|
throw new NotFoundException("User with ID: " + request.Sub + " does not exist.");
|
||||||
|
|
||||||
var roles = await uw.UserRoleRepository.GetUserRolesAsync(request.Sub, cancellationToken);
|
var roles = await uw.UserRoleRepository.GetUserRolesAsync(request.Sub, cancellationToken);
|
||||||
|
|
||||||
|
|||||||
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
48
src/Imprink.Application/Users/SetUserPhoneHandler.cs
Normal file
48
src/Imprink.Application/Users/SetUserPhoneHandler.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 SetUserPhoneCommand(string PhoneNumber) : IRequest<UserDto?>;
|
||||||
|
|
||||||
|
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,3 +1,4 @@
|
|||||||
|
using Imprink.Application.Exceptions;
|
||||||
using Imprink.Application.Users.Dtos;
|
using Imprink.Application.Users.Dtos;
|
||||||
using Imprink.Domain.Entities.Users;
|
using Imprink.Domain.Entities.Users;
|
||||||
using MediatR;
|
using MediatR;
|
||||||
@@ -10,20 +11,34 @@ public class SetUserRoleHandler(IUnitOfWork uw) : IRequestHandler<SetUserRoleCom
|
|||||||
{
|
{
|
||||||
public async Task<UserRoleDto?> Handle(SetUserRoleCommand request, CancellationToken cancellationToken)
|
public async Task<UserRoleDto?> Handle(SetUserRoleCommand request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (!await uw.UserRepository.UserExistsAsync(request.Sub, cancellationToken)) return null;
|
await uw.BeginTransactionAsync(cancellationToken);
|
||||||
|
|
||||||
var userRole = new UserRole
|
try
|
||||||
{
|
{
|
||||||
UserId = request.Sub,
|
if (!await uw.UserRepository.UserExistsAsync(request.Sub, cancellationToken))
|
||||||
RoleId = request.RoleId
|
throw new NotFoundException("User with ID: " + request.Sub + " does not exist.");
|
||||||
};
|
|
||||||
|
|
||||||
var addedRole = await uw.UserRoleRepository.AddUserRoleAsync(userRole, cancellationToken);
|
var userRole = new UserRole
|
||||||
|
{
|
||||||
|
UserId = request.Sub,
|
||||||
|
RoleId = request.RoleId
|
||||||
|
};
|
||||||
|
|
||||||
return new UserRoleDto
|
var addedRole = await uw.UserRoleRepository.AddUserRoleAsync(userRole, cancellationToken);
|
||||||
|
|
||||||
|
await uw.SaveAsync(cancellationToken);
|
||||||
|
await uw.CommitTransactionAsync(cancellationToken);
|
||||||
|
|
||||||
|
return new UserRoleDto
|
||||||
|
{
|
||||||
|
UserId = addedRole.UserId,
|
||||||
|
RoleId = addedRole.RoleId
|
||||||
|
};
|
||||||
|
}
|
||||||
|
catch
|
||||||
{
|
{
|
||||||
UserId = addedRole.UserId,
|
await uw.RollbackTransactionAsync(cancellationToken);
|
||||||
RoleId = addedRole.RoleId
|
throw;
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,26 +1,38 @@
|
|||||||
|
using Imprink.Application.Users.Dtos;
|
||||||
using Imprink.Domain.Models;
|
using Imprink.Domain.Models;
|
||||||
using MediatR;
|
using MediatR;
|
||||||
|
|
||||||
namespace Imprink.Application.Users;
|
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);
|
await uw.BeginTransactionAsync(cancellationToken);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!await uw.UserRepository.UpdateOrCreateUserAsync(request.User, cancellationToken))
|
var user = await uw.UserRepository.UpdateOrCreateUserAsync(request.User, cancellationToken);
|
||||||
{
|
|
||||||
await uw.RollbackTransactionAsync(cancellationToken);
|
if (user == null) throw new Exception("User exists but could not be updated");
|
||||||
}
|
|
||||||
|
|
||||||
await uw.SaveAsync(cancellationToken);
|
await uw.SaveAsync(cancellationToken);
|
||||||
await uw.CommitTransactionAsync(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
|
catch
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,9 +8,10 @@ public class User
|
|||||||
public required string Name { get; set; }
|
public required string Name { get; set; }
|
||||||
public required string Nickname { get; set; }
|
public required string Nickname { get; set; }
|
||||||
public required string Email { get; set; }
|
public required string Email { get; set; }
|
||||||
|
|
||||||
public bool EmailVerified { 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 string? PhoneNumber { get; set; }
|
||||||
public required bool IsActive { get; set; }
|
public required bool IsActive { get; set; }
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,18 @@
|
|||||||
using Imprink.Domain.Entities.Users;
|
using Imprink.Domain.Entities.Users;
|
||||||
using Imprink.Domain.Models;
|
using Imprink.Domain.Models;
|
||||||
|
|
||||||
namespace Imprink.Domain.Repositories;
|
namespace Imprink.Domain.Repositories.Users;
|
||||||
|
|
||||||
public interface IUserRepository
|
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?> GetUserByIdAsync(string userId, CancellationToken cancellationToken = default);
|
||||||
Task<User?> GetUserByEmailAsync(string email, CancellationToken cancellationToken = default);
|
Task<User?> GetUserByEmailAsync(string email, CancellationToken cancellationToken = default);
|
||||||
Task<IEnumerable<User>> GetAllUsersAsync(CancellationToken cancellationToken = default);
|
Task<IEnumerable<User>> GetAllUsersAsync(CancellationToken cancellationToken = default);
|
||||||
Task<IEnumerable<User>> GetActiveUsersAsync(CancellationToken cancellationToken = default);
|
Task<IEnumerable<User>> GetActiveUsersAsync(CancellationToken cancellationToken = default);
|
||||||
Task<bool> UserExistsAsync(string userId, CancellationToken cancellationToken = default);
|
Task<bool> UserExistsAsync(string userId, CancellationToken cancellationToken = default);
|
||||||
Task<IEnumerable<User>> GetUsersByRoleAsync(Guid roleId, 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);
|
Task<User?> GetUserWithAllRelatedDataAsync(string userId, CancellationToken cancellationToken = default);
|
||||||
}
|
}
|
||||||
@@ -28,7 +28,10 @@ public class UserConfiguration : IEntityTypeConfiguration<User>
|
|||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasMaxLength(100);
|
.HasMaxLength(100);
|
||||||
|
|
||||||
builder.Property(u => u.FullName)
|
builder.Property(u => u.FirstName)
|
||||||
|
.HasMaxLength(100);
|
||||||
|
|
||||||
|
builder.Property(u => u.LastName)
|
||||||
.HasMaxLength(100);
|
.HasMaxLength(100);
|
||||||
|
|
||||||
builder.Property(u => u.PhoneNumber)
|
builder.Property(u => u.PhoneNumber)
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="AutoMapper" Version="14.0.0" />
|
<PackageReference Include="AutoMapper" Version="14.0.0" />
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Http" Version="2.3.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="9.0.5" />
|
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="9.0.5" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.5" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.5" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.5">
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.5">
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
|||||||
namespace Imprink.Infrastructure.Migrations
|
namespace Imprink.Infrastructure.Migrations
|
||||||
{
|
{
|
||||||
[DbContext(typeof(ApplicationDbContext))]
|
[DbContext(typeof(ApplicationDbContext))]
|
||||||
[Migration("20250609202250_InitialSetup")]
|
[Migration("20250610225629_InitialSetup")]
|
||||||
partial class InitialSetup
|
partial class InitialSetup
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@@ -743,11 +743,6 @@ namespace Imprink.Infrastructure.Migrations
|
|||||||
b.ToTable("Roles");
|
b.ToTable("Roles");
|
||||||
|
|
||||||
b.HasData(
|
b.HasData(
|
||||||
new
|
|
||||||
{
|
|
||||||
Id = new Guid("11111111-1111-1111-1111-111111111111"),
|
|
||||||
RoleName = "User"
|
|
||||||
},
|
|
||||||
new
|
new
|
||||||
{
|
{
|
||||||
Id = new Guid("22222222-2222-2222-2222-222222222222"),
|
Id = new Guid("22222222-2222-2222-2222-222222222222"),
|
||||||
@@ -775,7 +770,7 @@ namespace Imprink.Infrastructure.Migrations
|
|||||||
.HasMaxLength(100)
|
.HasMaxLength(100)
|
||||||
.HasColumnType("bit");
|
.HasColumnType("bit");
|
||||||
|
|
||||||
b.Property<string>("FullName")
|
b.Property<string>("FirstName")
|
||||||
.HasMaxLength(100)
|
.HasMaxLength(100)
|
||||||
.HasColumnType("nvarchar(100)");
|
.HasColumnType("nvarchar(100)");
|
||||||
|
|
||||||
@@ -784,6 +779,10 @@ namespace Imprink.Infrastructure.Migrations
|
|||||||
.HasColumnType("bit")
|
.HasColumnType("bit")
|
||||||
.HasDefaultValue(true);
|
.HasDefaultValue(true);
|
||||||
|
|
||||||
|
b.Property<string>("LastName")
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("nvarchar(100)");
|
||||||
|
|
||||||
b.Property<string>("Name")
|
b.Property<string>("Name")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasMaxLength(100)
|
.HasMaxLength(100)
|
||||||
@@ -85,7 +85,8 @@ namespace Imprink.Infrastructure.Migrations
|
|||||||
Nickname = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false),
|
Nickname = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false),
|
||||||
Email = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: false),
|
Email = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: false),
|
||||||
EmailVerified = table.Column<bool>(type: "bit", maxLength: 100, 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),
|
PhoneNumber = table.Column<string>(type: "nvarchar(20)", maxLength: 20, nullable: true),
|
||||||
IsActive = table.Column<bool>(type: "bit", nullable: false, defaultValue: true)
|
IsActive = table.Column<bool>(type: "bit", nullable: false, defaultValue: true)
|
||||||
},
|
},
|
||||||
@@ -339,7 +340,6 @@ namespace Imprink.Infrastructure.Migrations
|
|||||||
columns: new[] { "Id", "RoleName" },
|
columns: new[] { "Id", "RoleName" },
|
||||||
values: new object[,]
|
values: new object[,]
|
||||||
{
|
{
|
||||||
{ new Guid("11111111-1111-1111-1111-111111111111"), "User" },
|
|
||||||
{ new Guid("22222222-2222-2222-2222-222222222222"), "Merchant" },
|
{ new Guid("22222222-2222-2222-2222-222222222222"), "Merchant" },
|
||||||
{ new Guid("33333333-3333-3333-3333-333333333333"), "Admin" }
|
{ new Guid("33333333-3333-3333-3333-333333333333"), "Admin" }
|
||||||
});
|
});
|
||||||
@@ -740,11 +740,6 @@ namespace Imprink.Infrastructure.Migrations
|
|||||||
b.ToTable("Roles");
|
b.ToTable("Roles");
|
||||||
|
|
||||||
b.HasData(
|
b.HasData(
|
||||||
new
|
|
||||||
{
|
|
||||||
Id = new Guid("11111111-1111-1111-1111-111111111111"),
|
|
||||||
RoleName = "User"
|
|
||||||
},
|
|
||||||
new
|
new
|
||||||
{
|
{
|
||||||
Id = new Guid("22222222-2222-2222-2222-222222222222"),
|
Id = new Guid("22222222-2222-2222-2222-222222222222"),
|
||||||
@@ -772,7 +767,7 @@ namespace Imprink.Infrastructure.Migrations
|
|||||||
.HasMaxLength(100)
|
.HasMaxLength(100)
|
||||||
.HasColumnType("bit");
|
.HasColumnType("bit");
|
||||||
|
|
||||||
b.Property<string>("FullName")
|
b.Property<string>("FirstName")
|
||||||
.HasMaxLength(100)
|
.HasMaxLength(100)
|
||||||
.HasColumnType("nvarchar(100)");
|
.HasColumnType("nvarchar(100)");
|
||||||
|
|
||||||
@@ -781,6 +776,10 @@ namespace Imprink.Infrastructure.Migrations
|
|||||||
.HasColumnType("bit")
|
.HasColumnType("bit")
|
||||||
.HasDefaultValue(true);
|
.HasDefaultValue(true);
|
||||||
|
|
||||||
|
b.Property<string>("LastName")
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("nvarchar(100)");
|
||||||
|
|
||||||
b.Property<string>("Name")
|
b.Property<string>("Name")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasMaxLength(100)
|
.HasMaxLength(100)
|
||||||
|
|||||||
@@ -62,23 +62,21 @@ public class CategoryRepository(ApplicationDbContext context) : ICategoryReposit
|
|||||||
.ToListAsync(cancellationToken);
|
.ToListAsync(cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Category> AddAsync(Category category, CancellationToken cancellationToken = default)
|
public Task<Category> AddAsync(Category category, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
category.Id = Guid.NewGuid();
|
category.Id = Guid.NewGuid();
|
||||||
category.CreatedAt = DateTime.UtcNow;
|
category.CreatedAt = DateTime.UtcNow;
|
||||||
category.ModifiedAt = DateTime.UtcNow;
|
category.ModifiedAt = DateTime.UtcNow;
|
||||||
|
|
||||||
context.Categories.Add(category);
|
context.Categories.Add(category);
|
||||||
await context.SaveChangesAsync(cancellationToken);
|
return Task.FromResult(category);
|
||||||
return category;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Category> UpdateAsync(Category category, CancellationToken cancellationToken = default)
|
public Task<Category> UpdateAsync(Category category, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
category.ModifiedAt = DateTime.UtcNow;
|
category.ModifiedAt = DateTime.UtcNow;
|
||||||
context.Categories.Update(category);
|
context.Categories.Update(category);
|
||||||
await context.SaveChangesAsync(cancellationToken);
|
return Task.FromResult(category);
|
||||||
return category;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task DeleteAsync(Guid id, CancellationToken cancellationToken = default)
|
public async Task DeleteAsync(Guid id, CancellationToken cancellationToken = default)
|
||||||
@@ -87,7 +85,6 @@ public class CategoryRepository(ApplicationDbContext context) : ICategoryReposit
|
|||||||
if (category != null)
|
if (category != null)
|
||||||
{
|
{
|
||||||
context.Categories.Remove(category);
|
context.Categories.Remove(category);
|
||||||
await context.SaveChangesAsync(cancellationToken);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using Imprink.Domain.Entities.Product;
|
using Imprink.Domain.Entities.Product;
|
||||||
using Imprink.Domain.Models;
|
using Imprink.Domain.Models;
|
||||||
using Imprink.Domain.Repositories;
|
|
||||||
using Imprink.Domain.Repositories.Products;
|
using Imprink.Domain.Repositories.Products;
|
||||||
using Imprink.Infrastructure.Database;
|
using Imprink.Infrastructure.Database;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
@@ -143,12 +142,11 @@ public class ProductRepository(ApplicationDbContext context) : IProductRepositor
|
|||||||
return product;
|
return product;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Product> UpdateAsync(Product product, CancellationToken cancellationToken = default)
|
public Task<Product> UpdateAsync(Product product, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
product.ModifiedAt = DateTime.UtcNow;
|
product.ModifiedAt = DateTime.UtcNow;
|
||||||
context.Products.Update(product);
|
context.Products.Update(product);
|
||||||
await context.SaveChangesAsync(cancellationToken);
|
return Task.FromResult(product);
|
||||||
return product;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task DeleteAsync(Guid id, CancellationToken cancellationToken = default)
|
public async Task DeleteAsync(Guid id, CancellationToken cancellationToken = default)
|
||||||
@@ -157,7 +155,6 @@ public class ProductRepository(ApplicationDbContext context) : IProductRepositor
|
|||||||
if (product != null)
|
if (product != null)
|
||||||
{
|
{
|
||||||
context.Products.Remove(product);
|
context.Products.Remove(product);
|
||||||
await context.SaveChangesAsync(cancellationToken);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -58,23 +58,21 @@ public class ProductVariantRepository(ApplicationDbContext context) : IProductVa
|
|||||||
.ToListAsync(cancellationToken);
|
.ToListAsync(cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<ProductVariant> AddAsync(ProductVariant productVariant, CancellationToken cancellationToken = default)
|
public Task<ProductVariant> AddAsync(ProductVariant productVariant, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
productVariant.Id = Guid.NewGuid();
|
productVariant.Id = Guid.NewGuid();
|
||||||
productVariant.CreatedAt = DateTime.UtcNow;
|
productVariant.CreatedAt = DateTime.UtcNow;
|
||||||
productVariant.ModifiedAt = DateTime.UtcNow;
|
productVariant.ModifiedAt = DateTime.UtcNow;
|
||||||
|
|
||||||
context.ProductVariants.Add(productVariant);
|
context.ProductVariants.Add(productVariant);
|
||||||
await context.SaveChangesAsync(cancellationToken);
|
return Task.FromResult(productVariant);
|
||||||
return productVariant;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<ProductVariant> UpdateAsync(ProductVariant productVariant, CancellationToken cancellationToken = default)
|
public Task<ProductVariant> UpdateAsync(ProductVariant productVariant, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
productVariant.ModifiedAt = DateTime.UtcNow;
|
productVariant.ModifiedAt = DateTime.UtcNow;
|
||||||
context.ProductVariants.Update(productVariant);
|
context.ProductVariants.Update(productVariant);
|
||||||
await context.SaveChangesAsync(cancellationToken);
|
return Task.FromResult(productVariant);
|
||||||
return productVariant;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task DeleteAsync(Guid id, CancellationToken cancellationToken = default)
|
public async Task DeleteAsync(Guid id, CancellationToken cancellationToken = default)
|
||||||
@@ -83,7 +81,6 @@ public class ProductVariantRepository(ApplicationDbContext context) : IProductVa
|
|||||||
if (productVariant != null)
|
if (productVariant != null)
|
||||||
{
|
{
|
||||||
context.ProductVariants.Remove(productVariant);
|
context.ProductVariants.Remove(productVariant);
|
||||||
await context.SaveChangesAsync(cancellationToken);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,7 +109,6 @@ public class ProductVariantRepository(ApplicationDbContext context) : IProductVa
|
|||||||
{
|
{
|
||||||
productVariant.StockQuantity = quantity;
|
productVariant.StockQuantity = quantity;
|
||||||
productVariant.ModifiedAt = DateTime.UtcNow;
|
productVariant.ModifiedAt = DateTime.UtcNow;
|
||||||
await context.SaveChangesAsync(cancellationToken);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
using Imprink.Domain.Entities.Users;
|
using Imprink.Domain.Entities.Users;
|
||||||
using Imprink.Domain.Models;
|
using Imprink.Domain.Models;
|
||||||
using Imprink.Domain.Repositories;
|
using Imprink.Domain.Repositories;
|
||||||
|
using Imprink.Domain.Repositories.Users;
|
||||||
using Imprink.Infrastructure.Database;
|
using Imprink.Infrastructure.Database;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
@@ -8,7 +9,7 @@ namespace Imprink.Infrastructure.Repositories.Users;
|
|||||||
|
|
||||||
public class UserRepository(ApplicationDbContext context) : IUserRepository
|
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
|
var userToUpdate = await context.Users
|
||||||
.Where(u => u.Id.Equals(user.Sub))
|
.Where(u => u.Id.Equals(user.Sub))
|
||||||
@@ -27,16 +28,15 @@ public class UserRepository(ApplicationDbContext context) : IUserRepository
|
|||||||
};
|
};
|
||||||
|
|
||||||
context.Users.Add(newUser);
|
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)
|
public async Task<User?> GetUserByIdAsync(string userId, CancellationToken cancellationToken = default)
|
||||||
@@ -92,4 +92,27 @@ public class UserRepository(ApplicationDbContext context) : IUserRepository
|
|||||||
.Include(u => u.Orders)
|
.Include(u => u.Orders)
|
||||||
.FirstOrDefaultAsync(u => u.Id == userId, cancellationToken);
|
.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,5 +1,4 @@
|
|||||||
using Imprink.Domain.Entities.Users;
|
using Imprink.Domain.Entities.Users;
|
||||||
using Imprink.Domain.Repositories;
|
|
||||||
using Imprink.Domain.Repositories.Users;
|
using Imprink.Domain.Repositories.Users;
|
||||||
using Imprink.Infrastructure.Database;
|
using Imprink.Infrastructure.Database;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
@@ -33,17 +32,15 @@ public class UserRoleRepository(ApplicationDbContext context) : IUserRoleReposit
|
|||||||
.FirstOrDefaultAsync(ur => ur.UserId == userId && ur.RoleId == roleId, cancellationToken);
|
.FirstOrDefaultAsync(ur => ur.UserId == userId && ur.RoleId == roleId, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<UserRole> AddUserRoleAsync(UserRole userRole, CancellationToken cancellationToken = default)
|
public Task<UserRole> AddUserRoleAsync(UserRole userRole, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
var ur = context.UserRole.Add(userRole);
|
var ur = context.UserRole.Add(userRole);
|
||||||
await context.SaveChangesAsync(cancellationToken);
|
return Task.FromResult(ur.Entity);
|
||||||
return ur.Entity;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<UserRole> RemoveUserRoleAsync(UserRole userRole, CancellationToken cancellationToken = default)
|
public Task<UserRole> RemoveUserRoleAsync(UserRole userRole, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
var ur = context.UserRole.Remove(userRole);
|
var ur = context.UserRole.Remove(userRole);
|
||||||
await context.SaveChangesAsync(cancellationToken);
|
return Task.FromResult(ur.Entity);
|
||||||
return ur.Entity;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
14
src/Imprink.Infrastructure/Services/CurrentUserService.cs
Normal file
14
src/Imprink.Infrastructure/Services/CurrentUserService.cs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
using System.Security.Claims;
|
||||||
|
using Imprink.Application.Service;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
|
||||||
|
namespace Imprink.Infrastructure.Services;
|
||||||
|
|
||||||
|
public class CurrentUserService(IHttpContextAccessor httpContextAccessor) : ICurrentUserService
|
||||||
|
{
|
||||||
|
public string? GetCurrentUserId()
|
||||||
|
{
|
||||||
|
return httpContextAccessor.HttpContext?.User?.FindFirst(ClaimTypes.NameIdentifier)?.Value
|
||||||
|
?? httpContextAccessor.HttpContext?.User?.FindFirst("sub")?.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,7 +12,6 @@ public class CategoriesController(IMediator mediator) : ControllerBase
|
|||||||
[HttpGet]
|
[HttpGet]
|
||||||
public async Task<ActionResult<IEnumerable<CategoryDto>>> GetCategories([FromQuery] GetCategoriesQuery query)
|
public async Task<ActionResult<IEnumerable<CategoryDto>>> GetCategories([FromQuery] GetCategoriesQuery query)
|
||||||
{
|
{
|
||||||
var result = await mediator.Send(query);
|
return Ok(await mediator.Send(query));
|
||||||
return Ok(result);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -13,7 +13,6 @@ public class ProductVariantsController(IMediator mediator) : ControllerBase
|
|||||||
public async Task<ActionResult<IEnumerable<ProductVariantDto>>> GetProductVariants(
|
public async Task<ActionResult<IEnumerable<ProductVariantDto>>> GetProductVariants(
|
||||||
[FromQuery] GetProductVariantsQuery query)
|
[FromQuery] GetProductVariantsQuery query)
|
||||||
{
|
{
|
||||||
var result = await mediator.Send(query);
|
return Ok(await mediator.Send(query));
|
||||||
return Ok(result);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -17,7 +17,7 @@ public class ProductsController(IMediator mediator) : ControllerBase
|
|||||||
public async Task<ActionResult<PagedResultDto<ProductDto>>> GetProducts(
|
public async Task<ActionResult<PagedResultDto<ProductDto>>> GetProducts(
|
||||||
[FromQuery] ProductFilterParameters filterParameters)
|
[FromQuery] ProductFilterParameters filterParameters)
|
||||||
{
|
{
|
||||||
var result = await mediator.Send(new GetProductsQuery { FilterParameters = filterParameters });
|
var result = await mediator.Send(new GetProductsQuery { FilterParameters = filterParameters});
|
||||||
return Ok(result);
|
return Ok(result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,33 +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,36 +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 claims = User.Claims as Claim[] ?? User.Claims.ToArray();
|
|
||||||
var sub = claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier)?.Value ?? string.Empty;
|
|
||||||
|
|
||||||
var myRoles = await mediator.Send(new GetUserRolesCommand(sub));
|
|
||||||
|
|
||||||
return Ok(myRoles);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Authorize(Roles = "Admin")]
|
|
||||||
[HttpPost("set")]
|
|
||||||
public async Task<IActionResult> SetUserRole(SetUserRoleCommand command)
|
|
||||||
{
|
|
||||||
var userRole = await mediator.Send(command);
|
|
||||||
|
|
||||||
if (userRole == null)
|
|
||||||
return BadRequest();
|
|
||||||
|
|
||||||
return Ok(userRole);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
75
src/Imprink.WebApi/Middleware/ExceptionHandlingMiddleware.cs
Normal file
75
src/Imprink.WebApi/Middleware/ExceptionHandlingMiddleware.cs
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
using System.Net;
|
||||||
|
using System.Text.Json;
|
||||||
|
using Imprink.Application.Exceptions;
|
||||||
|
|
||||||
|
namespace Imprink.WebApi.Middleware;
|
||||||
|
|
||||||
|
public class ExceptionHandlingMiddleware(
|
||||||
|
RequestDelegate next,
|
||||||
|
ILogger<ExceptionHandlingMiddleware> logger)
|
||||||
|
{
|
||||||
|
public async Task InvokeAsync(HttpContext context)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await next(context);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
var (_, _, shouldLog) = GetStatusCodeAndMessage(ex);
|
||||||
|
|
||||||
|
if (shouldLog)
|
||||||
|
{
|
||||||
|
logger.LogError(ex, "An unhandled exception occurred: {Message}", ex.Message);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
logger.LogInformation("Handled: {Message}", ex.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
await HandleExceptionAsync(context, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task HandleExceptionAsync(HttpContext context, Exception exception)
|
||||||
|
{
|
||||||
|
context.Response.ContentType = "application/json";
|
||||||
|
|
||||||
|
var (statusCode, message, _) = GetStatusCodeAndMessage(exception);
|
||||||
|
|
||||||
|
context.Response.StatusCode = (int)statusCode;
|
||||||
|
|
||||||
|
var response = new
|
||||||
|
{
|
||||||
|
error = new
|
||||||
|
{
|
||||||
|
message,
|
||||||
|
timestamp = DateTime.UtcNow,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var jsonResponse = JsonSerializer.Serialize(response, new JsonSerializerOptions
|
||||||
|
{
|
||||||
|
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
||||||
|
});
|
||||||
|
|
||||||
|
await context.Response.WriteAsync(jsonResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static (HttpStatusCode statusCode, string message, bool shouldLog) GetStatusCodeAndMessage(Exception exception)
|
||||||
|
{
|
||||||
|
return exception switch
|
||||||
|
{
|
||||||
|
NotFoundException => (HttpStatusCode.NotFound, exception.Message, false),
|
||||||
|
_ => (HttpStatusCode.InternalServerError, "An internal server error occurred", true)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class GlobalExceptionHandlingMiddlewareExtensions
|
||||||
|
{
|
||||||
|
public static void UseGlobalExceptionHandling(this IApplicationBuilder builder)
|
||||||
|
{
|
||||||
|
builder.UseMiddleware<ExceptionHandlingMiddleware>();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ using System.Security.Claims;
|
|||||||
using FluentValidation;
|
using FluentValidation;
|
||||||
using Imprink.Application;
|
using Imprink.Application;
|
||||||
using Imprink.Application.Products.Create;
|
using Imprink.Application.Products.Create;
|
||||||
|
using Imprink.Application.Service;
|
||||||
using Imprink.Application.Validation.Models;
|
using Imprink.Application.Validation.Models;
|
||||||
using Imprink.Domain.Repositories;
|
using Imprink.Domain.Repositories;
|
||||||
using Imprink.Domain.Repositories.Products;
|
using Imprink.Domain.Repositories.Products;
|
||||||
@@ -10,6 +11,7 @@ using Imprink.Infrastructure;
|
|||||||
using Imprink.Infrastructure.Database;
|
using Imprink.Infrastructure.Database;
|
||||||
using Imprink.Infrastructure.Repositories.Products;
|
using Imprink.Infrastructure.Repositories.Products;
|
||||||
using Imprink.Infrastructure.Repositories.Users;
|
using Imprink.Infrastructure.Repositories.Users;
|
||||||
|
using Imprink.Infrastructure.Services;
|
||||||
using Imprink.WebApi.Filters;
|
using Imprink.WebApi.Filters;
|
||||||
using Imprink.WebApi.Middleware;
|
using Imprink.WebApi.Middleware;
|
||||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||||
@@ -31,9 +33,12 @@ public static class Startup
|
|||||||
services.AddScoped<IUserRepository, UserRepository>();
|
services.AddScoped<IUserRepository, UserRepository>();
|
||||||
services.AddScoped<IUserRoleRepository, UserRoleRepository>();
|
services.AddScoped<IUserRoleRepository, UserRoleRepository>();
|
||||||
services.AddScoped<IUnitOfWork, UnitOfWork>();
|
services.AddScoped<IUnitOfWork, UnitOfWork>();
|
||||||
|
services.AddScoped<ICurrentUserService, CurrentUserService>();
|
||||||
|
|
||||||
services.AddScoped<Seeder>();
|
services.AddScoped<Seeder>();
|
||||||
|
|
||||||
|
services.AddHttpContextAccessor();
|
||||||
|
|
||||||
services.AddDbContext<ApplicationDbContext>(options =>
|
services.AddDbContext<ApplicationDbContext>(options =>
|
||||||
options.UseSqlServer(
|
options.UseSqlServer(
|
||||||
builder.Configuration.GetConnectionString("DefaultConnection"),
|
builder.Configuration.GetConnectionString("DefaultConnection"),
|
||||||
@@ -140,13 +145,13 @@ public static class Startup
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
app.UseGlobalExceptionHandling();
|
||||||
app.UseRequestTiming();
|
app.UseRequestTiming();
|
||||||
|
|
||||||
if (env.IsDevelopment())
|
if (env.IsDevelopment())
|
||||||
{
|
{
|
||||||
app.UseSwagger();
|
app.UseSwagger();
|
||||||
app.UseSwaggerUI();
|
app.UseSwaggerUI();
|
||||||
app.UseDeveloperExceptionPage();
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user