Split creators out of identity
This commit is contained in:
@@ -4,5 +4,4 @@ public static class CommonFileNames
|
||||
{
|
||||
public static string ProfilePicture = "profilePicture";
|
||||
public static string BannerPicture = "bannerPicture";
|
||||
public static string WebsiteIcon = "websiteIcon";
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
|
||||
public interface IUser
|
||||
{
|
||||
string? Id { get; }
|
||||
Guid? Id { get; }
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
23
src/Application/Users/Queries/GetUser/GetUserById.cs
Normal file
23
src/Application/Users/Queries/GetUser/GetUserById.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
23
src/Application/Users/Queries/GetUser/GetUserByUserName.cs
Normal file
23
src/Application/Users/Queries/GetUser/GetUserByUserName.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user