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

@@ -4,5 +4,4 @@ public static class CommonFileNames
{
public static string ProfilePicture = "profilePicture";
public static string BannerPicture = "bannerPicture";
public static string WebsiteIcon = "websiteIcon";
}

View File

@@ -7,62 +7,74 @@ namespace Hutopy.Application.Common.Behaviours;
public class AuthorizationBehaviour<TRequest, TResponse>(
IUser user,
IIdentityService identityService)
IIdentityService identityService)
: IPipelineBehavior<TRequest, TResponse>
where TRequest : notnull
{
public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken cancellationToken)
public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next,
CancellationToken cancellationToken)
{
var authorizeAttributes = request.GetType().GetCustomAttributes<AuthorizeAttribute>();
var authorizeAttributes = request
.GetType()
.GetCustomAttributes<AuthorizeAttribute>()
.ToArray();
if (authorizeAttributes.Any())
if (authorizeAttributes.Length == 0)
{
// Must be authenticated user
if (user.Id == null)
return await next();
}
if (user.Id is null)
{
throw new UnauthorizedAccessException();
}
// Role-based authorization
var authorizeAttributesWithRoles = authorizeAttributes
.Where(a => !string.IsNullOrWhiteSpace(a.Roles))
.ToArray();
if (authorizeAttributesWithRoles.Length != 0)
{
var authorized = false;
foreach (var roles in authorizeAttributesWithRoles.Select(a => a.Roles.Split(',')))
{
throw new UnauthorizedAccessException();
}
// Role-based authorization
var authorizeAttributesWithRoles = authorizeAttributes.Where(a => !string.IsNullOrWhiteSpace(a.Roles));
if (authorizeAttributesWithRoles.Any())
{
var authorized = false;
foreach (var roles in authorizeAttributesWithRoles.Select(a => a.Roles.Split(',')))
foreach (var role in roles)
{
foreach (var role in roles)
var isInRole = await identityService.IsInRoleAsync(user.Id.Value, role.Trim());
if (isInRole)
{
var isInRole = await identityService.IsInRoleAsync(user.Id, role.Trim());
if (isInRole)
{
authorized = true;
break;
}
authorized = true;
break;
}
}
// Must be a member of at least one role in roles
if (!authorized)
{
throw new ForbiddenAccessException();
}
}
// Policy-based authorization
var authorizeAttributesWithPolicies = authorizeAttributes.Where(a => !string.IsNullOrWhiteSpace(a.Policy));
if (authorizeAttributesWithPolicies.Any())
// Must be a member of at least one role in roles
if (!authorized)
{
foreach (var policy in authorizeAttributesWithPolicies.Select(a => a.Policy))
{
var authorized = await identityService.AuthorizeAsync(user.Id, policy);
throw new ForbiddenAccessException();
}
}
if (!authorized)
{
throw new ForbiddenAccessException();
}
}
// Policy-based authorization
var authorizeAttributesWithPolicies = authorizeAttributes
.Where(a => !string.IsNullOrWhiteSpace(a.Policy))
.ToArray();
if (authorizeAttributesWithPolicies.Length == 0)
{
return await next();
}
foreach (var policy in authorizeAttributesWithPolicies.Select(a => a.Policy))
{
var authorized = await identityService.AuthorizeAsync(user.Id.Value, policy);
if (!authorized)
{
throw new ForbiddenAccessException();
}
}

View File

@@ -5,9 +5,9 @@ using Microsoft.Extensions.Logging;
namespace Hutopy.Application.Common.Behaviours;
public class LoggingBehaviour<TRequest>(
ILogger<TRequest> logger,
IUser user,
IIdentityService identityService)
ILogger<TRequest> logger,
IUser user,
IIdentityService identityService)
: IRequestPreProcessor<TRequest>
where TRequest : notnull
{
@@ -16,15 +16,15 @@ public class LoggingBehaviour<TRequest>(
public async Task Process(TRequest request, CancellationToken cancellationToken)
{
var requestName = typeof(TRequest).Name;
var userId = user.Id ?? string.Empty;
string? userName = string.Empty;
if (!string.IsNullOrEmpty(userId))
if (user.Id.HasValue)
{
userName = await identityService.GetUserNameAsync(userId);
userName = await identityService.GetUserNameAsync(user.Id.Value);
}
_logger.LogInformation("Hutopy Request: {Name} {@UserId} {@UserName} {@Request}",
requestName, userId, userName, request);
_logger.LogInformation(
"Hutopy Request: {Name} {@UserId} {@UserName} {@Request}",
requestName, user.Id ?? Guid.Empty, userName, request);
}
}

View File

@@ -26,13 +26,12 @@ public class PerformanceBehaviour<TRequest, TResponse>(
if (elapsedMilliseconds <= 500) return response;
var requestName = typeof(TRequest).Name;
var userId = user.Id ?? string.Empty;
var userName = string.Empty;
if (!string.IsNullOrEmpty(userId)) userName = await identityService.GetUserNameAsync(userId);
if (user.Id.HasValue) userName = await identityService.GetUserNameAsync(user.Id.Value);
logger.LogWarning("Hutopy Long Running Request: {Name} ({ElapsedMilliseconds} milliseconds) {@UserId} {@UserName} {@Request}",
requestName, elapsedMilliseconds, userId, userName, request);
requestName, elapsedMilliseconds, user.Id ?? Guid.Empty, userName, request);
return response;
}

View File

@@ -2,6 +2,7 @@
public interface IAzureBlobStorageService
{
Task<string> UploadFileAsync(string containerName, string blobName, MemoryStream memoryStream, string contentType, CancellationToken ct = default);
Task<string> UploadFileAsync(string containerName, string blobName, Stream stream, string contentType,
CancellationToken ct = default);
Task<MemoryStream> DownloadFileAsync(string containerName, string blobName, CancellationToken ct = default);
}

View File

@@ -1,29 +1,27 @@
using Google.Apis.Oauth2.v2.Data;
using Hutopy.Application.Common.Models;
using Hutopy.Application.Common.Models;
namespace Hutopy.Application.Common.Interfaces;
public interface IIdentityService
{
Task<Result<string>> CreateUserAsync(Userinfo userInfo);
Task<Result<string>> CreateUserAsync(string email, string userName, string firstName, string lastName,
Task<Result<Guid>> CreateUserAsync(
string email,
string userName,
string firstName,
string lastName,
string password);
Task<UserModel?> GetCurrentUserAsync();
Task<Result> UpdateCurrentUserBannerPictureUrlAsync(string url);
Task<Result> UpdateCurrentUserProfilePictureUrlAsync(string url);
Task<Result> UpdateCurrentUserWebsiteIconUrlAsync(string url);
Task<Result<string>> UpdateCurrentUserAsync(UserModel userModel);
Task<Result> UpdateCurrentUserPortraitUrlAsync(string url);
Task<Result<Guid>> UpdateCurrentUserAsync(UserModel userModel);
Task<IList<string>> GetCurrentUserRolesAsync();
Task<UserModel?> FindUserByIdAsync(string id);
Task<UserModel?> FindUserByCreatorAliasAsync(string creatorAlias, CancellationToken cancellationToken);
Task<UserModel?> FindUserByEmailAsync(string email);
Task<UserModel?> GetUserByUserNameAsync(string userName);
Task<string?> LoginAsync(string email, string password);
Task<bool> IsInRoleAsync(string userId, string role);
Task<bool> AuthorizeAsync(string userId, string policyName);
Task<string?> GetUserNameAsync(string userId);
Task<bool> IsInRoleAsync(Guid userId, string role);
Task<bool> AuthorizeAsync(Guid userId, string policyName);
Task<string?> GetUserNameAsync(Guid userId);
Task<Result> AddRoleAsync(string userId, string role);
Task<Result> DeleteUserAsync(string userId);

View File

@@ -2,5 +2,5 @@
public interface IUser
{
string? Id { get; }
Guid? Id { get; }
}

View File

@@ -2,6 +2,6 @@ namespace Hutopy.Application.Common.Models;
public class RoleModel
{
public string? Id { get; set; }
public Guid Id { get; set; }
public string? Name { get; set; }
}

View File

@@ -1,26 +1,16 @@
using Hutopy.Application.Users.Models;
namespace Hutopy.Application.Common.Models;
public class UserModel
{
public string Id { get; set; } = string.Empty;
public string? CreatorAlias { get; set; }
public string UserName { get; set; } = string.Empty;
public string? Alias { get; set; }
public string FirstName { get; set; } = string.Empty;
public string LastName { get; set; } = string.Empty;
public string Occupation { get; set; } = 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 string ProfilePictureUrl { get; set; } = string.Empty;
public Guid Id { get; set; }
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; }
}

View File

@@ -50,7 +50,7 @@ public class ConfirmStripeTransactionCommandHandler(
{
public async Task<string> Handle(ConfirmStripeTransactionCommand request, CancellationToken cancellationToken)
{
var lastTransaction = await dbContext.UserTransactions.OrderBy(x => x.Created).LastAsync(cancellationToken);
var lastTransaction = await dbContext.UserTransactions.OrderBy(x => x.CreatedAt).LastAsync(cancellationToken);
var stripeConfirmation = stripeService.ValidateTransaction(request);
if (stripeConfirmation.Succeeded)

View File

@@ -2,30 +2,37 @@
using Hutopy.Domain.Entities;
namespace Hutopy.Application.Stripe.Commands;
public record CreateSessionCheckoutCommand : IRequest<string>
{
public required string CreatorId { get; init; }
public required Guid CreatorId { get; init; }
public required int Amount { get; init; }
public string Currency { get; init; } = "CAD";
public string TipMessage { get; init; } = string.Empty;
}
public class CreateSessionCheckoutCommandHandler(
IApplicationDbContext dbContext,
IApplicationDbContext dbContext,
IStripeService stripeService
)
)
: IRequestHandler<CreateSessionCheckoutCommand, string>
{
public async Task<string> Handle(CreateSessionCheckoutCommand request, CancellationToken cancellationToken)
{
var stripeSecret = await stripeService.CreateCheckoutSession(request.Amount, request.CreatorId, request.Currency);
var stripeSecret = await stripeService.CreateCheckoutSession(
request.Amount,
request.CreatorId.ToString(),
request.Currency);
// ReSharper disable once PossibleLossOfFraction
decimal priceInDollars = (request.Amount / 100);
var userTransaction = new UserTransaction
{
Currency = request.Currency, Amount = priceInDollars, TipMessage = request.TipMessage, ApplicationUserId = request.CreatorId
Currency = request.Currency,
Amount = priceInDollars,
TipMessage = request.TipMessage,
ApplicationUserId = request.CreatorId
};
await dbContext.UserTransactions.AddAsync(userTransaction, cancellationToken);

View File

@@ -4,25 +4,23 @@ namespace Hutopy.Application.Stripe.Queries;
public record GetMyLastReceiptQuery : IRequest<MyLastReceiptDto>
{
public Guid CreatorId { get; set; }
public string Email { get; set; } = string.Empty;
public string CreatorId { get; set; } = string.Empty;
};
public class GetMyLastReceiptQueryHandler(
IApplicationDbContext dbContext
)
)
: IRequestHandler<GetMyLastReceiptQuery, MyLastReceiptDto>
{
public async Task<MyLastReceiptDto> Handle(GetMyLastReceiptQuery request, CancellationToken cancellationToken)
{
var lastTransaction = await dbContext.UserTransactions.OrderBy(x => x.Created)
.LastOrDefaultAsync(x => x.ApplicationUserId == request.CreatorId && x.StripeBillingDetailEmail == request.Email,
var lastTransaction = await dbContext.UserTransactions.OrderBy(x => x.CreatedAt)
.LastOrDefaultAsync(
x => x.ApplicationUserId == request.CreatorId && x.StripeBillingDetailEmail == request.Email,
cancellationToken);
var receiptUrl = new MyLastReceiptDto
{
ReceiptUrl = lastTransaction?.StripeReceiptUrl ?? "",
};
var receiptUrl = new MyLastReceiptDto { ReceiptUrl = lastTransaction?.StripeReceiptUrl ?? "", };
return receiptUrl;
}

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
};
}