Split creators out of identity

This commit is contained in:
Jonathan Bourdon
2024-07-31 23:29:26 -04:00
parent bbcc7a8a33
commit 2b30e1a03c
105 changed files with 1497 additions and 7490 deletions

View File

@@ -28,8 +28,10 @@ public class CreateUserCommandHandler : IRequestHandler<CreateUserCommand, IResu
var user = await _identityService.FindUserByEmailAsync(request.EmailAddress);
if (user is null) throw new InvalidOperationException("This should never happen, we just created the user.");
await _context.SaveChangesAsync(cancellationToken);
return Results.Ok(new Guid(user?.Id ?? string.Empty));
return Results.Ok(user.Id);
}
}

View File

@@ -1,27 +1,21 @@
using System.ComponentModel.DataAnnotations.Schema;
using Hutopy.Application.Common.Interfaces;
using Hutopy.Application.Common.Models;
using Hutopy.Application.Users.Models;
using Microsoft.AspNetCore.Http;
namespace Hutopy.Application.Users.Commands;
public class UpdateCurrentUserCommand : IRequest<IResult>
{
public required string FirstName { get; init; }
public required string LastName { get; init; }
public required string Occupation { get; init; }
public required string PhoneNumber { get; init; }
public required string BirthDate { get; init; }
public required string Country { get; init; }
public required string City { get; init; }
public required string Address { get; init; }
public required string About { get; init; }
public required string Description { get; init; }
public required SocialNetworksModel SocialNetworks { get; init; }
public required ProfileColorsModel ProfileColors { get; init; }
public required string? Alias { get; init; }
public required string? FirstName { get; init; }
public required string? LastName { get; init; }
public required string? Occupation { get; init; }
public required string? BirthDate { get; init; }
public required string? Country { get; init; }
public required string? City { get; init; }
public required string? Address { get; init; }
[NotMapped]
private class Mapping : Profile
{
@@ -32,8 +26,11 @@ public class UpdateCurrentUserCommand : IRequest<IResult>
}
}
public class UpdateCurrentUserCommandHandler(IApplicationDbContext context, IIdentityService identityService, IMapper mapper) :
IRequestHandler<UpdateCurrentUserCommand, IResult>
public class UpdateCurrentUserCommandHandler(
IApplicationDbContext context,
IIdentityService identityService,
IMapper mapper)
: IRequestHandler<UpdateCurrentUserCommand, IResult>
{
public async Task<IResult> Handle(UpdateCurrentUserCommand request, CancellationToken cancellationToken)
{
@@ -43,12 +40,11 @@ public class UpdateCurrentUserCommandHandler(IApplicationDbContext context, IIde
var userModel = mapper.Map<UserModel>(request);
userModel.Id = identityUser.Id;
var result = await identityService.UpdateCurrentUserAsync(userModel);
await context.SaveChangesAsync(cancellationToken);
return result.Succeeded ? Results.Ok(result.GetValueOrDefault()) : Results.Problem(result.GetErrorsAsString());
}
}

View File

@@ -1,47 +0,0 @@
using Hutopy.Application.AzureBlobStorage.Constants;
using Hutopy.Application.Common.Interfaces;
using Hutopy.Application.Utils;
using Microsoft.AspNetCore.Http;
namespace Hutopy.Application.Users.Commands;
/// <summary>
/// Upload a banner picture. If the user has the url already, set the BannerPictureUrl in the user only without upload.
/// </summary>
public class UploadBannerPictureCommand : IRequest<IResult>
{
public required MemoryStream BannerPicture { get; init; }
public string BannerPictureUrl { get; init; } = string.Empty;
}
public class UploadBannerPictureCommandHandler(IHttpContextAccessor contextAccessor, IIdentityService identityService, IAzureBlobStorageService azureBlobStorageService) : IRequestHandler<UploadBannerPictureCommand, IResult>
{
public async Task<IResult> Handle(UploadBannerPictureCommand request, CancellationToken cancellationToken)
{
// If an url to the picture is provided, use it right away and don't upload anything.
if (!string.IsNullOrEmpty(request.BannerPictureUrl))
{
await identityService.UpdateCurrentUserBannerPictureUrlAsync(request.BannerPictureUrl);
return Results.Ok(request.BannerPictureUrl);
}
var contentType = contextAccessor.EnsureContentType();
var identityUser = await identityService.GetCurrentUserAsync();
var currentUserId = new Guid(identityUser?.Id ?? "").ToString();
var blobName = $"{currentUserId}/{SubDirectoryNames.Profile}/{CommonFileNames.BannerPicture}";
var url = await azureBlobStorageService.UploadFileAsync(
ContainerNames.Users,
blobName,
request.BannerPicture,
contentType,
cancellationToken);
await identityService.UpdateCurrentUserBannerPictureUrlAsync(url);
return Results.Ok(url);
}
}

View File

@@ -10,38 +10,29 @@ namespace Hutopy.Application.Users.Commands;
/// </summary>
public class UploadProfilePictureCommand : IRequest<IResult>
{
public required MemoryStream ProfilePicture { get; init; }
public string ProfilePictureUrl { get; init; } = string.Empty;
public required IFormFile File { get; init; }
}
public class UploadProfilePictureCommandHandler(IHttpContextAccessor contextAccessor, IIdentityService identityService, IAzureBlobStorageService azureBlobStorageService) : IRequestHandler<UploadProfilePictureCommand, IResult>
public class UploadProfilePictureCommandHandler(
IHttpContextAccessor contextAccessor,
IIdentityService identityService,
IAzureBlobStorageService azureBlobStorageService) : IRequestHandler<UploadProfilePictureCommand, IResult>
{
public async Task<IResult> Handle(UploadProfilePictureCommand request, CancellationToken ct)
{
// If an url to the picture is provided, use it right away and don't upload anything.
if (!string.IsNullOrEmpty(request.ProfilePictureUrl))
{
await identityService.UpdateCurrentUserProfilePictureUrlAsync(request.ProfilePictureUrl);
return Results.Ok(request.ProfilePictureUrl);
}
var contentType = contextAccessor.EnsureContentType();
var identityUser = await identityService.GetCurrentUserAsync();
var currentUserId = new Guid(identityUser?.Id ?? "").ToString();
var blobName = $"{currentUserId}/{SubDirectoryNames.Profile}/{CommonFileNames.ProfilePicture}";
var url = await azureBlobStorageService.UploadFileAsync(
ContainerNames.Users,
blobName,
request.ProfilePicture,
ContainerNames.Users,
$"{identityUser.Id}/{SubDirectoryNames.Profile}/{CommonFileNames.ProfilePicture}",
request.File.OpenReadStream(),
contentType,
ct);
await identityService.UpdateCurrentUserProfilePictureUrlAsync(url);
await identityService.UpdateCurrentUserPortraitUrlAsync(url);
return Results.Ok(url);
}
}

View File

@@ -1,50 +0,0 @@
using Hutopy.Application.AzureBlobStorage.Constants;
using Hutopy.Application.Common.Interfaces;
using Hutopy.Application.Utils;
using Microsoft.AspNetCore.Http;
namespace Hutopy.Application.Users.Commands;
/// <summary>
/// Upload a website icon. If the user has the url already, set the WebsitePictureUrl in the user only without upload.
/// </summary>
public class UploadWebsiteIconCommand : IRequest<IResult>
{
public required MemoryStream WebsiteIcon { get; init; }
public string WebsitePictureUrl { get; init; } = string.Empty;
}
public class UploadWebsiteIconCommandHandler(
IHttpContextAccessor contextAccessor,
IIdentityService identityService,
IAzureBlobStorageService azureBlobStorageService) : IRequestHandler<UploadWebsiteIconCommand, IResult>
{
public async Task<IResult> Handle(UploadWebsiteIconCommand request, CancellationToken ct)
{
// If an url to the picture is provided, use it right away and don't upload anything.
if (!string.IsNullOrEmpty(request.WebsitePictureUrl))
{
await identityService.UpdateCurrentUserWebsiteIconUrlAsync(request.WebsitePictureUrl);
return Results.Ok(request.WebsitePictureUrl);
}
var contentType = contextAccessor.EnsureContentType();
var identityUser = await identityService.GetCurrentUserAsync();
var currentUserId = new Guid(identityUser?.Id ?? "").ToString();
var blobName = $"{currentUserId}/{SubDirectoryNames.Profile}/{CommonFileNames.WebsiteIcon}";
var url = await azureBlobStorageService.UploadFileAsync(
ContainerNames.Users,
blobName,
request.WebsiteIcon,
contentType,
ct);
await identityService.UpdateCurrentUserWebsiteIconUrlAsync(url);
return Results.Ok(request.WebsitePictureUrl);
}
}

View File

@@ -1,9 +0,0 @@
namespace Hutopy.Application.Users.Models;
public class ProfileColorsModel
{
public string BannerTop { get; init; } = String.Empty;
public string BannerBottom { get; init; } = String.Empty;
public string Accent { get; init; } = String.Empty;
public string Menu { get; init; } = String.Empty;
}

View File

@@ -1,13 +0,0 @@
namespace Hutopy.Application.Users.Models;
public class SocialNetworksModel
{
public string FacebookUrl { get; init; } = string.Empty;
public string InstagramUrl { get; init; } = string.Empty;
public string XUrl { get; init; } = string.Empty;
public string LinkedInUrl { get; init; } = string.Empty;
public string TikTokUrl { get; init; } = string.Empty;
public string YoutubeUrl { get; init; } = string.Empty;
public string RedditUrl { get; init; } = string.Empty;
public string YourWebsiteUrl { get; init; } = string.Empty;
}

View File

@@ -1,8 +0,0 @@
namespace Hutopy.Application.Users.Models;
public class StoredDataUrlsModel
{
public string? BannerPictureUrl { get; set; }
public string? ProfilePictureUrl { get; set; }
public string? WebsiteIconUrl { get; set; }
}

View File

@@ -11,15 +11,16 @@ public class GetCurrentUserQueryHandler(
)
: IRequestHandler<GetCurrentUserQuery, UserDto>
{
public async Task<UserDto> Handle(GetCurrentUserQuery request, CancellationToken cancellationToken)
public async Task<UserDto?> Handle(GetCurrentUserQuery request, CancellationToken cancellationToken)
{
var identityUser = await identityService.GetCurrentUserAsync();
var currentUserId = Guid.Parse(identityUser!.Id!);
var userModel = await identityService.GetCurrentUserAsync();
if (userModel is null) return null;
var transactions = await context
.UserTransactions
.Where(x => x.ApplicationUserId == currentUserId.ToString())
.OrderBy(x => x.LastModified)
.Where(x => x.ApplicationUserId == userModel.Id)
.OrderBy(x => x.LastModifiedAt)
.ProjectTo<UserTransactionDto>(mapper.ConfigurationProvider)
.Where(x => x.IsConfirmed == true)
.ToListAsync(cancellationToken);
@@ -28,24 +29,17 @@ public class GetCurrentUserQueryHandler(
var user = new UserDto
{
Id = currentUserId,
Alias = identityUser.Alias,
FirstName = identityUser.FirstName,
LastName = identityUser.LastName,
UserName = identityUser.UserName,
CreatorAlias= identityUser.CreatorAlias,
Occupation = identityUser.Occupation,
PhoneNumber = identityUser.PhoneNumber,
Email = identityUser.Email,
BirthDate = identityUser.BirthDate,
Country = identityUser.Country,
City = identityUser.City,
Address = identityUser.Address,
About = identityUser.About,
Description = identityUser.Description,
SocialNetworks = identityUser.SocialNetworks,
ProfileColors = identityUser.ProfileColors,
StoredDataUrls = identityUser.StoredDataUrls,
Id = userModel.Id,
Alias = userModel.Alias,
PortraitUrl = userModel.PortraitUrl,
FirstName = userModel.FirstName,
LastName = userModel.LastName,
UserName = userModel.UserName,
Occupation = userModel.Occupation,
PhoneNumber = userModel.PhoneNumber,
Email = userModel.Email,
BirthDate = userModel.BirthDate,
Address = userModel.Address,
UserTransactions = transactions,
TotalBalance = transactions.Sum(x => x.Amount),
UserRoles = roles,

View File

@@ -8,16 +8,16 @@ public record GetCurrentUserProfilePictureQuery : IRequest<Stream>;
public class GetCurrentUserProfilePictureQueryHandler(
IIdentityService identityService,
IAzureBlobStorageService azureBlobStorageService
)
)
: IRequestHandler<GetCurrentUserProfilePictureQuery, Stream>
{
public async Task<Stream> Handle(GetCurrentUserProfilePictureQuery request, CancellationToken cancellationToken)
{
var identityUser = await identityService.GetCurrentUserAsync();
var currentUserId = new Guid(identityUser?.Id ?? "");
var blobName = $"{currentUserId.ToString()}/{SubDirectoryNames.Profile}/{CommonFileNames.ProfilePicture}";
return await azureBlobStorageService.DownloadFileAsync(ContainerNames.Users, blobName);
return await azureBlobStorageService.DownloadFileAsync(
ContainerNames.Users,
$"{identityUser.Id.ToString()}/{SubDirectoryNames.Profile}/{CommonFileNames.ProfilePicture}",
cancellationToken);
}
}

View File

@@ -1,28 +1,19 @@
using Hutopy.Application.Users.Models;
namespace Hutopy.Application.Users.Queries.GetCurrentUser;
public class UserDto
{
public Guid Id { get; init; }
public string? Alias { get; init; }
public required string FirstName { get; init; }
public required string LastName { get; init; }
public string? CreatorAlias { get; set; }
public string UserName { get; init; } = string.Empty;
public string Occupation { get; init; } = string.Empty;
public string Email { get; init; } = string.Empty;
public string PhoneNumber { get; init; } = string.Empty;
public string BirthDate { get; init; } = string.Empty;
public string Country { get; init; } = string.Empty;
public string City { get; init; } = string.Empty;
public string Address { get; init; } = string.Empty;
public string About { get; init; } = string.Empty;
public string Description { get; init; } = string.Empty;
public SocialNetworksModel SocialNetworks { get; init; } = new();
public ProfileColorsModel ProfileColors { get; init; } = new();
public StoredDataUrlsModel StoredDataUrls { get; init; } = new();
public List<UserTransactionDto> UserTransactions { get; init; } = [];
public IList<string> UserRoles { get; init; } = [];
public string UserName { get; init; } = null!;
public string? Alias { get; init; }
public string? PortraitUrl { get; init; }
public string? FirstName { get; init; }
public string? LastName { get; init; }
public string? Occupation { get; init; }
public string? Email { get; init; }
public string? PhoneNumber { get; init; }
public string? BirthDate { get; init; }
public string? Address { get; init; }
public List<UserTransactionDto> UserTransactions { get; init; } = [];
public required decimal TotalBalance { get; init; }
}

View File

@@ -1,46 +0,0 @@
using Hutopy.Application.Common.Interfaces;
using Hutopy.Application.Common.Models;
namespace Hutopy.Application.Users.Queries.GetUser;
public record GetUserQuery : IRequest<UserDto>
{
public string? UserId { get; set; } = string.Empty;
public string? UserName { get; set; } = string.Empty;
};
public class GetUserQueryHandler(
IIdentityService identityService
)
: IRequestHandler<GetUserQuery, UserDto>
{
public async Task<UserDto> Handle(GetUserQuery request, CancellationToken cancellationToken)
{
UserModel? identityUser = null;
if (request.UserId != string.Empty)
{
identityUser = await identityService.FindUserByIdAsync(request.UserId);
}
if (request.UserName != string.Empty)
{
identityUser = await identityService.GetUserByUserNameAsync(request.UserName);
}
var user = new UserDto
{
Id = identityUser?.Id ?? string.Empty,
FirstName = identityUser?.FirstName ?? string.Empty,
LastName = identityUser?.LastName ?? string.Empty,
UserName = identityUser?.UserName ?? string.Empty,
Occupation = identityUser?.Occupation ?? string.Empty,
SocialNetworks = identityUser?.SocialNetworks ?? new(),
ProfileColors = identityUser?.ProfileColors ?? new(),
StoredDataUrls = identityUser?.StoredDataUrls ?? new(),
};
return user;
}
}

View File

@@ -0,0 +1,23 @@
using Hutopy.Application.Common.Interfaces;
namespace Hutopy.Application.Users.Queries.GetUser;
public record GetUserByIdQuery : IRequest<UserDto>
{
public required string UserId { get; init; }
}
public class GetUserByIdHandler(
IIdentityService identityService
)
: IRequestHandler<GetUserByIdQuery, UserDto>
{
public async Task<UserDto> Handle(GetUserByIdQuery query, CancellationToken cancellationToken)
{
var user = await identityService.FindUserByIdAsync(query.UserId);
if (user is null) throw new InvalidOperationException();
return user.ToDto();
}
}

View File

@@ -0,0 +1,23 @@
using Hutopy.Application.Common.Interfaces;
namespace Hutopy.Application.Users.Queries.GetUser;
public record GetUserByUserNameQuery : IRequest<UserDto>
{
public required string UserName { get; init; }
};
public class GetUserByUserNameQueryHandler(
IIdentityService identityService
)
: IRequestHandler<GetUserByUserNameQuery, UserDto>
{
public async Task<UserDto> Handle(GetUserByUserNameQuery query, CancellationToken cancellationToken)
{
var user = await identityService.GetUserByUserNameAsync(query.UserName);
if (user is null) throw new InvalidOperationException();
return user.ToDto();
}
}

View File

@@ -1,17 +1,25 @@
using Hutopy.Application.Users.Models;
using Hutopy.Application.Common.Models;
namespace Hutopy.Application.Users.Queries.GetUser;
public class UserDto
{
public required string Id { get; init; }
public required string FirstName { get; init; }
public required string LastName { get; init; }
public string CreatorAlias { get; set; }
public required string UserName { get; init; } = String.Empty;
public required string Occupation { get; init; } = String.Empty;
public SocialNetworksModel SocialNetworks { get; init; } = new();
public ProfileColorsModel ProfileColors { get; init; } = new();
public StoredDataUrlsModel StoredDataUrls { get; init; } = new();
public required Guid Id { get; init; }
public required string UserName { get; init; }
public string? FirstName { get; init; }
public string? LastName { get; init; }
public string? Occupation { get; init; }
}
public static class UserDtoExtensions
{
public static UserDto ToDto(this UserModel model) =>
new()
{
Id = model.Id,
FirstName = model.FirstName,
LastName = model.LastName,
UserName = model.UserName,
Occupation = model.Occupation
};
}