Jostka
This commit is contained in:
@@ -7,6 +7,7 @@ public interface IUnitOfWork
|
||||
public IProductRepository ProductRepository { get; }
|
||||
public ICategoryRepository CategoryRepository { get; }
|
||||
public IProductVariantRepository ProductVariantRepository { get; }
|
||||
public IUserRepository UserRepository { get; }
|
||||
|
||||
Task SaveAsync(CancellationToken cancellationToken = default);
|
||||
Task BeginTransactionAsync(CancellationToken cancellationToken = default);
|
||||
|
||||
31
src/Imprink.Application/Users/SyncUserHandler.cs
Normal file
31
src/Imprink.Application/Users/SyncUserHandler.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using Imprink.Domain.Common.Models;
|
||||
using MediatR;
|
||||
|
||||
namespace Imprink.Application.Users;
|
||||
|
||||
public record SyncUserCommand(Auth0User User) : IRequest<bool>;
|
||||
|
||||
public class SyncUserHandler(IUnitOfWork uw): IRequestHandler<SyncUserCommand, bool>
|
||||
{
|
||||
public async Task<bool> Handle(SyncUserCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
await uw.BeginTransactionAsync(cancellationToken);
|
||||
|
||||
try
|
||||
{
|
||||
if (!await uw.UserRepository.UpdateOrCreateUserAsync(request.User, cancellationToken))
|
||||
{
|
||||
await uw.RollbackTransactionAsync(cancellationToken);
|
||||
}
|
||||
|
||||
await uw.SaveAsync(cancellationToken);
|
||||
await uw.CommitTransactionAsync(cancellationToken);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
await uw.RollbackTransactionAsync(cancellationToken);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Imprink.Domain.Common.Models;
|
||||
|
||||
public class Auth0User
|
||||
|
||||
@@ -5,19 +5,19 @@ namespace Imprink.Domain.Entities.Users;
|
||||
public class User
|
||||
{
|
||||
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 required string FirstName { get; set; }
|
||||
public required string LastName { get; set; }
|
||||
|
||||
public bool EmailVerified { get; set; }
|
||||
public string? FullName { get; set; }
|
||||
public string? PhoneNumber { get; set; }
|
||||
public DateTime? DateOfBirth { get; set; }
|
||||
public required bool IsActive { get; set; }
|
||||
public DateTime? LastLoginAt { 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 string FullName => $"{FirstName} {LastName}";
|
||||
public Address? DefaultAddress => Addresses.FirstOrDefault(a => a.IsDefault && a.IsActive);
|
||||
public Address? DefaultAddress => Addresses.FirstOrDefault(a => a is { IsDefault: true, IsActive: true });
|
||||
public IEnumerable<Role> Roles => UserRoles.Select(ur => ur.Role);
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
using Imprink.Domain.Common.Models;
|
||||
using Imprink.Domain.Entities.Users;
|
||||
|
||||
namespace Imprink.Domain.Repositories;
|
||||
|
||||
public interface IUserRepository
|
||||
{
|
||||
Task<bool> 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);
|
||||
@@ -18,19 +20,8 @@ public interface IUserRepository
|
||||
|
||||
Task<bool> ActivateUserAsync(string userId, CancellationToken cancellationToken = default);
|
||||
Task<bool> DeactivateUserAsync(string userId, CancellationToken cancellationToken = default);
|
||||
Task UpdateLastLoginAsync(string userId, DateTime loginTime, CancellationToken cancellationToken = default);
|
||||
|
||||
Task<IEnumerable<User>> SearchUsersAsync(string searchTerm, CancellationToken cancellationToken = default);
|
||||
Task<IEnumerable<User>> GetUsersByRoleAsync(Guid roleId, CancellationToken cancellationToken = default);
|
||||
|
||||
Task<(IEnumerable<User> Users, int TotalCount)> GetUsersPagedAsync(
|
||||
int pageNumber,
|
||||
int pageSize,
|
||||
string? searchTerm = null,
|
||||
bool? isActive = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task<User?> GetUserWithAddressesAsync(string userId, CancellationToken cancellationToken = default);
|
||||
Task<User?> GetUserWithRolesAsync(string userId, CancellationToken cancellationToken = default);
|
||||
Task<User?> GetUserWithAllRelatedDataAsync(string userId, CancellationToken cancellationToken = default);
|
||||
}
|
||||
@@ -15,28 +15,29 @@ public class UserConfiguration : IEntityTypeConfiguration<User>
|
||||
builder.Property(u => u.Email)
|
||||
.IsRequired()
|
||||
.HasMaxLength(256);
|
||||
|
||||
builder.Property(u => u.FirstName)
|
||||
|
||||
builder.Property(u => u.Name)
|
||||
.IsRequired()
|
||||
.HasMaxLength(100);
|
||||
|
||||
builder.Property(u => u.LastName)
|
||||
|
||||
builder.Property(u => u.Nickname)
|
||||
.IsRequired()
|
||||
.HasMaxLength(100);
|
||||
|
||||
builder.Property(u => u.EmailVerified)
|
||||
.IsRequired()
|
||||
.HasMaxLength(100);
|
||||
|
||||
builder.Property(u => u.FullName)
|
||||
.HasMaxLength(100);
|
||||
|
||||
builder.Property(u => u.PhoneNumber)
|
||||
.HasMaxLength(20);
|
||||
|
||||
builder.Property(u => u.DateOfBirth)
|
||||
.HasColumnType("date");
|
||||
|
||||
builder.Property(u => u.IsActive)
|
||||
.IsRequired()
|
||||
.HasDefaultValue(true);
|
||||
|
||||
builder.Property(u => u.LastLoginAt)
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
builder.HasIndex(u => u.Email)
|
||||
.IsUnique()
|
||||
.HasDatabaseName("IX_User_Email");
|
||||
@@ -50,7 +51,6 @@ public class UserConfiguration : IEntityTypeConfiguration<User>
|
||||
.HasPrincipalKey(u => u.Id)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
builder.Ignore(u => u.FullName);
|
||||
builder.Ignore(u => u.DefaultAddress);
|
||||
builder.Ignore(u => u.Roles);
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
namespace Imprink.Infrastructure.Migrations
|
||||
{
|
||||
[DbContext(typeof(ApplicationDbContext))]
|
||||
[Migration("20250607211109_InitialSetup")]
|
||||
[Migration("20250608204903_InitialSetup")]
|
||||
partial class InitialSetup
|
||||
{
|
||||
/// <inheritdoc />
|
||||
@@ -766,16 +766,16 @@ namespace Imprink.Infrastructure.Migrations
|
||||
.HasMaxLength(450)
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.Property<DateTime?>("DateOfBirth")
|
||||
.HasColumnType("date");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.Property<string>("FirstName")
|
||||
.IsRequired()
|
||||
b.Property<bool>("EmailVerified")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<string>("FullName")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
@@ -784,10 +784,12 @@ namespace Imprink.Infrastructure.Migrations
|
||||
.HasColumnType("bit")
|
||||
.HasDefaultValue(true);
|
||||
|
||||
b.Property<DateTime?>("LastLoginAt")
|
||||
.HasColumnType("datetime2");
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<string>("LastName")
|
||||
b.Property<string>("Nickname")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
@@ -81,13 +81,13 @@ namespace Imprink.Infrastructure.Migrations
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "nvarchar(450)", maxLength: 450, nullable: false),
|
||||
Name = 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),
|
||||
FirstName = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false),
|
||||
LastName = table.Column<string>(type: "nvarchar(100)", 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),
|
||||
PhoneNumber = table.Column<string>(type: "nvarchar(20)", maxLength: 20, nullable: true),
|
||||
DateOfBirth = table.Column<DateTime>(type: "date", nullable: true),
|
||||
IsActive = table.Column<bool>(type: "bit", nullable: false, defaultValue: true),
|
||||
LastLoginAt = table.Column<DateTime>(type: "datetime2", nullable: true)
|
||||
IsActive = table.Column<bool>(type: "bit", nullable: false, defaultValue: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
@@ -763,16 +763,16 @@ namespace Imprink.Infrastructure.Migrations
|
||||
.HasMaxLength(450)
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.Property<DateTime?>("DateOfBirth")
|
||||
.HasColumnType("date");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.Property<string>("FirstName")
|
||||
.IsRequired()
|
||||
b.Property<bool>("EmailVerified")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<string>("FullName")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
@@ -781,10 +781,12 @@ namespace Imprink.Infrastructure.Migrations
|
||||
.HasColumnType("bit")
|
||||
.HasDefaultValue(true);
|
||||
|
||||
b.Property<DateTime?>("LastLoginAt")
|
||||
.HasColumnType("datetime2");
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<string>("LastName")
|
||||
b.Property<string>("Nickname")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Imprink.Domain.Common.Models;
|
||||
using Imprink.Domain.Entities.Users;
|
||||
using Imprink.Domain.Repositories;
|
||||
using Imprink.Infrastructure.Database;
|
||||
@@ -7,6 +8,37 @@ namespace Imprink.Infrastructure.Repositories;
|
||||
|
||||
public class UserRepository(ApplicationDbContext context) : IUserRepository
|
||||
{
|
||||
public async Task<bool> UpdateOrCreateUserAsync(Auth0User user, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var userToUpdate = await context.Users
|
||||
.Where(u => u.Id.Equals(user.Sub))
|
||||
.FirstOrDefaultAsync(cancellationToken);
|
||||
|
||||
if (userToUpdate == null)
|
||||
{
|
||||
var newUser = new User
|
||||
{
|
||||
Id = user.Sub,
|
||||
Email = user.Email,
|
||||
EmailVerified = user.EmailVerified,
|
||||
Name = user.Name,
|
||||
Nickname = user.Nickname,
|
||||
IsActive = true
|
||||
};
|
||||
|
||||
context.Users.Add(newUser);
|
||||
}
|
||||
else
|
||||
{
|
||||
userToUpdate.Email = user.Email;
|
||||
userToUpdate.Name = user.Name;
|
||||
userToUpdate.Nickname = user.Nickname;
|
||||
userToUpdate.EmailVerified = user.EmailVerified;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<User?> GetUserByIdAsync(string userId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await context.Users
|
||||
@@ -92,26 +124,6 @@ public class UserRepository(ApplicationDbContext context) : IUserRepository
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task UpdateLastLoginAsync(string userId, DateTime loginTime, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var user = await context.Users.FindAsync(new object[] { userId }, cancellationToken);
|
||||
if (user != null)
|
||||
{
|
||||
user.LastLoginAt = loginTime;
|
||||
await context.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<User>> SearchUsersAsync(string searchTerm, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await context.Users
|
||||
.AsNoTracking()
|
||||
.Where(u => u.Email.Contains(searchTerm) ||
|
||||
u.FirstName.Contains(searchTerm) ||
|
||||
u.LastName.Contains(searchTerm))
|
||||
.ToListAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<User>> GetUsersByRoleAsync(Guid roleId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await context.Users
|
||||
@@ -120,54 +132,6 @@ public class UserRepository(ApplicationDbContext context) : IUserRepository
|
||||
.ToListAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<(IEnumerable<User> Users, int TotalCount)> GetUsersPagedAsync(
|
||||
int pageNumber,
|
||||
int pageSize,
|
||||
string? searchTerm = null,
|
||||
bool? isActive = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var query = context.Users.AsNoTracking();
|
||||
|
||||
if (!string.IsNullOrEmpty(searchTerm))
|
||||
{
|
||||
query = query.Where(u => u.Email.Contains(searchTerm) ||
|
||||
u.FirstName.Contains(searchTerm) ||
|
||||
u.LastName.Contains(searchTerm));
|
||||
}
|
||||
|
||||
if (isActive.HasValue)
|
||||
{
|
||||
query = query.Where(u => u.IsActive == isActive.Value);
|
||||
}
|
||||
|
||||
var totalCount = await query.CountAsync(cancellationToken);
|
||||
|
||||
var users = await query
|
||||
.Skip((pageNumber - 1) * pageSize)
|
||||
.Take(pageSize)
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
return (users, totalCount);
|
||||
}
|
||||
|
||||
public async Task<User?> GetUserWithAddressesAsync(string userId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await context.Users
|
||||
.AsNoTracking()
|
||||
.Include(u => u.Addresses)
|
||||
.FirstOrDefaultAsync(u => u.Id == userId, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<User?> GetUserWithRolesAsync(string userId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await context.Users
|
||||
.AsNoTracking()
|
||||
.Include(u => u.UserRoles)
|
||||
.ThenInclude(ur => ur.Role)
|
||||
.FirstOrDefaultAsync(u => u.Id == userId, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<User?> GetUserWithAllRelatedDataAsync(string userId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await context.Users
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Imprink.Application;
|
||||
using Imprink.Domain.Repositories;
|
||||
using Imprink.Infrastructure.Database;
|
||||
using Microsoft.Identity.Client;
|
||||
|
||||
namespace Imprink.Infrastructure;
|
||||
|
||||
@@ -8,11 +9,13 @@ public class UnitOfWork(
|
||||
ApplicationDbContext context,
|
||||
IProductRepository productRepository,
|
||||
IProductVariantRepository productVariantRepository,
|
||||
ICategoryRepository categoryRepository) : IUnitOfWork
|
||||
ICategoryRepository categoryRepository,
|
||||
IUserRepository userRepository) : IUnitOfWork
|
||||
{
|
||||
public IProductRepository ProductRepository => productRepository;
|
||||
public IProductVariantRepository ProductVariantRepository => productVariantRepository;
|
||||
public ICategoryRepository CategoryRepository => categoryRepository;
|
||||
public IUserRepository UserRepository => userRepository;
|
||||
|
||||
public async Task SaveAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
|
||||
@@ -1,23 +1,33 @@
|
||||
using System.Security.Claims;
|
||||
using Imprink.Application.Users;
|
||||
using Imprink.Domain.Common.Models;
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Imprink.WebApi.Controllers.Users;
|
||||
|
||||
[ApiController]
|
||||
[Route("/api/[controller]")]
|
||||
public class UserController : ControllerBase
|
||||
[Route("/users")]
|
||||
public class UserController(IMediator mediator) : ControllerBase
|
||||
{
|
||||
[Authorize]
|
||||
[HttpPost]
|
||||
public IActionResult SyncUser()
|
||||
[HttpPost("sync")]
|
||||
public async Task<IActionResult> Sync()
|
||||
{
|
||||
var claims = User.Claims;
|
||||
|
||||
foreach (var claim in claims)
|
||||
var enumerable = User.Claims as Claim[] ?? User.Claims.ToArray();
|
||||
|
||||
var auth0User = new Auth0User
|
||||
{
|
||||
Console.WriteLine($"Claim Type: {claim.Type}, Claim Value: {claim.Value}");
|
||||
}
|
||||
Sub = enumerable.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier)?.Value ?? string.Empty,
|
||||
Name = enumerable.FirstOrDefault(c => c.Type == "name")?.Value ?? string.Empty,
|
||||
Nickname = enumerable.FirstOrDefault(c => c.Type == "nickname")?.Value ?? string.Empty,
|
||||
Email = enumerable.FirstOrDefault(c => c.Type == ClaimTypes.Email)?.Value ?? string.Empty,
|
||||
EmailVerified = bool.TryParse(enumerable.FirstOrDefault(c => c.Type == "email_verified")?.Value, out var emailVerified) && emailVerified
|
||||
};
|
||||
|
||||
await mediator.Send(new SyncUserCommand(auth0User));
|
||||
|
||||
return Ok("Claims logged to console.");
|
||||
return Ok("User Synced.");
|
||||
}
|
||||
}
|
||||
@@ -112,9 +112,6 @@ public static class Startup
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
|
||||
app.UseEndpoints(endpoints =>
|
||||
{
|
||||
endpoints.MapControllers();
|
||||
});
|
||||
app.UseEndpoints(endpoints => { endpoints.MapControllers(); });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user