From ffd8f43f3d66edc559deaa1c963665d5b861156c Mon Sep 17 00:00:00 2001 From: Karl Carriere Date: Wed, 18 Dec 2024 08:43:43 -0500 Subject: [PATCH 1/6] Added ChangeStripeId.cs --- .../Memberships/Handlers/ChangeStripeId.cs | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 src/Web/Features/Memberships/Handlers/ChangeStripeId.cs diff --git a/src/Web/Features/Memberships/Handlers/ChangeStripeId.cs b/src/Web/Features/Memberships/Handlers/ChangeStripeId.cs new file mode 100644 index 0000000..7008fee --- /dev/null +++ b/src/Web/Features/Memberships/Handlers/ChangeStripeId.cs @@ -0,0 +1,52 @@ +using Hutopy.Web.Common.Security; +using Hutopy.Web.Features.Memberships.Data; +using Hutopy.Web.Features.Memberships.Infrastructure; + +namespace Hutopy.Web.Features.Memberships.Handlers; + +[PublicAPI] +public record struct ChangeStripeIdRequest( + string StripeAccountId); + +public class ChangeStripeIdHandler( + MembershipDbContext dbContext, + StripeService stripeService) + : Endpoint +{ + public override void Configure() + { + Post("/api/creators/stripe-account"); + Options(o => o.WithTags("Memberships")); + } + + public override async Task HandleAsync( + ChangeStripeIdRequest req, + CancellationToken ct) + { + var creatorId = HttpContext.User.GetUserId(); + + var creator = await dbContext + .Creators + .FindAsync( + [creatorId], + cancellationToken: ct); + + if (creator is null) + { + creator = new Creator + { + Id = creatorId, + Name = HttpContext.User.GetAlias() ?? creatorId.ToString() + }; + + await dbContext.AddAsync(creator, ct); + } + + creator.StripeAccountId = req.StripeAccountId; + creator.PortraitUrl = HttpContext.User.GetPortraitUrl(); + + await dbContext.SaveChangesAsync(ct); + + await SendOkAsync(creator.Id, ct); + } +} From c447479362afa62b9e6522155c2f27eae5a79b9d Mon Sep 17 00:00:00 2001 From: Karl Carriere Date: Wed, 18 Dec 2024 08:44:10 -0500 Subject: [PATCH 2/6] Updated GetCurrentUser.cs to include StripeId --- src/Web/Features/Users/Handlers/GetCurrentUser.cs | 14 ++++++++++++-- src/Web/Features/Users/Handlers/Models/UserDto.cs | 1 + 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/Web/Features/Users/Handlers/GetCurrentUser.cs b/src/Web/Features/Users/Handlers/GetCurrentUser.cs index 2b9cc09..9bef3a2 100644 --- a/src/Web/Features/Users/Handlers/GetCurrentUser.cs +++ b/src/Web/Features/Users/Handlers/GetCurrentUser.cs @@ -1,16 +1,19 @@ using Hutopy.Web.Features.Users.Handlers.Models; +using Hutopy.Web.Features.Memberships.Data; +using Hutopy.Web.Features.Memberships.Infrastructure; namespace Hutopy.Web.Features.Users.Handlers; [PublicAPI] public class GetCurrentUserQueryHandler( - IdentityService identityService) + IdentityService identityService, + MembershipDbContext membershipDbContext) : EndpointWithoutRequest { public override void Configure() { Get("/api/users/profile"); - Options(o => o.WithTags("Users")); + Options(o => o.WithTags("Memberships")); } public override async Task HandleAsync( @@ -26,6 +29,12 @@ public class GetCurrentUserQueryHandler( var roles = await identityService.GetCurrentUserRolesAsync(); + var stripeId = await membershipDbContext + .Creators + .Where(c => c.Id == userModel.Id) + .Select(c => c.StripeAccountId) + .FirstOrDefaultAsync(cancellationToken); + await SendOkAsync( new UserDto { @@ -40,6 +49,7 @@ public class GetCurrentUserQueryHandler( BirthDate = userModel.BirthDate, Address = userModel.Address, UserRoles = roles, + StripeId = stripeId ?? string.Empty }, cancellationToken); } diff --git a/src/Web/Features/Users/Handlers/Models/UserDto.cs b/src/Web/Features/Users/Handlers/Models/UserDto.cs index b546f29..3295cac 100644 --- a/src/Web/Features/Users/Handlers/Models/UserDto.cs +++ b/src/Web/Features/Users/Handlers/Models/UserDto.cs @@ -13,4 +13,5 @@ public class UserDto public string? PhoneNumber { get; init; } public DateTime? BirthDate { get; init; } public string? Address { get; init; } + public string? StripeId { get; init; } } From 2c4f954af98be9e98c55dca66ccbdff73dbafcea Mon Sep 17 00:00:00 2001 From: Karl Carriere Date: Wed, 18 Dec 2024 08:44:50 -0500 Subject: [PATCH 3/6] Settings, migrations and updated TestDataSeeder.cs --- global.json | 4 +- .../20241216215210_UpdateSeedData.Designer.cs | 292 ++++++++++++++++++ .../20241216215210_UpdateSeedData.cs | 22 ++ .../20241217225954_ChangeStripeId.Designer.cs | 70 +++++ .../20241217225954_ChangeStripeId.cs | 38 +++ .../MessagingDbContextModelSnapshot.cs | 5 +- src/Web/TestDataSeeder.cs | 37 ++- 7 files changed, 454 insertions(+), 14 deletions(-) create mode 100644 src/Web/Features/Memberships/Data/Migrations/20241216215210_UpdateSeedData.Designer.cs create mode 100644 src/Web/Features/Memberships/Data/Migrations/20241216215210_UpdateSeedData.cs create mode 100644 src/Web/Features/Messages/Data/Migrations/20241217225954_ChangeStripeId.Designer.cs create mode 100644 src/Web/Features/Messages/Data/Migrations/20241217225954_ChangeStripeId.cs diff --git a/global.json b/global.json index b5b37b6..18b689d 100644 --- a/global.json +++ b/global.json @@ -1,7 +1,7 @@ { "sdk": { "version": "8.0.0", - "rollForward": "latestMajor", + "rollForward": "latestMinor", "allowPrerelease": false } -} \ No newline at end of file +} diff --git a/src/Web/Features/Memberships/Data/Migrations/20241216215210_UpdateSeedData.Designer.cs b/src/Web/Features/Memberships/Data/Migrations/20241216215210_UpdateSeedData.Designer.cs new file mode 100644 index 0000000..dc927cf --- /dev/null +++ b/src/Web/Features/Memberships/Data/Migrations/20241216215210_UpdateSeedData.Designer.cs @@ -0,0 +1,292 @@ +// +using System; +using Hutopy.Web.Features.Memberships.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Hutopy.Web.Features.Memberships.Data.Migrations +{ + [DbContext(typeof(MembershipDbContext))] + [Migration("20241216215210_UpdateSeedData")] + partial class UpdateSeedData + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasDefaultSchema("Membership") + .HasAnnotation("ProductVersion", "8.0.10") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Hutopy.Web.Features.Memberships.Data.Creator", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("PortraitUrl") + .IsRequired() + .HasColumnType("text"); + + b.Property("StripeAccountId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Creators", "Membership"); + }); + + modelBuilder.Entity("Hutopy.Web.Features.Memberships.Data.Subscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("CreatorId") + .HasColumnType("uuid"); + + b.Property("EndDate") + .HasColumnType("timestamp with time zone"); + + b.Property("StartDate") + .HasColumnType("timestamp with time zone"); + + b.Property("StripeSessionId") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("StripeSubscriptionId") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("TierId") + .HasColumnType("uuid"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("CreatorId"); + + b.HasIndex("TierId"); + + b.ToTable("Subscriptions", "Membership"); + }); + + modelBuilder.Entity("Hutopy.Web.Features.Memberships.Data.Tier", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("CreatorId") + .HasColumnType("uuid"); + + b.Property("CurrencyCode") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("character varying(4096)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("Price") + .HasColumnType("numeric"); + + b.Property("StripePriceId") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("StripeProductId") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.HasKey("Id"); + + b.HasIndex("CreatorId"); + + b.ToTable("Tiers", "Membership"); + }); + + modelBuilder.Entity("Hutopy.Web.Features.Memberships.Data.Tip", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Amount") + .HasColumnType("numeric"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("CreatorId") + .HasColumnType("uuid"); + + b.Property("CreatorName") + .IsRequired() + .HasColumnType("text"); + + b.Property("Currency") + .IsRequired() + .HasColumnType("text"); + + b.Property("Message") + .IsRequired() + .HasColumnType("text"); + + b.Property("StripeSessionId") + .IsRequired() + .HasColumnType("text"); + + b.Property("TipperId") + .HasColumnType("uuid"); + + b.Property("TipperName") + .IsRequired() + .HasColumnType("text"); + + b.Property("TransactionId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TransactionId"); + + b.ToTable("Tips", "Membership"); + }); + + modelBuilder.Entity("Hutopy.Web.Features.Memberships.Data.Transaction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Amount") + .HasColumnType("numeric"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("Currency") + .IsRequired() + .HasColumnType("text"); + + b.Property("StripeInvoiceUrl") + .HasColumnType("text"); + + b.Property("SubscriptionId") + .HasColumnType("uuid"); + + b.Property("Timestamp") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("SubscriptionId"); + + b.ToTable("Transactions", "Membership"); + }); + + modelBuilder.Entity("Hutopy.Web.Features.Memberships.Data.Subscription", b => + { + b.HasOne("Hutopy.Web.Features.Memberships.Data.Creator", "Creator") + .WithMany() + .HasForeignKey("CreatorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Hutopy.Web.Features.Memberships.Data.Tier", "Tier") + .WithMany("Subscriptions") + .HasForeignKey("TierId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("Tier"); + }); + + modelBuilder.Entity("Hutopy.Web.Features.Memberships.Data.Tier", b => + { + b.HasOne("Hutopy.Web.Features.Memberships.Data.Creator", "Creator") + .WithMany() + .HasForeignKey("CreatorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Creator"); + }); + + modelBuilder.Entity("Hutopy.Web.Features.Memberships.Data.Tip", b => + { + b.HasOne("Hutopy.Web.Features.Memberships.Data.Transaction", "Transaction") + .WithMany() + .HasForeignKey("TransactionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Transaction"); + }); + + modelBuilder.Entity("Hutopy.Web.Features.Memberships.Data.Transaction", b => + { + b.HasOne("Hutopy.Web.Features.Memberships.Data.Subscription", null) + .WithMany("Transactions") + .HasForeignKey("SubscriptionId"); + }); + + modelBuilder.Entity("Hutopy.Web.Features.Memberships.Data.Subscription", b => + { + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Hutopy.Web.Features.Memberships.Data.Tier", b => + { + b.Navigation("Subscriptions"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Web/Features/Memberships/Data/Migrations/20241216215210_UpdateSeedData.cs b/src/Web/Features/Memberships/Data/Migrations/20241216215210_UpdateSeedData.cs new file mode 100644 index 0000000..cb5b667 --- /dev/null +++ b/src/Web/Features/Memberships/Data/Migrations/20241216215210_UpdateSeedData.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Hutopy.Web.Features.Memberships.Data.Migrations +{ + /// + public partial class UpdateSeedData : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + + } + } +} diff --git a/src/Web/Features/Messages/Data/Migrations/20241217225954_ChangeStripeId.Designer.cs b/src/Web/Features/Messages/Data/Migrations/20241217225954_ChangeStripeId.Designer.cs new file mode 100644 index 0000000..ed928e5 --- /dev/null +++ b/src/Web/Features/Messages/Data/Migrations/20241217225954_ChangeStripeId.Designer.cs @@ -0,0 +1,70 @@ +// +using System; +using Hutopy.Web.Features.Messages.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Hutopy.Web.Features.Messages.Migrations +{ + [DbContext(typeof(MessagingDbContext))] + [Migration("20241217225954_ChangeStripeId")] + partial class ChangeStripeId + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasDefaultSchema("Messaging") + .HasAnnotation("ProductVersion", "8.0.10") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Hutopy.Web.Features.Messages.Data.Message", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedByName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("CreatedByPortraitUrl") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("ParentId") + .HasColumnType("uuid"); + + b.Property("SubjectId") + .HasColumnType("uuid"); + + b.Property("Value") + .IsRequired() + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.HasKey("Id"); + + b.ToTable("Messages", "Messaging"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Web/Features/Messages/Data/Migrations/20241217225954_ChangeStripeId.cs b/src/Web/Features/Messages/Data/Migrations/20241217225954_ChangeStripeId.cs new file mode 100644 index 0000000..44a9df9 --- /dev/null +++ b/src/Web/Features/Messages/Data/Migrations/20241217225954_ChangeStripeId.cs @@ -0,0 +1,38 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Hutopy.Web.Features.Messages.Migrations +{ + /// + public partial class ChangeStripeId : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "Value", + schema: "Messaging", + table: "Messages", + type: "character varying(2048)", + maxLength: 2048, + nullable: false, + oldClrType: typeof(string), + oldType: "text"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "Value", + schema: "Messaging", + table: "Messages", + type: "text", + nullable: false, + oldClrType: typeof(string), + oldType: "character varying(2048)", + oldMaxLength: 2048); + } + } +} diff --git a/src/Web/Features/Messages/Data/Migrations/MessagingDbContextModelSnapshot.cs b/src/Web/Features/Messages/Data/Migrations/MessagingDbContextModelSnapshot.cs index b1848d8..14c77a2 100644 --- a/src/Web/Features/Messages/Data/Migrations/MessagingDbContextModelSnapshot.cs +++ b/src/Web/Features/Messages/Data/Migrations/MessagingDbContextModelSnapshot.cs @@ -18,7 +18,7 @@ namespace Hutopy.Web.Features.Messages.Migrations #pragma warning disable 612, 618 modelBuilder .HasDefaultSchema("Messaging") - .HasAnnotation("ProductVersion", "8.0.4") + .HasAnnotation("ProductVersion", "8.0.10") .HasAnnotation("Relational:MaxIdentifierLength", 63); NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); @@ -54,7 +54,8 @@ namespace Hutopy.Web.Features.Messages.Migrations b.Property("Value") .IsRequired() - .HasColumnType("text"); + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); b.HasKey("Id"); diff --git a/src/Web/TestDataSeeder.cs b/src/Web/TestDataSeeder.cs index 5b0bef7..c4655f8 100644 --- a/src/Web/TestDataSeeder.cs +++ b/src/Web/TestDataSeeder.cs @@ -1,5 +1,8 @@ using Hutopy.Web.Common; using Hutopy.Web.Features.Contents.Data; +using ContentCreator = Hutopy.Web.Features.Contents.Data.Creator; +using Hutopy.Web.Features.Memberships.Data; +using MembershipCreator = Hutopy.Web.Features.Memberships.Data.Creator; using Hutopy.Web.Features.Messages.Data; using Hutopy.Web.Features.Users; @@ -16,7 +19,8 @@ public static class WebApplicationExtensions var seeder = new TestDataSeeder( scope.ServiceProvider.GetRequiredService(), scope.ServiceProvider.GetRequiredService(), - scope.ServiceProvider.GetRequiredService()); + scope.ServiceProvider.GetRequiredService(), + scope.ServiceProvider.GetRequiredService()); await seeder.SeedAsync(); } @@ -25,7 +29,8 @@ public static class WebApplicationExtensions internal class TestDataSeeder( ApplicationUserManager userManager, ContentDbContext contentContext, - MessagingDbContext messagingContext) + MessagingDbContext messagingContext, + MembershipDbContext membershipContext) { private const string DefaultPassword = "Test123#"; @@ -65,11 +70,23 @@ internal class TestDataSeeder( await messagingContext.SaveChangesAsync(); } + + // convert to MembershipCreator + var membershipCreator = new MembershipCreator + { + Id = creator.Id, + Name = creator.Name, + StripeAccountId = Guid.NewGuid().ToString(), + PortraitUrl = creator.Images.Logo, + }; + + await membershipContext.Creators.AddAsync(membershipCreator); + await membershipContext.SaveChangesAsync(); } } private List GenerateContent( - Creator creator, + ContentCreator creator, int contentCount) { var currentDate = DateTimeOffset.UtcNow; @@ -191,7 +208,7 @@ internal class TestDataSeeder( [ ]; - private readonly static Creator HutopyCreator = new() + private readonly static ContentCreator HutopyCreator = new() { Name = "hutopy", Title = "Page officielle", @@ -222,7 +239,7 @@ internal class TestDataSeeder( } }; - private readonly static Creator ArpsCreator = new() + private readonly static ContentCreator ArpsCreator = new() { Name = "arps", Title = "Créateur de contenu", @@ -255,7 +272,7 @@ internal class TestDataSeeder( } }; - private readonly static Creator ChloeBeaugrandCreator = new() + private readonly static ContentCreator ChloeBeaugrandCreator = new() { Name = "chloebeaugrand", Title = "Page officielle", @@ -285,7 +302,7 @@ internal class TestDataSeeder( } }; - private readonly static Creator GuillaumeMCreator = new() + private readonly static ContentCreator GuillaumeMCreator = new() { Name = "guillaumem", Title = "Page officielle", @@ -317,7 +334,7 @@ internal class TestDataSeeder( } }; - private readonly static Creator LeffetCreator = new() + private readonly static ContentCreator LeffetCreator = new() { Name = "leffet", Title = "Page officielle", @@ -348,7 +365,7 @@ internal class TestDataSeeder( } }; - private readonly static Creator MathieuCaron = new() + private readonly static ContentCreator MathieuCaron = new() { Name = "mathieucaron", Title = "Page officielle", @@ -378,7 +395,7 @@ internal class TestDataSeeder( } }; - private readonly Creator[] _creators = + private readonly ContentCreator[] _creators = [ HutopyCreator, ArpsCreator, From 7ac5b7315b8f6575d00f2c689ce42692ba7d9690 Mon Sep 17 00:00:00 2001 From: PascalMarchesseault <97350299+PascalMarchesseault@users.noreply.github.com> Date: Mon, 30 Dec 2024 23:32:15 -0500 Subject: [PATCH 4/6] =?UTF-8?q?Wip=20fonctionnement=20upload=20image=20Pr?= =?UTF-8?q?=C3=A9sentation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Handlers/ChangePresentationInfos.cs | 81 ++++++++++++------- 1 file changed, 53 insertions(+), 28 deletions(-) diff --git a/src/Web/Features/Contents/Handlers/ChangePresentationInfos.cs b/src/Web/Features/Contents/Handlers/ChangePresentationInfos.cs index d8b2e79..0136fd1 100644 --- a/src/Web/Features/Contents/Handlers/ChangePresentationInfos.cs +++ b/src/Web/Features/Contents/Handlers/ChangePresentationInfos.cs @@ -1,4 +1,5 @@ -using Hutopy.Web.Features.Contents.Data; +using Hutopy.Web.Common.BlobStorage; +using Hutopy.Web.Features.Contents.Data; namespace Hutopy.Web.Features.Contents.Handlers; @@ -8,29 +9,32 @@ public record ChangePresentationInfosRequest( string? PhoneNumber, string? Email, string? Title, - string? MainImageUrl, string? MainImageText, string? MainVideoText, string? ImagesSubtitle, - string? Image1Url, - string? Image2Url, - string? Image3Url, - string? Image4Url, string? ImagesText, string? VideoSubtitle, string? VideoSubtitleMain, string? VideoUrlMain, string? VideoUrl, - string? VideoText); + string? VideoText, + IFormFile? MainImage, + IFormFile? Image1, + IFormFile? Image2, + IFormFile? Image3, + IFormFile? Image4); +[PublicAPI] public class ChangePresentationInfosHandler( - ContentDbContext context) + ContentDbContext context, + AzureBlobStorage blobStorage) : Endpoint { public override void Configure() { Post("/api/creators/{CreatorId}/presentation-infos"); Options(o => o.WithTags("Creators")); + AllowFileUploads(); } public override async Task HandleAsync( @@ -40,30 +44,51 @@ public class ChangePresentationInfosHandler( var creator = await context .Creators .Include(c => c.PresentationInfos) - .SingleAsync( + .SingleOrDefaultAsync( c => c.Id == request.CreatorId, cancellationToken: ct); - creator.PresentationInfos.PhoneNumber = request.PhoneNumber ?? ""; - creator.PresentationInfos.Email = request.Email ?? ""; - creator.PresentationInfos.Title = request.Title ?? ""; - creator.PresentationInfos.MainImageUrl = request.MainImageUrl ?? ""; - creator.PresentationInfos.MainImageText = request.MainImageText ?? ""; - creator.PresentationInfos.MainVideoText = request.MainVideoText ?? ""; - creator.PresentationInfos.ImagesSubtitle = request.ImagesSubtitle ?? ""; - creator.PresentationInfos.Image1Url = request.Image1Url ?? ""; - creator.PresentationInfos.Image2Url = request.Image2Url ?? ""; - creator.PresentationInfos.Image3Url = request.Image3Url ?? ""; - creator.PresentationInfos.Image4Url = request.Image4Url ?? ""; - creator.PresentationInfos.ImagesText = request.ImagesText ?? ""; - creator.PresentationInfos.VideoSubtitle = request.VideoSubtitle ?? ""; - creator.PresentationInfos.VideoSubtitleMain = request.VideoSubtitleMain ?? ""; - creator.PresentationInfos.VideoUrlMain = request.VideoUrlMain ?? ""; - creator.PresentationInfos.VideoUrl = request.VideoUrl ?? ""; - creator.PresentationInfos.VideoText = request.VideoText ?? ""; - - await context.SaveChangesAsync(ct); + if (creator is null) + { + await SendNotFoundAsync(ct); + return; + } + // Upload images if provided and update URLs + async Task UploadFileAsync(IFormFile? file, string subDirectory, string fileName) + { + if (file is null) + return null; + + return await blobStorage.UploadFileAsync( + ContainerNames.Creators, + $"{request.CreatorId}/{subDirectory}/{fileName}", + file.OpenReadStream(), + file.ContentType, + ct); + } + + creator.PresentationInfos.MainImageUrl = await UploadFileAsync(request.MainImage, "Profile", "MainImage") ?? creator.PresentationInfos.MainImageUrl; + creator.PresentationInfos.Image1Url = await UploadFileAsync(request.Image1, "Profile", "Image1") ?? creator.PresentationInfos.Image1Url; + creator.PresentationInfos.Image2Url = await UploadFileAsync(request.Image2, "Profile", "Image2") ?? creator.PresentationInfos.Image2Url; + creator.PresentationInfos.Image3Url = await UploadFileAsync(request.Image3, "Profile", "Image3") ?? creator.PresentationInfos.Image3Url; + creator.PresentationInfos.Image4Url = await UploadFileAsync(request.Image4, "Profile", "Image4") ?? creator.PresentationInfos.Image4Url; + + // Update other fields + creator.PresentationInfos.PhoneNumber = request.PhoneNumber ?? creator.PresentationInfos.PhoneNumber; + creator.PresentationInfos.Email = request.Email ?? creator.PresentationInfos.Email; + creator.PresentationInfos.Title = request.Title ?? creator.PresentationInfos.Title; + creator.PresentationInfos.MainImageText = request.MainImageText ?? creator.PresentationInfos.MainImageText; + creator.PresentationInfos.MainVideoText = request.MainVideoText ?? creator.PresentationInfos.MainVideoText; + creator.PresentationInfos.ImagesSubtitle = request.ImagesSubtitle ?? creator.PresentationInfos.ImagesSubtitle; + creator.PresentationInfos.ImagesText = request.ImagesText ?? creator.PresentationInfos.ImagesText; + creator.PresentationInfos.VideoSubtitle = request.VideoSubtitle ?? creator.PresentationInfos.VideoSubtitle; + creator.PresentationInfos.VideoSubtitleMain = request.VideoSubtitleMain ?? creator.PresentationInfos.VideoSubtitleMain; + creator.PresentationInfos.VideoUrlMain = request.VideoUrlMain ?? creator.PresentationInfos.VideoUrlMain; + creator.PresentationInfos.VideoUrl = request.VideoUrl ?? creator.PresentationInfos.VideoUrl; + creator.PresentationInfos.VideoText = request.VideoText ?? creator.PresentationInfos.VideoText; + + await context.SaveChangesAsync(ct); await SendOkAsync(ct); } } From 6b0cc416a9c2a00bc305f89fca43ae790177739a Mon Sep 17 00:00:00 2001 From: PascalMarchesseault <97350299+PascalMarchesseault@users.noreply.github.com> Date: Tue, 31 Dec 2024 18:10:11 -0500 Subject: [PATCH 5/6] ChangePresentationInfos - upload delete image and text --- src/Web/Features/Contents/Data/Creator.cs | 10 +-- .../Handlers/ChangePresentationInfos.cs | 77 ++++++++++++------- 2 files changed, 54 insertions(+), 33 deletions(-) diff --git a/src/Web/Features/Contents/Data/Creator.cs b/src/Web/Features/Contents/Data/Creator.cs index fd411ed..dad01ac 100644 --- a/src/Web/Features/Contents/Data/Creator.cs +++ b/src/Web/Features/Contents/Data/Creator.cs @@ -52,14 +52,14 @@ public class PresentationInfos [MaxLength(255)] public string PhoneNumber { get; set; } = string.Empty; [MaxLength(255)] public string Email { get; set; } = string.Empty; [MaxLength(2000)] public string Title { get; set; } = string.Empty; - [MaxLength(2000)] public string MainImageUrl { get; set; } = string.Empty; + [MaxLength(2000)] public string? MainImageUrl { get; set; } = string.Empty; [MaxLength(10000)] public string MainImageText { get; set; } = string.Empty; [MaxLength(10000)] public string MainVideoText { get; set; } = string.Empty; [MaxLength(2000)] public string ImagesSubtitle { get; set; } = string.Empty; - [MaxLength(2000)] public string Image1Url { get; set; } = string.Empty; - [MaxLength(2000)] public string Image2Url { get; set; } = string.Empty; - [MaxLength(2000)] public string Image3Url { get; set; } = string.Empty; - [MaxLength(2000)] public string Image4Url { get; set; } = string.Empty; + [MaxLength(2000)] public string? Image1Url { get; set; } = string.Empty; + [MaxLength(2000)] public string? Image2Url { get; set; } = string.Empty; + [MaxLength(2000)] public string? Image3Url { get; set; } = string.Empty; + [MaxLength(2000)] public string? Image4Url { get; set; } = string.Empty; [MaxLength(10000)] public string ImagesText { get; set; } = string.Empty; [MaxLength(2000)] public string VideoSubtitle { get; set; } = string.Empty; [MaxLength(2000)] public string VideoSubtitleMain { get; set; } = string.Empty; diff --git a/src/Web/Features/Contents/Handlers/ChangePresentationInfos.cs b/src/Web/Features/Contents/Handlers/ChangePresentationInfos.cs index 0136fd1..8573bf7 100644 --- a/src/Web/Features/Contents/Handlers/ChangePresentationInfos.cs +++ b/src/Web/Features/Contents/Handlers/ChangePresentationInfos.cs @@ -18,6 +18,11 @@ public record ChangePresentationInfosRequest( string? VideoUrlMain, string? VideoUrl, string? VideoText, + string? MainImageUrl, + string? Image1Url, + string? Image2Url, + string? Image3Url, + string? Image4Url, IFormFile? MainImage, IFormFile? Image1, IFormFile? Image2, @@ -54,39 +59,55 @@ public class ChangePresentationInfosHandler( return; } - // Upload images if provided and update URLs - async Task UploadFileAsync(IFormFile? file, string subDirectory, string fileName) + async Task UploadFileOrDefaultAsync( + IFormFile? file, + string subDirectory, + string fileName, + string? newUrl) { - if (file is null) - return null; + if (newUrl == "") + return ""; - return await blobStorage.UploadFileAsync( - ContainerNames.Creators, - $"{request.CreatorId}/{subDirectory}/{fileName}", - file.OpenReadStream(), - file.ContentType, - ct); + if (file != null) + { + return await blobStorage.UploadFileAsync( + ContainerNames.Creators, + $"{request.CreatorId}/{subDirectory}/{fileName}", + file.OpenReadStream(), + file.ContentType, + ct); + } + + return newUrl?.Trim() ?? ""; } - creator.PresentationInfos.MainImageUrl = await UploadFileAsync(request.MainImage, "Profile", "MainImage") ?? creator.PresentationInfos.MainImageUrl; - creator.PresentationInfos.Image1Url = await UploadFileAsync(request.Image1, "Profile", "Image1") ?? creator.PresentationInfos.Image1Url; - creator.PresentationInfos.Image2Url = await UploadFileAsync(request.Image2, "Profile", "Image2") ?? creator.PresentationInfos.Image2Url; - creator.PresentationInfos.Image3Url = await UploadFileAsync(request.Image3, "Profile", "Image3") ?? creator.PresentationInfos.Image3Url; - creator.PresentationInfos.Image4Url = await UploadFileAsync(request.Image4, "Profile", "Image4") ?? creator.PresentationInfos.Image4Url; + creator.PresentationInfos.MainImageUrl = await UploadFileOrDefaultAsync( + request.MainImage, "Profile", "MainImage", request.MainImageUrl); - // Update other fields - creator.PresentationInfos.PhoneNumber = request.PhoneNumber ?? creator.PresentationInfos.PhoneNumber; - creator.PresentationInfos.Email = request.Email ?? creator.PresentationInfos.Email; - creator.PresentationInfos.Title = request.Title ?? creator.PresentationInfos.Title; - creator.PresentationInfos.MainImageText = request.MainImageText ?? creator.PresentationInfos.MainImageText; - creator.PresentationInfos.MainVideoText = request.MainVideoText ?? creator.PresentationInfos.MainVideoText; - creator.PresentationInfos.ImagesSubtitle = request.ImagesSubtitle ?? creator.PresentationInfos.ImagesSubtitle; - creator.PresentationInfos.ImagesText = request.ImagesText ?? creator.PresentationInfos.ImagesText; - creator.PresentationInfos.VideoSubtitle = request.VideoSubtitle ?? creator.PresentationInfos.VideoSubtitle; - creator.PresentationInfos.VideoSubtitleMain = request.VideoSubtitleMain ?? creator.PresentationInfos.VideoSubtitleMain; - creator.PresentationInfos.VideoUrlMain = request.VideoUrlMain ?? creator.PresentationInfos.VideoUrlMain; - creator.PresentationInfos.VideoUrl = request.VideoUrl ?? creator.PresentationInfos.VideoUrl; - creator.PresentationInfos.VideoText = request.VideoText ?? creator.PresentationInfos.VideoText; + creator.PresentationInfos.Image1Url = await UploadFileOrDefaultAsync( + request.Image1, "Profile", "Image1", request.Image1Url); + + creator.PresentationInfos.Image2Url = await UploadFileOrDefaultAsync( + request.Image2, "Profile", "Image2", request.Image2Url); + + creator.PresentationInfos.Image3Url = await UploadFileOrDefaultAsync( + request.Image3, "Profile", "Image3", request.Image3Url); + + creator.PresentationInfos.Image4Url = await UploadFileOrDefaultAsync( + request.Image4, "Profile", "Image4", request.Image4Url); + + creator.PresentationInfos.PhoneNumber = request.PhoneNumber?.Trim() ?? ""; + creator.PresentationInfos.Email = request.Email?.Trim() ?? ""; + creator.PresentationInfos.Title = request.Title?.Trim() ?? ""; + creator.PresentationInfos.MainImageText = request.MainImageText?.Trim() ?? ""; + creator.PresentationInfos.MainVideoText = request.MainVideoText?.Trim() ?? ""; + creator.PresentationInfos.ImagesSubtitle = request.ImagesSubtitle?.Trim() ?? ""; + creator.PresentationInfos.ImagesText = request.ImagesText?.Trim() ?? ""; + creator.PresentationInfos.VideoSubtitle = request.VideoSubtitle?.Trim() ?? ""; + creator.PresentationInfos.VideoSubtitleMain = request.VideoSubtitleMain?.Trim() ?? ""; + creator.PresentationInfos.VideoUrlMain = request.VideoUrlMain?.Trim() ?? ""; + creator.PresentationInfos.VideoUrl = request.VideoUrl?.Trim() ?? ""; + creator.PresentationInfos.VideoText = request.VideoText?.Trim() ?? ""; await context.SaveChangesAsync(ct); await SendOkAsync(ct); From a9c015e82d6f5ca690b8870af5aaaa9ab71191a1 Mon Sep 17 00:00:00 2001 From: Karl Carriere Date: Mon, 6 Jan 2025 15:18:33 -0500 Subject: [PATCH 6/6] Removed need for users to be signed in to tip --- src/Web/Features/Memberships/Handlers/SendTip.cs | 7 ++++--- .../Features/Memberships/Infrastructure/StripeService.cs | 9 +-------- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/src/Web/Features/Memberships/Handlers/SendTip.cs b/src/Web/Features/Memberships/Handlers/SendTip.cs index 3c80edb..b845ca9 100644 --- a/src/Web/Features/Memberships/Handlers/SendTip.cs +++ b/src/Web/Features/Memberships/Handlers/SendTip.cs @@ -51,6 +51,8 @@ public class SendTipHandler( { Post("/api/tips"); Options(o => o.WithTags("Memberships")); + + AllowAnonymous(); } public override async Task HandleAsync( @@ -68,8 +70,6 @@ public class SendTipHandler( } var checkoutSession = await stripeService.CreateTipCheckoutSessionAsync( - User.GetUserId(), - User.GetAlias()!, creator.Id, creator.Name, req.Amount, @@ -77,7 +77,8 @@ public class SendTipHandler( req.Message, creator.StripeAccountId, req.CheckoutSuccessUrl, - req.CheckoutCancelledUrl); + req.CheckoutCancelledUrl + ); await SendAsync( new SendTipResponse("Pending", checkoutSession.Url), diff --git a/src/Web/Features/Memberships/Infrastructure/StripeService.cs b/src/Web/Features/Memberships/Infrastructure/StripeService.cs index 0a69a60..c1db0ec 100644 --- a/src/Web/Features/Memberships/Infrastructure/StripeService.cs +++ b/src/Web/Features/Memberships/Infrastructure/StripeService.cs @@ -55,8 +55,6 @@ public sealed class StripeService( } public async Task CreateTipCheckoutSessionAsync( - Guid tipperId, - string tipperName, Guid creatorId, string creatorName, decimal amount, @@ -72,10 +70,7 @@ public sealed class StripeService( // Create Stripe customer for the user if not already created var customerService = new CustomerService(); var customer = await customerService.CreateAsync( - new CustomerCreateOptions - { - Metadata = new Dictionary { { "userId", tipperId.ToString() } } - }, + new CustomerCreateOptions{}, cancellationToken: ct); // Create paymentIntent for the user @@ -116,8 +111,6 @@ public sealed class StripeService( CancelUrl = cancelUrl, // Redirect after canceled payment Metadata = new Dictionary { - { "tipperId", tipperId.ToString() }, - { "tipperName", tipperName }, { "creatorId", creatorId.ToString() }, { "creatorName", creatorName }, { "message", message },