From ede5483bbf6b29bc9db541b9902e7518c81e1433 Mon Sep 17 00:00:00 2001 From: Jonathan Bourdon Date: Wed, 8 Jan 2025 21:22:00 -0500 Subject: [PATCH] Adds AcceptDonation to Creator. Sync Content's Creator to Membership's Creator --- src/Web/Features/Contents/Data/Creator.cs | 1 + ...56_Adds_AcceptDonation_Creator.Designer.cs | 406 ++++++++++++++++++ ...50109015556_Adds_AcceptDonation_Creator.cs | 31 ++ .../ContentDbContextModelSnapshot.cs | 3 + .../StripeAccountConfiguredHandler.cs | 43 ++ .../Contents/Handlers/GetCreatorByAlias.cs | 2 + src/Web/Features/Memberships/Data/Creator.cs | 2 +- .../Events/StripeAccountConfigured.cs | 5 + .../Memberships/Handlers/ChangeStripeId.cs | 15 +- .../Handlers/ConfigureStripeAccount.cs | 52 --- 10 files changed, 501 insertions(+), 59 deletions(-) create mode 100644 src/Web/Features/Contents/Data/Migrations/20250109015556_Adds_AcceptDonation_Creator.Designer.cs create mode 100644 src/Web/Features/Contents/Data/Migrations/20250109015556_Adds_AcceptDonation_Creator.cs create mode 100644 src/Web/Features/Contents/EventHandlers/StripeAccountConfiguredHandler.cs create mode 100644 src/Web/Features/Memberships/Events/StripeAccountConfigured.cs delete mode 100644 src/Web/Features/Memberships/Handlers/ConfigureStripeAccount.cs diff --git a/src/Web/Features/Contents/Data/Creator.cs b/src/Web/Features/Contents/Data/Creator.cs index 8fad026..496a4dc 100644 --- a/src/Web/Features/Contents/Data/Creator.cs +++ b/src/Web/Features/Contents/Data/Creator.cs @@ -7,6 +7,7 @@ public class Creator public Guid Id { get; set; } public Guid CreatedBy { get; set; } public DateTimeOffset CreatedAt { get; init; } + public bool AcceptDonation { get; set; } public bool Verified { get; set; } [MaxLength(255)] public string Name { get; set; } = null!; [MaxLength(255)] public string NormalizedName { get; set; } = null!; diff --git a/src/Web/Features/Contents/Data/Migrations/20250109015556_Adds_AcceptDonation_Creator.Designer.cs b/src/Web/Features/Contents/Data/Migrations/20250109015556_Adds_AcceptDonation_Creator.Designer.cs new file mode 100644 index 0000000..1a12506 --- /dev/null +++ b/src/Web/Features/Contents/Data/Migrations/20250109015556_Adds_AcceptDonation_Creator.Designer.cs @@ -0,0 +1,406 @@ +// +using System; +using Hutopy.Web.Features.Contents.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.Contents.Data.Migrations +{ + [DbContext(typeof(ContentDbContext))] + [Migration("20250109015556_Adds_AcceptDonation_Creator")] + partial class Adds_AcceptDonation_Creator + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasDefaultSchema("Content") + .HasAnnotation("ProductVersion", "8.0.10") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.Content", 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("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedBy") + .HasColumnType("uuid"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.Property("HtmlFileUrl") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.Property("ThumbnailUrl") + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("Urls") + .HasColumnType("text[]"); + + b.HasKey("Id"); + + b.HasIndex("CreatedBy"); + + b.ToTable("Contents", "Content"); + }); + + modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.Creator", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AcceptDonation") + .HasColumnType("boolean"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("NormalizedName") + .IsRequired() + .ValueGeneratedOnAddOrUpdate() + .HasMaxLength(255) + .HasColumnType("character varying(255)") + .HasComputedColumnSql("LOWER( \"Content\".\"Creators\".\"Name\")", true); + + b.Property("Title") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Verified") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique(); + + b.ToTable("Creators", "Content"); + }); + + modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.Content", b => + { + b.HasOne("Hutopy.Web.Features.Contents.Data.Creator", "Creator") + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.OwnsMany("Hutopy.Web.Features.Contents.Data.ContentReaction", "Reactions", b1 => + { + b1.Property("ContentId") + .HasColumnType("uuid"); + + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b1.Property("Id")); + + b1.Property("Reaction") + .HasColumnType("integer"); + + b1.Property("UserId") + .HasColumnType("uuid"); + + b1.Property("UserName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b1.HasKey("ContentId", "Id"); + + b1.ToTable("Reactions", "Content"); + + b1.WithOwner() + .HasForeignKey("ContentId"); + }); + + b.Navigation("Creator"); + + b.Navigation("Reactions"); + }); + + modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.Creator", b => + { + b.OwnsOne("Hutopy.Web.Features.Contents.Data.Colors", "Colors", b1 => + { + b1.Property("CreatorId") + .HasColumnType("uuid"); + + b1.Property("Background") + .IsRequired() + .HasMaxLength(9) + .HasColumnType("character varying(9)"); + + b1.Property("Error") + .IsRequired() + .HasMaxLength(9) + .HasColumnType("character varying(9)"); + + b1.Property("OnBackground") + .IsRequired() + .HasMaxLength(9) + .HasColumnType("character varying(9)"); + + b1.Property("OnError") + .IsRequired() + .HasMaxLength(9) + .HasColumnType("character varying(9)"); + + b1.Property("OnPrimary") + .IsRequired() + .HasMaxLength(9) + .HasColumnType("character varying(9)"); + + b1.Property("OnSecondary") + .IsRequired() + .HasMaxLength(9) + .HasColumnType("character varying(9)"); + + b1.Property("OnSurface") + .IsRequired() + .HasMaxLength(9) + .HasColumnType("character varying(9)"); + + b1.Property("Primary") + .IsRequired() + .HasMaxLength(9) + .HasColumnType("character varying(9)"); + + b1.Property("Secondary") + .IsRequired() + .HasMaxLength(9) + .HasColumnType("character varying(9)"); + + b1.Property("Surface") + .IsRequired() + .HasMaxLength(9) + .HasColumnType("character varying(9)"); + + b1.HasKey("CreatorId"); + + b1.ToTable("Colors", "Content"); + + b1.WithOwner() + .HasForeignKey("CreatorId"); + }); + + b.OwnsOne("Hutopy.Web.Features.Contents.Data.Images", "Images", b1 => + { + b1.Property("CreatorId") + .HasColumnType("uuid"); + + b1.Property("Banner") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b1.Property("Logo") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b1.HasKey("CreatorId"); + + b1.ToTable("Images", "Content"); + + b1.WithOwner() + .HasForeignKey("CreatorId"); + }); + + b.OwnsOne("Hutopy.Web.Features.Contents.Data.PresentationInfos", "PresentationInfos", b1 => + { + b1.Property("CreatorId") + .HasColumnType("uuid"); + + b1.Property("Email") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b1.Property("Image1Url") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b1.Property("Image2Url") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b1.Property("Image3Url") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b1.Property("Image4Url") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b1.Property("ImagesSubtitle") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b1.Property("ImagesText") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("character varying(10000)"); + + b1.Property("MainImageText") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("character varying(10000)"); + + b1.Property("MainImageUrl") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b1.Property("MainVideoText") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("character varying(10000)"); + + b1.Property("PhoneNumber") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b1.Property("Title") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b1.Property("VideoSubtitle") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b1.Property("VideoSubtitleMain") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b1.Property("VideoText") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("character varying(10000)"); + + b1.Property("VideoUrl") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b1.Property("VideoUrlMain") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b1.HasKey("CreatorId"); + + b1.ToTable("PresentationInfos", "Content"); + + b1.WithOwner() + .HasForeignKey("CreatorId"); + }); + + b.OwnsOne("Hutopy.Web.Features.Contents.Data.Socials", "Socials", b1 => + { + b1.Property("CreatorId") + .HasColumnType("uuid"); + + b1.Property("FacebookUrl") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b1.Property("InstagramUrl") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b1.Property("LinkedInUrl") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b1.Property("RedditUrl") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b1.Property("TikTokUrl") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b1.Property("WebsiteUrl") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b1.Property("XUrl") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b1.Property("YoutubeUrl") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b1.HasKey("CreatorId"); + + b1.ToTable("Socials", "Content"); + + b1.WithOwner() + .HasForeignKey("CreatorId"); + }); + + b.Navigation("Colors") + .IsRequired(); + + b.Navigation("Images") + .IsRequired(); + + b.Navigation("PresentationInfos") + .IsRequired(); + + b.Navigation("Socials") + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Web/Features/Contents/Data/Migrations/20250109015556_Adds_AcceptDonation_Creator.cs b/src/Web/Features/Contents/Data/Migrations/20250109015556_Adds_AcceptDonation_Creator.cs new file mode 100644 index 0000000..339404e --- /dev/null +++ b/src/Web/Features/Contents/Data/Migrations/20250109015556_Adds_AcceptDonation_Creator.cs @@ -0,0 +1,31 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Hutopy.Web.Features.Contents.Data.Migrations +{ + /// + public partial class Adds_AcceptDonation_Creator : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "AcceptDonation", + schema: "Content", + table: "Creators", + type: "boolean", + nullable: false, + defaultValue: false); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "AcceptDonation", + schema: "Content", + table: "Creators"); + } + } +} diff --git a/src/Web/Features/Contents/Data/Migrations/ContentDbContextModelSnapshot.cs b/src/Web/Features/Contents/Data/Migrations/ContentDbContextModelSnapshot.cs index caa7670..1beecf0 100644 --- a/src/Web/Features/Contents/Data/Migrations/ContentDbContextModelSnapshot.cs +++ b/src/Web/Features/Contents/Data/Migrations/ContentDbContextModelSnapshot.cs @@ -77,6 +77,9 @@ namespace Hutopy.Web.Features.Contents.Data.Migrations .ValueGeneratedOnAdd() .HasColumnType("uuid"); + b.Property("AcceptDonation") + .HasColumnType("boolean"); + b.Property("CreatedAt") .HasColumnType("timestamp with time zone"); diff --git a/src/Web/Features/Contents/EventHandlers/StripeAccountConfiguredHandler.cs b/src/Web/Features/Contents/EventHandlers/StripeAccountConfiguredHandler.cs new file mode 100644 index 0000000..af735c7 --- /dev/null +++ b/src/Web/Features/Contents/EventHandlers/StripeAccountConfiguredHandler.cs @@ -0,0 +1,43 @@ +using Hutopy.Web.Features.Contents.Data; +using Hutopy.Web.Features.Memberships.Events; + +namespace Hutopy.Web.Features.Contents.EventHandlers; + +[UsedImplicitly] +public class StripeAccountConfiguredHandler( + ILogger logger, + IServiceScopeFactory scopeFactory) + : IEventHandler +{ + public async Task HandleAsync( + StripeAccountConfigured eventModel, + CancellationToken ct) + { + using var scope = scopeFactory.CreateScope(); + await using var dbContext = scope.ServiceProvider.GetRequiredService(); + + var creator = await dbContext.FindAsync( + [eventModel.CreatorId], + cancellationToken: ct); + + if (creator is null) + { + logger.LogError( + "Creator with id {CreatorId} was not found.", + eventModel.CreatorId); + return; + } + + creator.AcceptDonation = true; + + var rows = await dbContext.SaveChangesAsync(ct); + + if (rows is 0 or > 1) + { + logger.LogError( + "An error occured while updating Creator with id {CreatorId}: rows:{Rows}", + eventModel.CreatorId, + rows); + } + } +} diff --git a/src/Web/Features/Contents/Handlers/GetCreatorByAlias.cs b/src/Web/Features/Contents/Handlers/GetCreatorByAlias.cs index 3724191..75e0dbc 100644 --- a/src/Web/Features/Contents/Handlers/GetCreatorByAlias.cs +++ b/src/Web/Features/Contents/Handlers/GetCreatorByAlias.cs @@ -15,6 +15,7 @@ public record struct GetCreatorByAliasResponse( Guid CreatedBy, DateTimeOffset CreatedAt, bool Verified, + bool AcceptDonation, string Name, string? Title, Socials Socials, @@ -68,6 +69,7 @@ public class GetCreatorByAliasHandler( creator.CreatedBy, creator.CreatedAt, creator.Verified, + creator.AcceptDonation, creator.Name, creator.Title, creator.Socials, diff --git a/src/Web/Features/Memberships/Data/Creator.cs b/src/Web/Features/Memberships/Data/Creator.cs index b07160e..b536f28 100644 --- a/src/Web/Features/Memberships/Data/Creator.cs +++ b/src/Web/Features/Memberships/Data/Creator.cs @@ -4,6 +4,6 @@ public class Creator { public Guid Id { get; set; } public string Name { get; set; } - public string StripeAccountId { get; set; } + public string? StripeAccountId { get; set; } public string PortraitUrl { get; set; } } diff --git a/src/Web/Features/Memberships/Events/StripeAccountConfigured.cs b/src/Web/Features/Memberships/Events/StripeAccountConfigured.cs new file mode 100644 index 0000000..cb91976 --- /dev/null +++ b/src/Web/Features/Memberships/Events/StripeAccountConfigured.cs @@ -0,0 +1,5 @@ +namespace Hutopy.Web.Features.Memberships.Events; + +public record StripeAccountConfigured( + Guid CreatorId, + string StripeAccountId); diff --git a/src/Web/Features/Memberships/Handlers/ChangeStripeId.cs b/src/Web/Features/Memberships/Handlers/ChangeStripeId.cs index 7008fee..9a3dae0 100644 --- a/src/Web/Features/Memberships/Handlers/ChangeStripeId.cs +++ b/src/Web/Features/Memberships/Handlers/ChangeStripeId.cs @@ -1,6 +1,6 @@ using Hutopy.Web.Common.Security; using Hutopy.Web.Features.Memberships.Data; -using Hutopy.Web.Features.Memberships.Infrastructure; +using Hutopy.Web.Features.Memberships.Events; namespace Hutopy.Web.Features.Memberships.Handlers; @@ -9,13 +9,12 @@ public record struct ChangeStripeIdRequest( string StripeAccountId); public class ChangeStripeIdHandler( - MembershipDbContext dbContext, - StripeService stripeService) + MembershipDbContext dbContext) : Endpoint { public override void Configure() { - Post("/api/creators/stripe-account"); + Post("/api/membership/stripe-account"); Options(o => o.WithTags("Memberships")); } @@ -36,17 +35,21 @@ public class ChangeStripeIdHandler( creator = new Creator { Id = creatorId, - Name = HttpContext.User.GetAlias() ?? creatorId.ToString() + Name = HttpContext.User.GetAlias() ?? creatorId.ToString(), + PortraitUrl = HttpContext.User.GetPortraitUrl() ?? string.Empty }; await dbContext.AddAsync(creator, ct); } creator.StripeAccountId = req.StripeAccountId; - creator.PortraitUrl = HttpContext.User.GetPortraitUrl(); await dbContext.SaveChangesAsync(ct); + await PublishAsync( + new StripeAccountConfigured(creator.Id, creator.StripeAccountId), + cancellation: ct); + await SendOkAsync(creator.Id, ct); } } diff --git a/src/Web/Features/Memberships/Handlers/ConfigureStripeAccount.cs b/src/Web/Features/Memberships/Handlers/ConfigureStripeAccount.cs deleted file mode 100644 index f2b3f25..0000000 --- a/src/Web/Features/Memberships/Handlers/ConfigureStripeAccount.cs +++ /dev/null @@ -1,52 +0,0 @@ -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 ConfigureStripeAccountRequest( - Guid SubscriptionId, - string StripeAccountId); - -public class ConfigureStripeAccountHandler( - MembershipDbContext dbContext, - StripeService stripeService) - : Endpoint -{ - public override void Configure() - { - Post("/api/membership/stripe-account"); - Options(o => o.WithTags("Memberships")); - } - - public override async Task HandleAsync( - ConfigureStripeAccountRequest 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; - - await dbContext.SaveChangesAsync(ct); - - await SendOkAsync(creator.Id, ct); - } -}