Merge remote-tracking branch 'origin/feature/update-current-user' into feature/update-current-user

# Conflicts:
#	src/Application/Common/Interfaces/IIdentityService.cs
#	src/Application/Common/Models/Result.cs
#	src/Application/Common/Models/UserModel.cs
#	src/Application/Users/Commands/UpdateCurrentUserCommand.cs
#	src/Application/Users/Models/SocialNetworksModel.cs
#	src/Application/Users/Queries/GetCurrentUser/GetCurrentUser.cs
#	src/Application/Users/Queries/GetCurrentUser/UserDto.cs
#	src/Infrastructure/Identity/ApplicationUser.cs
#	src/Infrastructure/Identity/IdentityService.cs
#	src/Infrastructure/Utils/GenerateJwtToken.cs
#	src/Web/Controllers/GoogleController.cs
#	src/Web/Endpoints/UpdateMyUser.cs
This commit is contained in:
Dominic Villemure
2024-07-04 20:43:04 -04:00
10 changed files with 559 additions and 10 deletions

View File

@@ -12,7 +12,7 @@ public interface IIdentityService
Task<Result> UpdateCurrentUserBannerPictureUrlAsync(string url);
Task<Result> UpdateCurrentUserProfilePictureUrlAsync(string url);
Task<Result> UpdateCurrentUserWebsiteIconUrlAsync(string url);
Task<Result<string>> UpdateCurrentUserAsync(UserModel userModel);
Task<Result<string>> UpdateCurrentUserAsync(UserModel userModel, string? portraitUrl);
Task<IList<string>> GetCurrentUserRolesAsync();
Task<UserModel?> FindUserByIdAsync(string id);
Task<UserModel?> FindUserByEmailAsync(string email);

View File

@@ -1,4 +1,4 @@
using System.ComponentModel.DataAnnotations.Schema;
using System.ComponentModel.DataAnnotations.Schema;
using Hutopy.Application.Common.Interfaces;
using Hutopy.Application.Common.Models;
using Hutopy.Application.Users.Models;
@@ -19,6 +19,8 @@ public class UpdateCurrentUserCommand : IRequest<string>
public required string Description { get; init; }
public required SocialNetworksModel SocialNetworks { get; init; }
public required ProfileColorsModel ProfileColors { get; init; }
public string PortraitUrl { get; init; } = string.Empty;
[NotMapped]
private class Mapping : Profile
@@ -42,7 +44,7 @@ public class UpdateCurrentUserCommandHandler(IApplicationDbContext context, IIde
var userModel = mapper.Map<UserModel>(request);
userModel.Id = identityUser.Id;
var result = await identityService.UpdateCurrentUserAsync(userModel);
var result = await identityService.UpdateCurrentUserAsync(userModel, request.PortraitUrl);
await context.SaveChangesAsync(cancellationToken);

View File

@@ -68,7 +68,12 @@ public class ApplicationDbContextInitializer(
}
// Default users
var administrator = new ApplicationUser { UserName = "administrator@localhost", Email = "administrator@localhost" };
var administrator = new ApplicationUser
{
UserName = "administrator@localhost",
Email = "administrator@localhost",
PortraitUrl = "images/usersmedia/anonyme/profilepictures/profilePascal.jpg"
};
if (userManager.Users.All(u => u.UserName != administrator.UserName))
{

View File

@@ -17,4 +17,5 @@ public class ApplicationUser : IdentityUser
public SocialNetworks SocialNetworks { get; set; } = new();
public ProfileColors ProfileColors { get; set; } = new();
public StoredDataUrls StoredDataUrls { get; set; } = new();
public string? PortraitUrl { get; set; }
}

View File

@@ -88,7 +88,7 @@ public class IdentityService(
return result;
}
public async Task<Result<string>> UpdateCurrentUserAsync(UserModel userModel)
public async Task<Result<string>> UpdateCurrentUserAsync(UserModel userModel, string? portraitUrl)
{
var applicationUser = await userManager.FindByIdAsync(userModel.Id);
@@ -104,6 +104,7 @@ public class IdentityService(
applicationUser.Address = userModel.Address;
applicationUser.About = userModel.About;
applicationUser.Description = userModel.Description;
applicationUser.PortraitUrl = portraitUrl;
applicationUser.SocialNetworks = new SocialNetworks
{
FacebookUrl = userModel.SocialNetworks.FacebookUrl,
@@ -154,6 +155,7 @@ public class IdentityService(
Address = response.Address,
About = response.About,
Description = response.Description,
PortraitUrl = response.PortraitUrl,
SocialNetworks = new SocialNetworksModel
{
FacebookUrl = response.SocialNetworks.FacebookUrl,
@@ -385,7 +387,7 @@ public class IdentityService(
email: user.Email,
firstname: user.FirstName,
lastname: user.LastName,
portraitUrl: user.ProfilePictureUrl);
portraitUrl: user.PortraitUrl);
return token;
}

View File

@@ -0,0 +1,500 @@
// <auto-generated />
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("20240701072549_AddPortraitUrlToUser")]
partial class AddPortraitUrlToUser
{
/// <inheritdoc />
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<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<DateTimeOffset>("Created")
.HasColumnType("datetimeoffset");
b.Property<string>("CreatedBy")
.HasColumnType("nvarchar(max)");
b.Property<string>("EmailAddress")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("FirstName")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<DateTimeOffset>("LastModified")
.HasColumnType("datetimeoffset");
b.Property<string>("LastModifiedBy")
.HasColumnType("nvarchar(max)");
b.Property<string>("LastName")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("PhoneNumber")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("ReasonToJoin")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("SocialNetworkAccount")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.ToTable("FutureCreators");
});
modelBuilder.Entity("Hutopy.Domain.Entities.UserTransaction", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<decimal>("Amount")
.HasPrecision(18, 2)
.HasColumnType("decimal(18,2)");
b.Property<string>("ApplicationUserId")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.Property<DateTimeOffset>("Created")
.HasColumnType("datetimeoffset");
b.Property<string>("CreatedBy")
.HasColumnType("nvarchar(max)");
b.Property<string>("Currency")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<bool>("IsConfirmed")
.HasColumnType("bit");
b.Property<DateTimeOffset>("LastModified")
.HasColumnType("datetimeoffset");
b.Property<string>("LastModifiedBy")
.HasColumnType("nvarchar(max)");
b.Property<bool>("Paid")
.HasColumnType("bit");
b.Property<string>("StripeBillingDetailEmail")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("StripeBillingDetailName")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("StripeChargeId")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("StripeEventId")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("StripePaymentIntent")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("StripePaymentMethod")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("StripeReceiptUrl")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("TipMessage")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.HasIndex("ApplicationUserId");
b.ToTable("UserTransactions");
});
modelBuilder.Entity("Hutopy.Infrastructure.Identity.ApplicationUser", b =>
{
b.Property<string>("Id")
.HasColumnType("nvarchar(450)");
b.Property<string>("About")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<int>("AccessFailedCount")
.HasColumnType("int");
b.Property<string>("Address")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("BirthDate")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("City")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("nvarchar(max)");
b.Property<string>("Country")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("Description")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("Email")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<bool>("EmailConfirmed")
.HasColumnType("bit");
b.Property<string>("FirstName")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("LastName")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<bool>("LockoutEnabled")
.HasColumnType("bit");
b.Property<DateTimeOffset?>("LockoutEnd")
.HasColumnType("datetimeoffset");
b.Property<string>("NormalizedEmail")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("NormalizedUserName")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("Occupation")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("PasswordHash")
.HasColumnType("nvarchar(max)");
b.Property<string>("PhoneNumber")
.HasColumnType("nvarchar(max)");
b.Property<bool>("PhoneNumberConfirmed")
.HasColumnType("bit");
b.Property<string>("PortraitUrl")
.HasColumnType("nvarchar(max)");
b.Property<string>("SecurityStamp")
.HasColumnType("nvarchar(max)");
b.Property<bool>("TwoFactorEnabled")
.HasColumnType("bit");
b.Property<string>("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<string>("Id")
.HasColumnType("nvarchar(450)");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("nvarchar(max)");
b.Property<string>("Name")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("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<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("ClaimType")
.HasColumnType("nvarchar(max)");
b.Property<string>("ClaimValue")
.HasColumnType("nvarchar(max)");
b.Property<string>("RoleId")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("ClaimType")
.HasColumnType("nvarchar(max)");
b.Property<string>("ClaimValue")
.HasColumnType("nvarchar(max)");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.Property<string>("LoginProvider")
.HasColumnType("nvarchar(450)");
b.Property<string>("ProviderKey")
.HasColumnType("nvarchar(450)");
b.Property<string>("ProviderDisplayName")
.HasColumnType("nvarchar(max)");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("nvarchar(450)");
b.Property<string>("RoleId")
.HasColumnType("nvarchar(450)");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("nvarchar(450)");
b.Property<string>("LoginProvider")
.HasColumnType("nvarchar(450)");
b.Property<string>("Name")
.HasColumnType("nvarchar(450)");
b.Property<string>("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<string>("ApplicationUserId")
.HasColumnType("nvarchar(450)");
b1.Property<string>("FacebookUrl")
.IsRequired()
.HasColumnType("nvarchar(max)");
b1.Property<string>("InstagramUrl")
.IsRequired()
.HasColumnType("nvarchar(max)");
b1.Property<string>("LinkedInUrl")
.IsRequired()
.HasColumnType("nvarchar(max)");
b1.Property<string>("RedditUrl")
.IsRequired()
.HasColumnType("nvarchar(max)");
b1.Property<string>("TikTokUrl")
.IsRequired()
.HasColumnType("nvarchar(max)");
b1.Property<string>("XUrl")
.IsRequired()
.HasColumnType("nvarchar(max)");
b1.Property<string>("YourWebsiteUrl")
.IsRequired()
.HasColumnType("nvarchar(max)");
b1.Property<string>("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<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.HasOne("Hutopy.Infrastructure.Identity.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.HasOne("Hutopy.Infrastructure.Identity.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", 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<string>", b =>
{
b.HasOne("Hutopy.Infrastructure.Identity.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -0,0 +1,28 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Hutopy.Infrastructure.Migrations
{
/// <inheritdoc />
public partial class AddPortraitUrlToUser : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "PortraitUrl",
table: "AspNetUsers",
type: "nvarchar(max)",
nullable: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "PortraitUrl",
table: "AspNetUsers");
}
}
}

View File

@@ -222,6 +222,9 @@ namespace Hutopy.Infrastructure.Migrations
b.Property<bool>("PhoneNumberConfirmed")
.HasColumnType("bit");
b.Property<string>("PortraitUrl")
.HasColumnType("nvarchar(max)");
b.Property<string>("SecurityStamp")
.HasColumnType("nvarchar(max)");

View File

@@ -7,8 +7,15 @@ namespace Hutopy.Infrastructure.Utils;
public static class JwtTokenHelper
{
public static string GenerateJwtToken(string issuer, string audience, string key, string? userId, string? email,
string? firstname, string? lastname, string? portraitUrl)
public static string GenerateJwtToken(
string issuer,
string audience,
string key,
string? userId,
string? email,
string? firstname,
string? lastname,
string? portraitUrl)
{
var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key));
var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
@@ -19,6 +26,7 @@ public static class JwtTokenHelper
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(ClaimTypes.NameIdentifier, userId),
new Claim(ClaimTypes.Email, email),
new Claim(ClaimTypes.Name, email),
new Claim(ClaimTypes.GivenName, firstname),
new Claim(ClaimTypes.Surname, lastname),
});

View File

@@ -71,7 +71,7 @@ public class GoogleController(IIdentityService identityService, IHttpClientFacto
user.Email,
user.FirstName,
user.LastName,
user.ProfilePictureUrl);
user.PortraitUrl);
return Ok(new { accessToken = token, email });
}