diff --git a/Directory.Packages.props b/Directory.Packages.props
index a2b4869..3c60acd 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -10,6 +10,7 @@
+
diff --git a/Hutopy.sln b/Hutopy.sln
index 5714f45..2fe5931 100644
--- a/Hutopy.sln
+++ b/Hutopy.sln
@@ -25,6 +25,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
Directory.Packages.props = Directory.Packages.props
global.json = global.json
README.md = README.md
+ start-infrastructure.sh = start-infrastructure.sh
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Web", "src\Web\Web.csproj", "{4E4EE20C-F06A-4A1B-851F-C5577796941C}"
diff --git a/src/Application/AzureBlobStorage/Constants/CommonFileNames.cs b/src/Application/AzureBlobStorage/Constants/CommonFileNames.cs
index 7eabb9a..b6d44aa 100644
--- a/src/Application/AzureBlobStorage/Constants/CommonFileNames.cs
+++ b/src/Application/AzureBlobStorage/Constants/CommonFileNames.cs
@@ -3,4 +3,6 @@
public static class CommonFileNames
{
public static string ProfilePicture = "profilePicture";
+ public static string BannerPicture = "bannerPicture";
+ public static string WebsiteIcon = "websiteIcon";
}
diff --git a/src/Application/Common/Interfaces/IIdentityService.cs b/src/Application/Common/Interfaces/IIdentityService.cs
index cd97117..74e9240 100644
--- a/src/Application/Common/Interfaces/IIdentityService.cs
+++ b/src/Application/Common/Interfaces/IIdentityService.cs
@@ -1,21 +1,27 @@
using Google.Apis.Oauth2.v2.Data;
using Hutopy.Application.Common.Models;
+using Hutopy.Application.Users.Models;
namespace Hutopy.Application.Common.Interfaces;
public interface IIdentityService
{
- Task GetUserNameAsync(string userId);
- Task CreateUserAsync(string email, string userName, string firstName, string lastName, string password);
- Task FindUserByIdAsync(string id);
+ Task> CreateUserAsync(Userinfo userInfo);
+ Task> CreateUserAsync(string email, string userName, string firstName, string lastName, string password);
Task GetCurrentUserAsync();
- Task FindUserByEmailAsync(string id);
- Task LoginAsync(string email, string password);
+ Task UpdateCurrentUserBannerPictureUrlAsync(string url);
+ Task UpdateCurrentUserProfilePictureUrlAsync(string url);
+ Task UpdateCurrentUserWebsiteIconUrlAsync(string url);
+ Task> UpdateCurrentUserAsync(UserModel userModel);
+ Task> GetCurrentUserRolesAsync();
+ Task FindUserByIdAsync(string id);
+ Task FindUserByEmailAsync(string email);
Task GetUserByUserNameAsync(string userName);
+ Task LoginAsync(string email, string password);
Task IsInRoleAsync(string userId, string role);
Task AuthorizeAsync(string userId, string policyName);
+ Task GetUserNameAsync(string userId);
+
Task AddRoleAsync(string userId, string role);
- Task> GetCurrentUserRolesAsync();
- Task<(Result Result, string UserId)> CreateUserAsync(Userinfo userInfo);
Task DeleteUserAsync(string userId);
}
diff --git a/src/Application/Common/Models/Result.cs b/src/Application/Common/Models/Result.cs
index f99bb27..294e2ed 100644
--- a/src/Application/Common/Models/Result.cs
+++ b/src/Application/Common/Models/Result.cs
@@ -5,9 +5,8 @@ public class Result(
IEnumerable errors)
{
public bool Succeeded { get; init; } = succeeded;
-
public string[] Errors { get; init; } = errors.ToArray();
-
+
public static Result Success()
{
return new Result(true, Array.Empty());
@@ -18,3 +17,28 @@ public class Result(
return new Result(false, errors);
}
}
+
+public class Result(
+ T? value,
+ bool succeeded,
+ IEnumerable errors)
+{
+ public bool Succeeded { get; init; } = succeeded;
+ public string[] Errors { get; init; } = errors.ToArray();
+ public T? Value { get; set; } = value;
+
+ public T GetValueOrDefault()
+ {
+ return Value ?? default(T)!;
+ }
+
+ public static Result Success(T value)
+ {
+ return new Result(value, true, Array.Empty());
+ }
+
+ public static Result Failure(T value, IEnumerable errors)
+ {
+ return new Result(value, false, errors);
+ }
+}
diff --git a/src/Application/Common/Models/UserModel.cs b/src/Application/Common/Models/UserModel.cs
index 5c4358b..ccff715 100644
--- a/src/Application/Common/Models/UserModel.cs
+++ b/src/Application/Common/Models/UserModel.cs
@@ -1,10 +1,24 @@
+using Hutopy.Application.Users.Models;
+
namespace Hutopy.Application.Common.Models;
public class UserModel
{
- public string? Id { get; set; }
- public string? UserName { get; set; }
- public string? FirstName { get; set; }
- public string? LastName { get; set; }
- public string? Email { get; set; }
+ public string Id { get; set; } = string.Empty;
+ public string UserName { get; set; } = string.Empty;
+ 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;
}
diff --git a/src/Application/Stripe/Commands/ConfirmStripeTransaction.cs b/src/Application/Stripe/Commands/ConfirmStripeTransaction.cs
index 0ddd17e..0c74658 100644
--- a/src/Application/Stripe/Commands/ConfirmStripeTransaction.cs
+++ b/src/Application/Stripe/Commands/ConfirmStripeTransaction.cs
@@ -17,29 +17,29 @@ public class Data
public class Object
{
- public string Id { get; set; } = String.Empty;
+ public string Id { get; set; } = string.Empty;
public int Amount { get; set; }
public BillingDetails Billing_details { get; set; } = new();
- public string Calculated_statement_descriptor { get; set; } = String.Empty;
- public string Currency { get; set; } = String.Empty;
+ public string Calculated_statement_descriptor { get; set; } = string.Empty;
+ public string Currency { get; set; } = string.Empty;
public bool Paid { get; set; }
- public string Payment_intent { get; set; } = String.Empty;
- public string Payment_method { get; set; } = String.Empty;
- public string Receipt_url { get; set; } = String.Empty;
- public string Status { get; set; } = String.Empty;
- public string Failure_message { get; set; } = String.Empty;
+ public string Payment_intent { get; set; } = string.Empty;
+ public string Payment_method { get; set; } = string.Empty;
+ public string Receipt_url { get; set; } = string.Empty;
+ public string Status { get; set; } = string.Empty;
+ public string Failure_message { get; set; } = string.Empty;
}
public class BillingDetails
{
- public string Email { get; set; } = String.Empty;
- public string Name { get; set; } = String.Empty;
- public string Phone { get; set; } = String.Empty;
+ public string Email { get; set; } = string.Empty;
+ public string Name { get; set; } = string.Empty;
+ public string Phone { get; set; } = string.Empty;
}
public class Request
{
- public string Id { get; set; } = String.Empty;
+ public string Id { get; set; } = string.Empty;
}
public class ConfirmStripeTransactionCommandHandler(
diff --git a/src/Application/Users/Commands/Login.cs b/src/Application/Users/Commands/Login.cs
index f61f810..8f232b5 100644
--- a/src/Application/Users/Commands/Login.cs
+++ b/src/Application/Users/Commands/Login.cs
@@ -1,27 +1,27 @@
using Hutopy.Application.Common.Interfaces;
namespace Hutopy.Application.Users.Commands;
-public record LoginCommand : IRequest
+
+public record LoginCommand(
+ string Email,
+ string Password)
+ : IRequest;
+
+public record LoginResponse(
+ string AccessToken,
+ string RefreshToken);
+
+public class LoginCommandHandler(
+ IApplicationDbContext Context,
+ IIdentityService identityService)
+ : IRequestHandler
{
- public required string EmailAddress { get; init; }
- public required string Password { get; init; }
-}
-
-public class LoginCommandHandler : IRequestHandler
-{
- private readonly IApplicationDbContext _context;
- private readonly IIdentityService _identityService;
-
- public LoginCommandHandler(IApplicationDbContext context, IIdentityService identityService)
+ public async Task Handle(LoginCommand request, CancellationToken cancellationToken)
{
- _context = context;
- _identityService = identityService;
- }
+ var accessToken = await identityService.LoginAsync(request.Email, request.Password);
- public async Task Handle(LoginCommand request, CancellationToken cancellationToken)
- {
- var jwt = await _identityService.LoginAsync(request.EmailAddress, request.Password);
+ if (string.IsNullOrWhiteSpace(accessToken)) throw new InvalidOperationException("Invalid login credentials");
- return jwt ?? "Invalid login credentials";
+ return new LoginResponse(accessToken, string.Empty);
}
}
diff --git a/src/Application/Users/Commands/UpdateCurrentUserCommand.cs b/src/Application/Users/Commands/UpdateCurrentUserCommand.cs
new file mode 100644
index 0000000..193d904
--- /dev/null
+++ b/src/Application/Users/Commands/UpdateCurrentUserCommand.cs
@@ -0,0 +1,52 @@
+using System.ComponentModel.DataAnnotations.Schema;
+using Hutopy.Application.Common.Interfaces;
+using Hutopy.Application.Common.Models;
+using Hutopy.Application.Users.Models;
+
+namespace Hutopy.Application.Users.Commands;
+
+public class UpdateCurrentUserCommand : IRequest
+{
+ 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; }
+
+ [NotMapped]
+ private class Mapping : Profile
+ {
+ public Mapping()
+ {
+ CreateMap();
+ }
+ }
+}
+
+public class UpdateCurrentUserCommandHandler(IApplicationDbContext context, IIdentityService identityService, IMapper mapper) :
+ IRequestHandler
+{
+ public async Task Handle(UpdateCurrentUserCommand request, CancellationToken cancellationToken)
+ {
+ var identityUser = await identityService.GetCurrentUserAsync();
+
+ if (identityUser?.Id is null) return string.Empty;
+
+ var userModel = mapper.Map(request);
+ userModel.Id = identityUser.Id;
+
+ var result = await identityService.UpdateCurrentUserAsync(userModel);
+
+ await context.SaveChangesAsync(cancellationToken);
+
+ return result.GetValueOrDefault();
+ }
+}
+
diff --git a/src/Application/Users/Commands/UploadBannerPicture.cs b/src/Application/Users/Commands/UploadBannerPicture.cs
new file mode 100644
index 0000000..66922a6
--- /dev/null
+++ b/src/Application/Users/Commands/UploadBannerPicture.cs
@@ -0,0 +1,27 @@
+using Hutopy.Application.AzureBlobStorage.Constants;
+using Hutopy.Application.Common.Interfaces;
+
+namespace Hutopy.Application.Users.Commands;
+
+public class UploadBannerPictureCommand : IRequest
+{
+ public required Stream BannerPicture { get; init; }
+}
+
+public class UploadBannerPictureCommandHandler(IIdentityService identityService, IAzureBlobStorageService azureBlobStorageService) : IRequestHandler
+{
+ public async Task Handle(UploadBannerPictureCommand request, CancellationToken cancellationToken)
+ {
+ 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);
+
+ await identityService.UpdateCurrentUserBannerPictureUrlAsync(url);
+
+ return url;
+ }
+}
+
diff --git a/src/Application/Users/Commands/UploadProfilePicture.cs b/src/Application/Users/Commands/UploadProfilePicture.cs
index 08746d9..6fc4714 100644
--- a/src/Application/Users/Commands/UploadProfilePicture.cs
+++ b/src/Application/Users/Commands/UploadProfilePicture.cs
@@ -19,6 +19,8 @@ public class UploadProfilePictureCommandHandler(IIdentityService identityService
var url = await azureBlobStorageService.UploadFileAsync(ContainerNames.Users, blobName, request.ProfilePicture);
+ await identityService.UpdateCurrentUserProfilePictureUrlAsync(url);
+
return url;
}
}
diff --git a/src/Application/Users/Commands/UploadWebsiteIcon.cs b/src/Application/Users/Commands/UploadWebsiteIcon.cs
new file mode 100644
index 0000000..78a83a2
--- /dev/null
+++ b/src/Application/Users/Commands/UploadWebsiteIcon.cs
@@ -0,0 +1,27 @@
+using Hutopy.Application.AzureBlobStorage.Constants;
+using Hutopy.Application.Common.Interfaces;
+
+namespace Hutopy.Application.Users.Commands;
+
+public class UploadWebsiteIconCommand : IRequest
+{
+ public required Stream WebsiteIcon { get; init; }
+}
+
+public class UploadWebsiteIconCommandHandler(IIdentityService identityService, IAzureBlobStorageService azureBlobStorageService) : IRequestHandler
+{
+ public async Task Handle(UploadWebsiteIconCommand request, CancellationToken cancellationToken)
+ {
+ 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);
+
+ await identityService.UpdateCurrentUserWebsiteIconUrlAsync(url);
+
+ return url;
+ }
+}
+
diff --git a/src/Application/Users/Models/ProfileColorsModel.cs b/src/Application/Users/Models/ProfileColorsModel.cs
new file mode 100644
index 0000000..04baadb
--- /dev/null
+++ b/src/Application/Users/Models/ProfileColorsModel.cs
@@ -0,0 +1,9 @@
+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;
+}
diff --git a/src/Application/Users/Models/SocialNetworksModel.cs b/src/Application/Users/Models/SocialNetworksModel.cs
new file mode 100644
index 0000000..19a2f02
--- /dev/null
+++ b/src/Application/Users/Models/SocialNetworksModel.cs
@@ -0,0 +1,13 @@
+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;
+}
diff --git a/src/Application/Users/Models/StoredDataUrlsModel.cs b/src/Application/Users/Models/StoredDataUrlsModel.cs
new file mode 100644
index 0000000..0539508
--- /dev/null
+++ b/src/Application/Users/Models/StoredDataUrlsModel.cs
@@ -0,0 +1,8 @@
+namespace Hutopy.Application.Users.Models;
+
+public class StoredDataUrlsModel
+{
+ public string BannerPictureUrl { get; set; } = string.Empty;
+ public string ProfilePictureUrl { get; set; } = string.Empty;
+ public string WebsiteIconUrl { get; set; } = string.Empty;
+}
diff --git a/src/Application/Users/Queries/GetCurrentUser/GetCurrentUser.cs b/src/Application/Users/Queries/GetCurrentUser/GetCurrentUser.cs
index 49f1152..f8cc1ab 100644
--- a/src/Application/Users/Queries/GetCurrentUser/GetCurrentUser.cs
+++ b/src/Application/Users/Queries/GetCurrentUser/GetCurrentUser.cs
@@ -8,14 +8,14 @@ public class GetCurrentUserQueryHandler(
IApplicationDbContext context,
IMapper mapper,
IIdentityService identityService
- )
+)
: IRequestHandler
{
public async Task Handle(GetCurrentUserQuery request, CancellationToken cancellationToken)
{
var identityUser = await identityService.GetCurrentUserAsync();
- var currentUserId = new Guid(identityUser?.Id ?? "");
-
+ var currentUserId = Guid.Parse(identityUser!.Id!);
+
var transactions = await context.UserTransactions
.Where(x => x.ApplicationUserId == currentUserId.ToString())
.OrderBy(x => x.LastModified)
@@ -28,12 +28,24 @@ public class GetCurrentUserQueryHandler(
var user = new UserDto
{
Id = currentUserId,
- FirstName = identityUser?.FirstName ?? "",
- LastName = identityUser?.LastName ?? "",
- UserName =identityUser?.UserName ?? "",
+ FirstName = identityUser.FirstName ?? "",
+ LastName = identityUser.LastName ?? "",
+ UserName = identityUser.UserName ?? "",
+ 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,
UserTransactions = transactions,
TotalBalance = transactions.Sum(x => x.Amount),
- UserRoles = roles
+ UserRoles = roles,
};
return user;
diff --git a/src/Application/Users/Queries/GetCurrentUser/UserDto.cs b/src/Application/Users/Queries/GetCurrentUser/UserDto.cs
index c7d121d..23dfaa8 100644
--- a/src/Application/Users/Queries/GetCurrentUser/UserDto.cs
+++ b/src/Application/Users/Queries/GetCurrentUser/UserDto.cs
@@ -1,3 +1,5 @@
+using Hutopy.Application.Users.Models;
+
namespace Hutopy.Application.Users.Queries.GetCurrentUser;
public class UserDto
@@ -5,9 +7,20 @@ public class UserDto
public Guid Id { get; init; }
public required string FirstName { get; init; }
public required string LastName { get; init; }
- public string UserName { get; init; } = String.Empty;
+ 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 UserTransactions { get; init; } = [];
public IList UserRoles { get; init; } = [];
public required decimal TotalBalance { get; init; }
-
}
diff --git a/src/Infrastructure/AzureBlob/AzureBlobStorageService.cs b/src/Infrastructure/AzureBlob/AzureBlobStorageService.cs
index f54f96b..b25521c 100644
--- a/src/Infrastructure/AzureBlob/AzureBlobStorageService.cs
+++ b/src/Infrastructure/AzureBlob/AzureBlobStorageService.cs
@@ -1,3 +1,6 @@
+using System;
+using System.IO;
+using System.Threading.Tasks;
using Azure.Storage.Blobs;
using Azure.Storage.Blobs.Models;
using Hutopy.Application.Common.Interfaces;
diff --git a/src/Infrastructure/Data/ApplicationDbContextInitializer.cs b/src/Infrastructure/Data/ApplicationDbContextInitializer.cs
index 6a985de..b771d57 100644
--- a/src/Infrastructure/Data/ApplicationDbContextInitializer.cs
+++ b/src/Infrastructure/Data/ApplicationDbContextInitializer.cs
@@ -1,4 +1,7 @@
-using Hutopy.Domain.Constants;
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+using Hutopy.Domain.Constants;
using Hutopy.Infrastructure.Identity;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Identity;
diff --git a/src/Infrastructure/Data/Configurations/ApplicationUserConfiguration.cs b/src/Infrastructure/Data/Configurations/ApplicationUserConfiguration.cs
index 0624b7a..b3a3735 100644
--- a/src/Infrastructure/Data/Configurations/ApplicationUserConfiguration.cs
+++ b/src/Infrastructure/Data/Configurations/ApplicationUserConfiguration.cs
@@ -12,5 +12,15 @@ public class ApplicationUserConfiguration : IEntityTypeConfiguration u.SocialNetworks)
.ToTable($"{nameof(ApplicationUser)}_SocialNetworks");
+
+ // Relationship between ApplicationUser and ProfileColors
+ builder
+ .OwnsOne(u => u.ProfileColors)
+ .ToTable($"{nameof(ApplicationUser)}_ProfileColors");
+
+ // Relationship between ApplicationUser and StoredDataUrls
+ builder
+ .OwnsOne(u => u.StoredDataUrls)
+ .ToTable($"{nameof(ApplicationUser)}_StoredDataUrls");
}
}
diff --git a/src/Infrastructure/DependencyInjection.cs b/src/Infrastructure/DependencyInjection.cs
index e03ce4c..2dfccb2 100644
--- a/src/Infrastructure/DependencyInjection.cs
+++ b/src/Infrastructure/DependencyInjection.cs
@@ -1,4 +1,5 @@
-using Hutopy.Application.Common.Interfaces;
+using System;
+using Hutopy.Application.Common.Interfaces;
using Hutopy.Domain.Constants;
using Hutopy.Infrastructure.AzureBlob;
using Hutopy.Infrastructure.Data;
@@ -15,22 +16,13 @@ namespace Hutopy.Infrastructure;
public static class DependencyInjection
{
- public static IServiceCollection AddInfrastructureServices(this IServiceCollection services, IConfiguration configuration)
+ public static IServiceCollection AddInfrastructureServices(this IServiceCollection services,
+ IConfiguration configuration)
{
// Replace password in the connection string with env var in local environment.
// Prod will use the connectionString stored in the vault with password in it directly.
- var connectionString = configuration.GetConnectionString("DefaultConnection") ?? "";
-
- var dbPassword = configuration["DB_PASSWORD"] ?? "";
- var dbHost = configuration["DB_HOST"] ?? "localhost";
-
- if (dbPassword != string.Empty)
- {
- connectionString = connectionString.Replace("{DB_PASSWORD}", dbPassword);
- connectionString = connectionString.Replace("{DB_HOST}", dbHost);
- }
-
- Guard.Against.Null(connectionString, message: "Connection string 'DefaultConnection' not found.");
+ var connectionString = configuration.GetConnectionString("DefaultConnection")
+ ?? throw new InvalidOperationException("Missing ConnectionString: DefaultConnection");
services.AddScoped();
services.AddScoped();
@@ -49,15 +41,9 @@ public static class DependencyInjection
.AddBearerToken(IdentityConstants.BearerScheme);
services.AddAuthorizationBuilder();
-
- services.AddIdentityCore(options =>
- {
- options.Password.RequireDigit = false;
- options.Password.RequireLowercase = false;
- options.Password.RequireUppercase = false;
- options.Password.RequireNonAlphanumeric = false;
- options.Password.RequiredLength = 8;
- })
+
+ services
+ .AddIdentityCore()
.AddRoles()
.AddEntityFrameworkStores()
.AddApiEndpoints()
diff --git a/src/Infrastructure/Identity/ApplicationUser.cs b/src/Infrastructure/Identity/ApplicationUser.cs
index 700a074..fbf49f7 100644
--- a/src/Infrastructure/Identity/ApplicationUser.cs
+++ b/src/Infrastructure/Identity/ApplicationUser.cs
@@ -7,5 +7,14 @@ public class ApplicationUser : IdentityUser
{
public string FirstName { get; set; } = string.Empty;
public string LastName { get; set; } = string.Empty;
+ public string Occupation { get; set; } = string.Empty;
+ public string BirthDate { get; set; } = string.Empty;
+ public string Country { get; set; } = string.Empty;
+ public string City { get; set; } = string.Empty;
+ public string Address { get; set; } = string.Empty;
+ public string About { get; set; } = string.Empty;
+ public string Description { get; set; } = string.Empty;
public SocialNetworks SocialNetworks { get; set; } = new();
+ public ProfileColors ProfileColors { get; set; } = new();
+ public StoredDataUrls StoredDataUrls { get; set; } = new();
}
diff --git a/src/Infrastructure/Identity/IdentityService.cs b/src/Infrastructure/Identity/IdentityService.cs
index 02b5ce0..b0be8f2 100644
--- a/src/Infrastructure/Identity/IdentityService.cs
+++ b/src/Infrastructure/Identity/IdentityService.cs
@@ -1,7 +1,12 @@
+using System;
+using System.Collections.Generic;
using Google.Apis.Oauth2.v2.Data;
using System.Security.Claims;
+using System.Threading.Tasks;
using Hutopy.Application.Common.Interfaces;
using Hutopy.Application.Common.Models;
+using Hutopy.Application.Users.Models;
+using Hutopy.Infrastructure.Identity.OwnedEntities;
using Hutopy.Infrastructure.Utils;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
@@ -44,23 +49,10 @@ public class IdentityService(
return userModel;
}
-
- public async Task<(Result Result, string UserId)> CreateUserAsync(string userName, string password)
- {
- var user = new ApplicationUser
- {
- UserName = userName,
- Email = userName,
- };
-
- var result = await userManager.CreateAsync(user, password);
-
- return (result.ToApplicationResult(), user.Id);
- }
- public async Task<(Result Result, string UserId)> CreateUserAsync(Userinfo userInfo)
+ public async Task> CreateUserAsync(Userinfo userInfo)
{
- var user = new ApplicationUser
+ var applicationUser = new ApplicationUser
{
UserName = userInfo.Name,
Email = userInfo.Email,
@@ -70,12 +62,16 @@ public class IdentityService(
var password = Guid.NewGuid().ToString("N")[..32];
- var result = await userManager.CreateAsync(user, password);
+ var identityResult = await userManager.CreateAsync(applicationUser, password);
- return (result.ToApplicationResult(), user.Id);
+ var applicationResult = identityResult.ToApplicationResult();
+
+ var result = new Result(applicationUser.Id, applicationResult.Succeeded, applicationResult.Errors);
+
+ return result;
}
- public async Task CreateUserAsync(string email, string userName, string firstName, string lastName, string password)
+ public async Task> CreateUserAsync(string email, string userName, string firstName, string lastName, string password)
{
var applicationUser = new ApplicationUser
{
@@ -87,7 +83,54 @@ public class IdentityService(
var response = await userManager.CreateAsync(applicationUser, password);
- return response.ToApplicationResult();
+ var result = new Result(applicationUser.Id, response.Succeeded, response.ToApplicationResult().Errors);
+
+ return result;
+ }
+
+ public async Task> UpdateCurrentUserAsync(UserModel userModel)
+ {
+ var applicationUser = await userManager.FindByIdAsync(userModel.Id);
+
+ if (applicationUser is null) return Result.Failure("", new[] { "User not found." });
+
+ applicationUser.FirstName = userModel.FirstName;
+ applicationUser.LastName = userModel.LastName;
+ applicationUser.Occupation = userModel.Occupation;
+ applicationUser.PhoneNumber = userModel.PhoneNumber;
+ applicationUser.BirthDate = userModel.BirthDate;
+ applicationUser.Country = userModel.Country;
+ applicationUser.City = userModel.City;
+ applicationUser.Address = userModel.Address;
+ applicationUser.About = userModel.About;
+ applicationUser.Description = userModel.Description;
+ applicationUser.SocialNetworks = new SocialNetworks
+ {
+ FacebookUrl = userModel.SocialNetworks.FacebookUrl,
+ InstagramUrl = userModel.SocialNetworks.InstagramUrl,
+ XUrl = userModel.SocialNetworks.XUrl,
+ LinkedInUrl = userModel.SocialNetworks.LinkedInUrl,
+ TikTokUrl = userModel.SocialNetworks.TikTokUrl,
+ YoutubeUrl = userModel.SocialNetworks.YoutubeUrl,
+ RedditUrl = userModel.SocialNetworks.RedditUrl,
+ YourWebsiteUrl = userModel.SocialNetworks.YourWebsiteUrl
+ };
+ applicationUser.ProfileColors = new ProfileColors
+ {
+ BannerTop = userModel.ProfileColors.BannerTop,
+ BannerBottom = userModel.ProfileColors.BannerBottom,
+ Accent = userModel.ProfileColors.Accent,
+ Menu = userModel.ProfileColors.Menu
+ };
+
+ var response = await userManager.UpdateAsync(applicationUser);
+
+ var applicationResult = response.ToApplicationResult();
+
+ var result = new Result(userModel.Id, applicationResult.Succeeded,
+ applicationResult.Errors);
+
+ return result;
}
public async Task FindUserByIdAsync(string id)
@@ -96,13 +139,95 @@ public class IdentityService(
if (response == null) return null;
- var userModel = new UserModel()
+ var userModel = new UserModel
{
Id = response.Id,
- UserName = response.UserName,
+ UserName = response.UserName ?? string.Empty,
FirstName = response.FirstName,
LastName = response.LastName,
- Email = response.Email,
+ Email = response.Email ?? string.Empty,
+ Occupation = response.Occupation,
+ PhoneNumber = response.PhoneNumber ?? string.Empty,
+ BirthDate = response.BirthDate,
+ Country = response.Country,
+ City = response.City,
+ Address = response.Address,
+ About = response.About,
+ Description = response.Description,
+ SocialNetworks = new SocialNetworksModel
+ {
+ FacebookUrl = response.SocialNetworks.FacebookUrl,
+ InstagramUrl = response.SocialNetworks.InstagramUrl,
+ XUrl = response.SocialNetworks.XUrl,
+ LinkedInUrl = response.SocialNetworks.LinkedInUrl,
+ TikTokUrl = response.SocialNetworks.TikTokUrl,
+ YoutubeUrl = response.SocialNetworks.YoutubeUrl,
+ RedditUrl = response.SocialNetworks.RedditUrl,
+ YourWebsiteUrl = response.SocialNetworks.YourWebsiteUrl,
+ },
+ ProfileColors = new ProfileColorsModel
+ {
+ BannerTop = response.ProfileColors.BannerTop,
+ BannerBottom = response.ProfileColors.BannerBottom,
+ Accent = response.ProfileColors.Accent,
+ Menu = response.ProfileColors.Menu
+ },
+ StoredDataUrls = new StoredDataUrlsModel
+ {
+ ProfilePictureUrl = response.StoredDataUrls.ProfilePictureUrl,
+ BannerPictureUrl = response.StoredDataUrls.BannerPictureUrl,
+ WebsiteIconUrl = response.StoredDataUrls.WebsiteIconUrl,
+ }
+ };
+
+ return userModel;
+ }
+
+ public async Task FindUserByEmailAsync(string email)
+ {
+ var response = await userManager.FindByEmailAsync(email);
+
+ if (response == null) return null;
+
+ var userModel = new UserModel
+ {
+ Id = response.Id,
+ UserName = response.UserName ?? string.Empty,
+ FirstName = response.FirstName,
+ LastName = response.LastName,
+ Email = response.Email ?? string.Empty,
+ Occupation = response.Occupation,
+ PhoneNumber = response.PhoneNumber ?? string.Empty,
+ BirthDate = response.BirthDate,
+ Country = response.Country,
+ City = response.City,
+ Address = response.Address,
+ About = response.About,
+ Description = response.Description,
+ SocialNetworks = new SocialNetworksModel
+ {
+ FacebookUrl = response.SocialNetworks.FacebookUrl,
+ InstagramUrl = response.SocialNetworks.InstagramUrl,
+ XUrl = response.SocialNetworks.XUrl,
+ LinkedInUrl = response.SocialNetworks.LinkedInUrl,
+ TikTokUrl = response.SocialNetworks.TikTokUrl,
+ YoutubeUrl = response.SocialNetworks.YoutubeUrl,
+ RedditUrl = response.SocialNetworks.RedditUrl,
+ YourWebsiteUrl = response.SocialNetworks.YourWebsiteUrl,
+ },
+ ProfileColors = new ProfileColorsModel
+ {
+ BannerTop = response.ProfileColors.BannerTop,
+ BannerBottom = response.ProfileColors.BannerBottom,
+ Accent = response.ProfileColors.Accent,
+ Menu = response.ProfileColors.Menu
+ },
+ StoredDataUrls = new StoredDataUrlsModel
+ {
+ ProfilePictureUrl = response.StoredDataUrls.ProfilePictureUrl,
+ BannerPictureUrl = response.StoredDataUrls.BannerPictureUrl,
+ WebsiteIconUrl = response.StoredDataUrls.WebsiteIconUrl,
+ }
};
return userModel;
@@ -119,22 +244,49 @@ public class IdentityService(
return await FindUserByIdAsync(currentUserId);
}
- public async Task FindUserByEmailAsync(string email)
+ public async Task UpdateCurrentUserBannerPictureUrlAsync(string url)
{
- var response = await userManager.FindByEmailAsync(email);
+ var userModel = await GetCurrentUserAsync();
+ if (userModel is null) return Result.Failure(new[] { "User not found." });
- if (response == null) return null;
+ var applicationUser = await userManager.FindByIdAsync(userModel.Id);
+ if (applicationUser is null) return Result.Failure(new[] { "ApplicationUser not found." });
- var userModel = new UserModel
- {
- Id = response.Id,
- UserName = response.UserName,
- FirstName = response.FirstName,
- LastName = response.LastName,
- Email = response.Email
- };
+ applicationUser.StoredDataUrls.BannerPictureUrl = url;
+
+ var response = await userManager.UpdateAsync(applicationUser);
- return userModel;
+ return response.ToApplicationResult();
+ }
+
+ public async Task UpdateCurrentUserProfilePictureUrlAsync(string url)
+ {
+ var userModel = await GetCurrentUserAsync();
+ if (userModel is null) return Result.Failure(new[] { "User not found." });
+
+ var applicationUser = await userManager.FindByIdAsync(userModel.Id);
+ if (applicationUser is null) return Result.Failure(new[] { "ApplicationUser not found." });
+
+ applicationUser.StoredDataUrls.ProfilePictureUrl = url;
+
+ var response = await userManager.UpdateAsync(applicationUser);
+
+ return response.ToApplicationResult();
+ }
+
+ public async Task UpdateCurrentUserWebsiteIconUrlAsync(string url)
+ {
+ var userModel = await GetCurrentUserAsync();
+ if (userModel is null) return Result.Failure(new[] { "User not found." });
+
+ var applicationUser = await userManager.FindByIdAsync(userModel.Id);
+ if (applicationUser is null) return Result.Failure(new[] { "ApplicationUser not found." });
+
+ applicationUser.StoredDataUrls.WebsiteIconUrl = url;
+
+ var response = await userManager.UpdateAsync(applicationUser);
+
+ return response.ToApplicationResult();
}
public async Task IsInRoleAsync(string userId, string role)
@@ -218,13 +370,22 @@ public class IdentityService(
{
return null;
}
-
+
var user = await GetUserByUserNameAsync(userName);
+
+ if (user is null) throw new InvalidOperationException();
+
+ var jwtSection = configuration.GetRequiredSection("Authentication:Jwt");
+
var token = JwtTokenHelper.GenerateJwtToken(
- issuer: configuration["Jwt-Issuer"] ?? "",
- audience: configuration["Jwt-Audience"] ?? "",
- key: configuration["Jwt-Key"] ?? "",
- userId: user?.Id ?? "");
+ issuer: jwtSection["Issuer"] ?? "",
+ audience: jwtSection["Audience"] ?? "",
+ key: jwtSection["Key"] ?? "",
+ userId: user.Id,
+ email: user.Email,
+ firstname: user.FirstName,
+ lastname: user.LastName,
+ portraitUrl: user.ProfilePictureUrl);
return token;
}
diff --git a/src/Infrastructure/Identity/OwnedEntities/ProfileColors.cs b/src/Infrastructure/Identity/OwnedEntities/ProfileColors.cs
new file mode 100644
index 0000000..3783044
--- /dev/null
+++ b/src/Infrastructure/Identity/OwnedEntities/ProfileColors.cs
@@ -0,0 +1,9 @@
+namespace Hutopy.Infrastructure.Identity.OwnedEntities;
+
+public class ProfileColors
+{
+ 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;
+}
diff --git a/src/Infrastructure/Identity/OwnedEntities/SocialNetworks.cs b/src/Infrastructure/Identity/OwnedEntities/SocialNetworks.cs
index 1e0d30e..a03e139 100644
--- a/src/Infrastructure/Identity/OwnedEntities/SocialNetworks.cs
+++ b/src/Infrastructure/Identity/OwnedEntities/SocialNetworks.cs
@@ -2,12 +2,12 @@ namespace Hutopy.Infrastructure.Identity.OwnedEntities;
public class SocialNetworks
{
- 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;
+ 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;
}
diff --git a/src/Infrastructure/Identity/OwnedEntities/StoredDataUrls.cs b/src/Infrastructure/Identity/OwnedEntities/StoredDataUrls.cs
new file mode 100644
index 0000000..bea6099
--- /dev/null
+++ b/src/Infrastructure/Identity/OwnedEntities/StoredDataUrls.cs
@@ -0,0 +1,8 @@
+namespace Hutopy.Infrastructure.Identity.OwnedEntities;
+
+public class StoredDataUrls
+{
+ public string BannerPictureUrl { get; set; } = string.Empty;
+ public string ProfilePictureUrl { get; set; } = string.Empty;
+ public string WebsiteIconUrl { get; set; } = string.Empty;
+}
diff --git a/src/Infrastructure/Migrations/20240630001806_AddMissingInformationsToUser.Designer.cs b/src/Infrastructure/Migrations/20240630001806_AddMissingInformationsToUser.Designer.cs
new file mode 100644
index 0000000..2a9623e
--- /dev/null
+++ b/src/Infrastructure/Migrations/20240630001806_AddMissingInformationsToUser.Designer.cs
@@ -0,0 +1,497 @@
+//
+using System;
+using Hutopy.Infrastructure.Data;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Metadata;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+#nullable disable
+
+namespace Hutopy.Infrastructure.Migrations
+{
+ [DbContext(typeof(ApplicationDbContext))]
+ [Migration("20240630001806_AddMissingInformationsToUser")]
+ partial class AddMissingInformationsToUser
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "8.0.3")
+ .HasAnnotation("Relational:MaxIdentifierLength", 128);
+
+ SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
+
+ modelBuilder.Entity("Hutopy.Domain.Entities.FutureCreator", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("Created")
+ .HasColumnType("datetimeoffset");
+
+ b.Property("CreatedBy")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("EmailAddress")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("FirstName")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("LastModified")
+ .HasColumnType("datetimeoffset");
+
+ b.Property("LastModifiedBy")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("LastName")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("PhoneNumber")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("ReasonToJoin")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("SocialNetworkAccount")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.HasKey("Id");
+
+ b.ToTable("FutureCreators");
+ });
+
+ modelBuilder.Entity("Hutopy.Domain.Entities.UserTransaction", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("Amount")
+ .HasPrecision(18, 2)
+ .HasColumnType("decimal(18,2)");
+
+ b.Property("ApplicationUserId")
+ .IsRequired()
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("Created")
+ .HasColumnType("datetimeoffset");
+
+ b.Property("CreatedBy")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Currency")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("IsConfirmed")
+ .HasColumnType("bit");
+
+ b.Property("LastModified")
+ .HasColumnType("datetimeoffset");
+
+ b.Property("LastModifiedBy")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Paid")
+ .HasColumnType("bit");
+
+ b.Property("StripeBillingDetailEmail")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("StripeBillingDetailName")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("StripeChargeId")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("StripeEventId")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("StripePaymentIntent")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("StripePaymentMethod")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("StripeReceiptUrl")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("TipMessage")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ApplicationUserId");
+
+ b.ToTable("UserTransactions");
+ });
+
+ modelBuilder.Entity("Hutopy.Infrastructure.Identity.ApplicationUser", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("About")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("AccessFailedCount")
+ .HasColumnType("int");
+
+ b.Property("Address")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("BirthDate")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("City")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("ConcurrencyStamp")
+ .IsConcurrencyToken()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Country")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Description")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Email")
+ .HasMaxLength(256)
+ .HasColumnType("nvarchar(256)");
+
+ b.Property("EmailConfirmed")
+ .HasColumnType("bit");
+
+ b.Property("FirstName")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("LastName")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("LockoutEnabled")
+ .HasColumnType("bit");
+
+ b.Property("LockoutEnd")
+ .HasColumnType("datetimeoffset");
+
+ b.Property("NormalizedEmail")
+ .HasMaxLength(256)
+ .HasColumnType("nvarchar(256)");
+
+ b.Property("NormalizedUserName")
+ .HasMaxLength(256)
+ .HasColumnType("nvarchar(256)");
+
+ b.Property("Occupation")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("PasswordHash")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("PhoneNumber")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("PhoneNumberConfirmed")
+ .HasColumnType("bit");
+
+ b.Property("SecurityStamp")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("TwoFactorEnabled")
+ .HasColumnType("bit");
+
+ b.Property("UserName")
+ .HasMaxLength(256)
+ .HasColumnType("nvarchar(256)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("NormalizedEmail")
+ .HasDatabaseName("EmailIndex");
+
+ b.HasIndex("NormalizedUserName")
+ .IsUnique()
+ .HasDatabaseName("UserNameIndex")
+ .HasFilter("[NormalizedUserName] IS NOT NULL");
+
+ b.ToTable("AspNetUsers", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("ConcurrencyStamp")
+ .IsConcurrencyToken()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Name")
+ .HasMaxLength(256)
+ .HasColumnType("nvarchar(256)");
+
+ b.Property("NormalizedName")
+ .HasMaxLength(256)
+ .HasColumnType("nvarchar(256)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("NormalizedName")
+ .IsUnique()
+ .HasDatabaseName("RoleNameIndex")
+ .HasFilter("[NormalizedName] IS NOT NULL");
+
+ b.ToTable("AspNetRoles", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("ClaimType")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("ClaimValue")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("RoleId")
+ .IsRequired()
+ .HasColumnType("nvarchar(450)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("RoleId");
+
+ b.ToTable("AspNetRoleClaims", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("ClaimType")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("ClaimValue")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("UserId")
+ .IsRequired()
+ .HasColumnType("nvarchar(450)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("AspNetUserClaims", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b =>
+ {
+ b.Property("LoginProvider")
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("ProviderKey")
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("ProviderDisplayName")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("UserId")
+ .IsRequired()
+ .HasColumnType("nvarchar(450)");
+
+ b.HasKey("LoginProvider", "ProviderKey");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("AspNetUserLogins", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b =>
+ {
+ b.Property("UserId")
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("RoleId")
+ .HasColumnType("nvarchar(450)");
+
+ b.HasKey("UserId", "RoleId");
+
+ b.HasIndex("RoleId");
+
+ b.ToTable("AspNetUserRoles", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b =>
+ {
+ b.Property("UserId")
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("LoginProvider")
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("Name")
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("Value")
+ .HasColumnType("nvarchar(max)");
+
+ b.HasKey("UserId", "LoginProvider", "Name");
+
+ b.ToTable("AspNetUserTokens", (string)null);
+ });
+
+ modelBuilder.Entity("Hutopy.Domain.Entities.UserTransaction", b =>
+ {
+ b.HasOne("Hutopy.Infrastructure.Identity.ApplicationUser", null)
+ .WithMany()
+ .HasForeignKey("ApplicationUserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Hutopy.Infrastructure.Identity.ApplicationUser", b =>
+ {
+ b.OwnsOne("Hutopy.Infrastructure.Identity.OwnedEntities.SocialNetworks", "SocialNetworks", b1 =>
+ {
+ b1.Property("ApplicationUserId")
+ .HasColumnType("nvarchar(450)");
+
+ b1.Property("FacebookUrl")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b1.Property("InstagramUrl")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b1.Property("LinkedInUrl")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b1.Property("RedditUrl")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b1.Property("TikTokUrl")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b1.Property("XUrl")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b1.Property("YourWebsiteUrl")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b1.Property("YoutubeUrl")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b1.HasKey("ApplicationUserId");
+
+ b1.ToTable("ApplicationUser_SocialNetworks", (string)null);
+
+ b1.WithOwner()
+ .HasForeignKey("ApplicationUserId");
+ });
+
+ b.Navigation("SocialNetworks")
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b =>
+ {
+ b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
+ .WithMany()
+ .HasForeignKey("RoleId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b =>
+ {
+ b.HasOne("Hutopy.Infrastructure.Identity.ApplicationUser", null)
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b =>
+ {
+ b.HasOne("Hutopy.Infrastructure.Identity.ApplicationUser", null)
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b =>
+ {
+ b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
+ .WithMany()
+ .HasForeignKey("RoleId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("Hutopy.Infrastructure.Identity.ApplicationUser", null)
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b =>
+ {
+ b.HasOne("Hutopy.Infrastructure.Identity.ApplicationUser", null)
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/src/Infrastructure/Migrations/20240630001806_AddMissingInformationsToUser.cs b/src/Infrastructure/Migrations/20240630001806_AddMissingInformationsToUser.cs
new file mode 100644
index 0000000..cd1a612
--- /dev/null
+++ b/src/Infrastructure/Migrations/20240630001806_AddMissingInformationsToUser.cs
@@ -0,0 +1,95 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace Hutopy.Infrastructure.Migrations
+{
+ ///
+ public partial class AddMissingInformationsToUser : Migration
+ {
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.AddColumn(
+ name: "About",
+ table: "AspNetUsers",
+ type: "nvarchar(max)",
+ nullable: false,
+ defaultValue: "");
+
+ migrationBuilder.AddColumn(
+ name: "Address",
+ table: "AspNetUsers",
+ type: "nvarchar(max)",
+ nullable: false,
+ defaultValue: "");
+
+ migrationBuilder.AddColumn(
+ name: "BirthDate",
+ table: "AspNetUsers",
+ type: "nvarchar(max)",
+ nullable: false,
+ defaultValue: "");
+
+ migrationBuilder.AddColumn(
+ name: "City",
+ table: "AspNetUsers",
+ type: "nvarchar(max)",
+ nullable: false,
+ defaultValue: "");
+
+ migrationBuilder.AddColumn(
+ name: "Country",
+ table: "AspNetUsers",
+ type: "nvarchar(max)",
+ nullable: false,
+ defaultValue: "");
+
+ migrationBuilder.AddColumn(
+ name: "Description",
+ table: "AspNetUsers",
+ type: "nvarchar(max)",
+ nullable: false,
+ defaultValue: "");
+
+ migrationBuilder.AddColumn(
+ name: "Occupation",
+ table: "AspNetUsers",
+ type: "nvarchar(max)",
+ nullable: false,
+ defaultValue: "");
+ }
+
+ ///
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropColumn(
+ name: "About",
+ table: "AspNetUsers");
+
+ migrationBuilder.DropColumn(
+ name: "Address",
+ table: "AspNetUsers");
+
+ migrationBuilder.DropColumn(
+ name: "BirthDate",
+ table: "AspNetUsers");
+
+ migrationBuilder.DropColumn(
+ name: "City",
+ table: "AspNetUsers");
+
+ migrationBuilder.DropColumn(
+ name: "Country",
+ table: "AspNetUsers");
+
+ migrationBuilder.DropColumn(
+ name: "Description",
+ table: "AspNetUsers");
+
+ migrationBuilder.DropColumn(
+ name: "Occupation",
+ table: "AspNetUsers");
+ }
+ }
+}
diff --git a/src/Infrastructure/Migrations/20240630163057_AddMoreInformationsToUser.Designer.cs b/src/Infrastructure/Migrations/20240630163057_AddMoreInformationsToUser.Designer.cs
new file mode 100644
index 0000000..af10f04
--- /dev/null
+++ b/src/Infrastructure/Migrations/20240630163057_AddMoreInformationsToUser.Designer.cs
@@ -0,0 +1,557 @@
+//
+using System;
+using Hutopy.Infrastructure.Data;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Metadata;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+#nullable disable
+
+namespace Hutopy.Infrastructure.Migrations
+{
+ [DbContext(typeof(ApplicationDbContext))]
+ [Migration("20240630163057_AddMoreInformationsToUser")]
+ partial class AddMoreInformationsToUser
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "8.0.3")
+ .HasAnnotation("Relational:MaxIdentifierLength", 128);
+
+ SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
+
+ modelBuilder.Entity("Hutopy.Domain.Entities.FutureCreator", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("Created")
+ .HasColumnType("datetimeoffset");
+
+ b.Property("CreatedBy")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("EmailAddress")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("FirstName")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("LastModified")
+ .HasColumnType("datetimeoffset");
+
+ b.Property("LastModifiedBy")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("LastName")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("PhoneNumber")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("ReasonToJoin")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("SocialNetworkAccount")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.HasKey("Id");
+
+ b.ToTable("FutureCreators");
+ });
+
+ modelBuilder.Entity("Hutopy.Domain.Entities.UserTransaction", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("Amount")
+ .HasPrecision(18, 2)
+ .HasColumnType("decimal(18,2)");
+
+ b.Property("ApplicationUserId")
+ .IsRequired()
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("Created")
+ .HasColumnType("datetimeoffset");
+
+ b.Property("CreatedBy")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Currency")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("IsConfirmed")
+ .HasColumnType("bit");
+
+ b.Property("LastModified")
+ .HasColumnType("datetimeoffset");
+
+ b.Property("LastModifiedBy")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Paid")
+ .HasColumnType("bit");
+
+ b.Property("StripeBillingDetailEmail")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("StripeBillingDetailName")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("StripeChargeId")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("StripeEventId")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("StripePaymentIntent")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("StripePaymentMethod")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("StripeReceiptUrl")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("TipMessage")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ApplicationUserId");
+
+ b.ToTable("UserTransactions");
+ });
+
+ modelBuilder.Entity("Hutopy.Infrastructure.Identity.ApplicationUser", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("About")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("AccessFailedCount")
+ .HasColumnType("int");
+
+ b.Property("Address")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("BirthDate")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("City")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("ConcurrencyStamp")
+ .IsConcurrencyToken()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Country")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Description")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Email")
+ .HasMaxLength(256)
+ .HasColumnType("nvarchar(256)");
+
+ b.Property("EmailConfirmed")
+ .HasColumnType("bit");
+
+ b.Property("FirstName")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("LastName")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("LockoutEnabled")
+ .HasColumnType("bit");
+
+ b.Property("LockoutEnd")
+ .HasColumnType("datetimeoffset");
+
+ b.Property("NormalizedEmail")
+ .HasMaxLength(256)
+ .HasColumnType("nvarchar(256)");
+
+ b.Property("NormalizedUserName")
+ .HasMaxLength(256)
+ .HasColumnType("nvarchar(256)");
+
+ b.Property("Occupation")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("PasswordHash")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("PhoneNumber")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("PhoneNumberConfirmed")
+ .HasColumnType("bit");
+
+ b.Property("SecurityStamp")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("TwoFactorEnabled")
+ .HasColumnType("bit");
+
+ b.Property("UserName")
+ .HasMaxLength(256)
+ .HasColumnType("nvarchar(256)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("NormalizedEmail")
+ .HasDatabaseName("EmailIndex");
+
+ b.HasIndex("NormalizedUserName")
+ .IsUnique()
+ .HasDatabaseName("UserNameIndex")
+ .HasFilter("[NormalizedUserName] IS NOT NULL");
+
+ b.ToTable("AspNetUsers", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("ConcurrencyStamp")
+ .IsConcurrencyToken()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Name")
+ .HasMaxLength(256)
+ .HasColumnType("nvarchar(256)");
+
+ b.Property("NormalizedName")
+ .HasMaxLength(256)
+ .HasColumnType("nvarchar(256)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("NormalizedName")
+ .IsUnique()
+ .HasDatabaseName("RoleNameIndex")
+ .HasFilter("[NormalizedName] IS NOT NULL");
+
+ b.ToTable("AspNetRoles", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("ClaimType")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("ClaimValue")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("RoleId")
+ .IsRequired()
+ .HasColumnType("nvarchar(450)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("RoleId");
+
+ b.ToTable("AspNetRoleClaims", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("ClaimType")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("ClaimValue")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("UserId")
+ .IsRequired()
+ .HasColumnType("nvarchar(450)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("AspNetUserClaims", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b =>
+ {
+ b.Property("LoginProvider")
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("ProviderKey")
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("ProviderDisplayName")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("UserId")
+ .IsRequired()
+ .HasColumnType("nvarchar(450)");
+
+ b.HasKey("LoginProvider", "ProviderKey");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("AspNetUserLogins", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b =>
+ {
+ b.Property