Merge remote-tracking branch 'origin/Devsights-changes'
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"sdk": {
|
"sdk": {
|
||||||
"version": "8.0.0",
|
"version": "8.0.0",
|
||||||
"rollForward": "latestMajor",
|
"rollForward": "latestMinor",
|
||||||
"allowPrerelease": false
|
"allowPrerelease": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,14 +53,14 @@ public class PresentationInfos
|
|||||||
[MaxLength(255)] public string PhoneNumber { get; set; } = string.Empty;
|
[MaxLength(255)] public string PhoneNumber { get; set; } = string.Empty;
|
||||||
[MaxLength(255)] public string Email { 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 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 MainImageText { get; set; } = string.Empty;
|
||||||
[MaxLength(10000)] public string MainVideoText { 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 ImagesSubtitle { get; set; } = string.Empty;
|
||||||
[MaxLength(2000)] public string Image1Url { 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? Image2Url { get; set; } = string.Empty;
|
||||||
[MaxLength(2000)] public string Image3Url { 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? Image4Url { get; set; } = string.Empty;
|
||||||
[MaxLength(10000)] public string ImagesText { 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 VideoSubtitle { get; set; } = string.Empty;
|
||||||
[MaxLength(2000)] public string VideoSubtitleMain { get; set; } = string.Empty;
|
[MaxLength(2000)] public string VideoSubtitleMain { get; set; } = string.Empty;
|
||||||
|
|||||||
@@ -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;
|
namespace Hutopy.Web.Features.Contents.Handlers;
|
||||||
|
|
||||||
@@ -8,29 +9,37 @@ public record ChangePresentationInfosRequest(
|
|||||||
string? PhoneNumber,
|
string? PhoneNumber,
|
||||||
string? Email,
|
string? Email,
|
||||||
string? Title,
|
string? Title,
|
||||||
string? MainImageUrl,
|
|
||||||
string? MainImageText,
|
string? MainImageText,
|
||||||
string? MainVideoText,
|
string? MainVideoText,
|
||||||
string? ImagesSubtitle,
|
string? ImagesSubtitle,
|
||||||
string? Image1Url,
|
|
||||||
string? Image2Url,
|
|
||||||
string? Image3Url,
|
|
||||||
string? Image4Url,
|
|
||||||
string? ImagesText,
|
string? ImagesText,
|
||||||
string? VideoSubtitle,
|
string? VideoSubtitle,
|
||||||
string? VideoSubtitleMain,
|
string? VideoSubtitleMain,
|
||||||
string? VideoUrlMain,
|
string? VideoUrlMain,
|
||||||
string? VideoUrl,
|
string? VideoUrl,
|
||||||
string? VideoText);
|
string? VideoText,
|
||||||
|
string? MainImageUrl,
|
||||||
|
string? Image1Url,
|
||||||
|
string? Image2Url,
|
||||||
|
string? Image3Url,
|
||||||
|
string? Image4Url,
|
||||||
|
IFormFile? MainImage,
|
||||||
|
IFormFile? Image1,
|
||||||
|
IFormFile? Image2,
|
||||||
|
IFormFile? Image3,
|
||||||
|
IFormFile? Image4);
|
||||||
|
|
||||||
|
[PublicAPI]
|
||||||
public class ChangePresentationInfosHandler(
|
public class ChangePresentationInfosHandler(
|
||||||
ContentDbContext context)
|
ContentDbContext context,
|
||||||
|
AzureBlobStorage blobStorage)
|
||||||
: Endpoint<ChangePresentationInfosRequest>
|
: Endpoint<ChangePresentationInfosRequest>
|
||||||
{
|
{
|
||||||
public override void Configure()
|
public override void Configure()
|
||||||
{
|
{
|
||||||
Post("/api/creators/{CreatorId}/presentation-infos");
|
Post("/api/creators/{CreatorId}/presentation-infos");
|
||||||
Options(o => o.WithTags("Creators"));
|
Options(o => o.WithTags("Creators"));
|
||||||
|
AllowFileUploads();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task HandleAsync(
|
public override async Task HandleAsync(
|
||||||
@@ -40,30 +49,67 @@ public class ChangePresentationInfosHandler(
|
|||||||
var creator = await context
|
var creator = await context
|
||||||
.Creators
|
.Creators
|
||||||
.Include(c => c.PresentationInfos)
|
.Include(c => c.PresentationInfos)
|
||||||
.SingleAsync(
|
.SingleOrDefaultAsync(
|
||||||
c => c.Id == request.CreatorId,
|
c => c.Id == request.CreatorId,
|
||||||
cancellationToken: ct);
|
cancellationToken: ct);
|
||||||
|
|
||||||
creator.PresentationInfos.PhoneNumber = request.PhoneNumber ?? "";
|
if (creator is null)
|
||||||
creator.PresentationInfos.Email = request.Email ?? "";
|
{
|
||||||
creator.PresentationInfos.Title = request.Title ?? "";
|
await SendNotFoundAsync(ct);
|
||||||
creator.PresentationInfos.MainImageUrl = request.MainImageUrl ?? "";
|
return;
|
||||||
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);
|
|
||||||
|
|
||||||
|
async Task<string> UploadFileOrDefaultAsync(
|
||||||
|
IFormFile? file,
|
||||||
|
string subDirectory,
|
||||||
|
string fileName,
|
||||||
|
string? newUrl)
|
||||||
|
{
|
||||||
|
if (newUrl == "")
|
||||||
|
return "";
|
||||||
|
|
||||||
|
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 UploadFileOrDefaultAsync(
|
||||||
|
request.MainImage, "Profile", "MainImage", request.MainImageUrl);
|
||||||
|
|
||||||
|
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);
|
await SendOkAsync(ct);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
292
src/Web/Features/Memberships/Data/Migrations/20241216215210_UpdateSeedData.Designer.cs
generated
Normal file
292
src/Web/Features/Memberships/Data/Migrations/20241216215210_UpdateSeedData.Designer.cs
generated
Normal file
@@ -0,0 +1,292 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
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
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
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<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("PortraitUrl")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("StripeAccountId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Creators", "Membership");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Hutopy.Web.Features.Memberships.Data.Subscription", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset>("CreatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||||
|
|
||||||
|
b.Property<Guid>("CreatorId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset?>("EndDate")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset>("StartDate")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("StripeSessionId")
|
||||||
|
.HasMaxLength(255)
|
||||||
|
.HasColumnType("character varying(255)");
|
||||||
|
|
||||||
|
b.Property<string>("StripeSubscriptionId")
|
||||||
|
.HasMaxLength(255)
|
||||||
|
.HasColumnType("character varying(255)");
|
||||||
|
|
||||||
|
b.Property<Guid>("TierId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<Guid>("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<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||||
|
|
||||||
|
b.Property<Guid>("CreatorId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<string>("CurrencyCode")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(128)
|
||||||
|
.HasColumnType("character varying(128)");
|
||||||
|
|
||||||
|
b.Property<string>("Description")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(4096)
|
||||||
|
.HasColumnType("character varying(4096)");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(128)
|
||||||
|
.HasColumnType("character varying(128)");
|
||||||
|
|
||||||
|
b.Property<decimal>("Price")
|
||||||
|
.HasColumnType("numeric");
|
||||||
|
|
||||||
|
b.Property<string>("StripePriceId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(128)
|
||||||
|
.HasColumnType("character varying(128)");
|
||||||
|
|
||||||
|
b.Property<string>("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<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<decimal>("Amount")
|
||||||
|
.HasColumnType("numeric");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset>("CreatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||||
|
|
||||||
|
b.Property<Guid>("CreatorId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<string>("CreatorName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Currency")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Message")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("StripeSessionId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<Guid>("TipperId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<string>("TipperName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<Guid>("TransactionId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("TransactionId");
|
||||||
|
|
||||||
|
b.ToTable("Tips", "Membership");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Hutopy.Web.Features.Memberships.Data.Transaction", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<decimal>("Amount")
|
||||||
|
.HasColumnType("numeric");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset>("CreatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||||
|
|
||||||
|
b.Property<string>("Currency")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("StripeInvoiceUrl")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<Guid?>("SubscriptionId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Timestamp")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Hutopy.Web.Features.Memberships.Data.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class UpdateSeedData : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
52
src/Web/Features/Memberships/Handlers/ChangeStripeId.cs
Normal file
52
src/Web/Features/Memberships/Handlers/ChangeStripeId.cs
Normal file
@@ -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<ChangeStripeIdRequest>
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -51,6 +51,8 @@ public class SendTipHandler(
|
|||||||
{
|
{
|
||||||
Post("/api/tips");
|
Post("/api/tips");
|
||||||
Options(o => o.WithTags("Memberships"));
|
Options(o => o.WithTags("Memberships"));
|
||||||
|
|
||||||
|
AllowAnonymous();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task HandleAsync(
|
public override async Task HandleAsync(
|
||||||
@@ -68,8 +70,6 @@ public class SendTipHandler(
|
|||||||
}
|
}
|
||||||
|
|
||||||
var checkoutSession = await stripeService.CreateTipCheckoutSessionAsync(
|
var checkoutSession = await stripeService.CreateTipCheckoutSessionAsync(
|
||||||
User.GetUserId(),
|
|
||||||
User.GetAlias()!,
|
|
||||||
creator.Id,
|
creator.Id,
|
||||||
creator.Name,
|
creator.Name,
|
||||||
req.Amount,
|
req.Amount,
|
||||||
@@ -77,7 +77,8 @@ public class SendTipHandler(
|
|||||||
req.Message,
|
req.Message,
|
||||||
creator.StripeAccountId,
|
creator.StripeAccountId,
|
||||||
req.CheckoutSuccessUrl,
|
req.CheckoutSuccessUrl,
|
||||||
req.CheckoutCancelledUrl);
|
req.CheckoutCancelledUrl
|
||||||
|
);
|
||||||
|
|
||||||
await SendAsync(
|
await SendAsync(
|
||||||
new SendTipResponse("Pending", checkoutSession.Url),
|
new SendTipResponse("Pending", checkoutSession.Url),
|
||||||
|
|||||||
@@ -55,8 +55,6 @@ public sealed class StripeService(
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Session> CreateTipCheckoutSessionAsync(
|
public async Task<Session> CreateTipCheckoutSessionAsync(
|
||||||
Guid tipperId,
|
|
||||||
string tipperName,
|
|
||||||
Guid creatorId,
|
Guid creatorId,
|
||||||
string creatorName,
|
string creatorName,
|
||||||
decimal amount,
|
decimal amount,
|
||||||
@@ -72,10 +70,7 @@ public sealed class StripeService(
|
|||||||
// Create Stripe customer for the user if not already created
|
// Create Stripe customer for the user if not already created
|
||||||
var customerService = new CustomerService();
|
var customerService = new CustomerService();
|
||||||
var customer = await customerService.CreateAsync(
|
var customer = await customerService.CreateAsync(
|
||||||
new CustomerCreateOptions
|
new CustomerCreateOptions{},
|
||||||
{
|
|
||||||
Metadata = new Dictionary<string, string> { { "userId", tipperId.ToString() } }
|
|
||||||
},
|
|
||||||
cancellationToken: ct);
|
cancellationToken: ct);
|
||||||
|
|
||||||
// Create paymentIntent for the user
|
// Create paymentIntent for the user
|
||||||
@@ -116,8 +111,6 @@ public sealed class StripeService(
|
|||||||
CancelUrl = cancelUrl, // Redirect after canceled payment
|
CancelUrl = cancelUrl, // Redirect after canceled payment
|
||||||
Metadata = new Dictionary<string, string>
|
Metadata = new Dictionary<string, string>
|
||||||
{
|
{
|
||||||
{ "tipperId", tipperId.ToString() },
|
|
||||||
{ "tipperName", tipperName },
|
|
||||||
{ "creatorId", creatorId.ToString() },
|
{ "creatorId", creatorId.ToString() },
|
||||||
{ "creatorName", creatorName },
|
{ "creatorName", creatorName },
|
||||||
{ "message", message },
|
{ "message", message },
|
||||||
|
|||||||
70
src/Web/Features/Messages/Data/Migrations/20241217225954_ChangeStripeId.Designer.cs
generated
Normal file
70
src/Web/Features/Messages/Data/Migrations/20241217225954_ChangeStripeId.Designer.cs
generated
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
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
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
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<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset>("CreatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||||
|
|
||||||
|
b.Property<Guid>("CreatedBy")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<string>("CreatedByName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(255)
|
||||||
|
.HasColumnType("character varying(255)");
|
||||||
|
|
||||||
|
b.Property<string>("CreatedByPortraitUrl")
|
||||||
|
.HasMaxLength(255)
|
||||||
|
.HasColumnType("character varying(255)");
|
||||||
|
|
||||||
|
b.Property<Guid?>("ParentId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<Guid>("SubjectId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<string>("Value")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(2048)
|
||||||
|
.HasColumnType("character varying(2048)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Messages", "Messaging");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Hutopy.Web.Features.Messages.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class ChangeStripeId : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AlterColumn<string>(
|
||||||
|
name: "Value",
|
||||||
|
schema: "Messaging",
|
||||||
|
table: "Messages",
|
||||||
|
type: "character varying(2048)",
|
||||||
|
maxLength: 2048,
|
||||||
|
nullable: false,
|
||||||
|
oldClrType: typeof(string),
|
||||||
|
oldType: "text");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AlterColumn<string>(
|
||||||
|
name: "Value",
|
||||||
|
schema: "Messaging",
|
||||||
|
table: "Messages",
|
||||||
|
type: "text",
|
||||||
|
nullable: false,
|
||||||
|
oldClrType: typeof(string),
|
||||||
|
oldType: "character varying(2048)",
|
||||||
|
oldMaxLength: 2048);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,7 +18,7 @@ namespace Hutopy.Web.Features.Messages.Migrations
|
|||||||
#pragma warning disable 612, 618
|
#pragma warning disable 612, 618
|
||||||
modelBuilder
|
modelBuilder
|
||||||
.HasDefaultSchema("Messaging")
|
.HasDefaultSchema("Messaging")
|
||||||
.HasAnnotation("ProductVersion", "8.0.4")
|
.HasAnnotation("ProductVersion", "8.0.10")
|
||||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||||
|
|
||||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||||
@@ -54,7 +54,8 @@ namespace Hutopy.Web.Features.Messages.Migrations
|
|||||||
|
|
||||||
b.Property<string>("Value")
|
b.Property<string>("Value")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("text");
|
.HasMaxLength(2048)
|
||||||
|
.HasColumnType("character varying(2048)");
|
||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,19 @@
|
|||||||
using Hutopy.Web.Features.Users.Handlers.Models;
|
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;
|
namespace Hutopy.Web.Features.Users.Handlers;
|
||||||
|
|
||||||
[PublicAPI]
|
[PublicAPI]
|
||||||
public class GetCurrentUserQueryHandler(
|
public class GetCurrentUserQueryHandler(
|
||||||
IdentityService identityService)
|
IdentityService identityService,
|
||||||
|
MembershipDbContext membershipDbContext)
|
||||||
: EndpointWithoutRequest<UserDto>
|
: EndpointWithoutRequest<UserDto>
|
||||||
{
|
{
|
||||||
public override void Configure()
|
public override void Configure()
|
||||||
{
|
{
|
||||||
Get("/api/users/profile");
|
Get("/api/users/profile");
|
||||||
Options(o => o.WithTags("Users"));
|
Options(o => o.WithTags("Memberships"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task HandleAsync(
|
public override async Task HandleAsync(
|
||||||
@@ -26,6 +29,12 @@ public class GetCurrentUserQueryHandler(
|
|||||||
|
|
||||||
var roles = await identityService.GetCurrentUserRolesAsync();
|
var roles = await identityService.GetCurrentUserRolesAsync();
|
||||||
|
|
||||||
|
var stripeId = await membershipDbContext
|
||||||
|
.Creators
|
||||||
|
.Where(c => c.Id == userModel.Id)
|
||||||
|
.Select(c => c.StripeAccountId)
|
||||||
|
.FirstOrDefaultAsync(cancellationToken);
|
||||||
|
|
||||||
await SendOkAsync(
|
await SendOkAsync(
|
||||||
new UserDto
|
new UserDto
|
||||||
{
|
{
|
||||||
@@ -40,6 +49,7 @@ public class GetCurrentUserQueryHandler(
|
|||||||
BirthDate = userModel.BirthDate,
|
BirthDate = userModel.BirthDate,
|
||||||
Address = userModel.Address,
|
Address = userModel.Address,
|
||||||
UserRoles = roles,
|
UserRoles = roles,
|
||||||
|
StripeId = stripeId ?? string.Empty
|
||||||
},
|
},
|
||||||
cancellationToken);
|
cancellationToken);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,4 +13,5 @@ public class UserDto
|
|||||||
public string? PhoneNumber { get; init; }
|
public string? PhoneNumber { get; init; }
|
||||||
public DateTime? BirthDate { get; init; }
|
public DateTime? BirthDate { get; init; }
|
||||||
public string? Address { get; init; }
|
public string? Address { get; init; }
|
||||||
|
public string? StripeId { get; init; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
using Hutopy.Web.Common;
|
using Hutopy.Web.Common;
|
||||||
using Hutopy.Web.Features.Contents.Data;
|
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.Messages.Data;
|
||||||
using Hutopy.Web.Features.Users;
|
using Hutopy.Web.Features.Users;
|
||||||
|
|
||||||
@@ -16,7 +19,8 @@ public static class WebApplicationExtensions
|
|||||||
var seeder = new TestDataSeeder(
|
var seeder = new TestDataSeeder(
|
||||||
scope.ServiceProvider.GetRequiredService<ApplicationUserManager>(),
|
scope.ServiceProvider.GetRequiredService<ApplicationUserManager>(),
|
||||||
scope.ServiceProvider.GetRequiredService<ContentDbContext>(),
|
scope.ServiceProvider.GetRequiredService<ContentDbContext>(),
|
||||||
scope.ServiceProvider.GetRequiredService<MessagingDbContext>());
|
scope.ServiceProvider.GetRequiredService<MessagingDbContext>(),
|
||||||
|
scope.ServiceProvider.GetRequiredService<MembershipDbContext>());
|
||||||
|
|
||||||
await seeder.SeedAsync();
|
await seeder.SeedAsync();
|
||||||
}
|
}
|
||||||
@@ -25,7 +29,8 @@ public static class WebApplicationExtensions
|
|||||||
internal class TestDataSeeder(
|
internal class TestDataSeeder(
|
||||||
ApplicationUserManager userManager,
|
ApplicationUserManager userManager,
|
||||||
ContentDbContext contentContext,
|
ContentDbContext contentContext,
|
||||||
MessagingDbContext messagingContext)
|
MessagingDbContext messagingContext,
|
||||||
|
MembershipDbContext membershipContext)
|
||||||
{
|
{
|
||||||
private const string DefaultPassword = "Test123#";
|
private const string DefaultPassword = "Test123#";
|
||||||
|
|
||||||
@@ -65,11 +70,23 @@ internal class TestDataSeeder(
|
|||||||
|
|
||||||
await messagingContext.SaveChangesAsync();
|
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<Content> GenerateContent(
|
private List<Content> GenerateContent(
|
||||||
Creator creator,
|
ContentCreator creator,
|
||||||
int contentCount)
|
int contentCount)
|
||||||
{
|
{
|
||||||
var currentDate = DateTimeOffset.UtcNow;
|
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",
|
Name = "hutopy",
|
||||||
Title = "Page officielle",
|
Title = "Page officielle",
|
||||||
@@ -222,7 +239,7 @@ internal class TestDataSeeder(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private readonly static Creator ArpsCreator = new()
|
private readonly static ContentCreator ArpsCreator = new()
|
||||||
{
|
{
|
||||||
Name = "arps",
|
Name = "arps",
|
||||||
Title = "Créateur de contenu",
|
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",
|
Name = "chloebeaugrand",
|
||||||
Title = "Page officielle",
|
Title = "Page officielle",
|
||||||
@@ -285,7 +302,7 @@ internal class TestDataSeeder(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private readonly static Creator GuillaumeMCreator = new()
|
private readonly static ContentCreator GuillaumeMCreator = new()
|
||||||
{
|
{
|
||||||
Name = "guillaumem",
|
Name = "guillaumem",
|
||||||
Title = "Page officielle",
|
Title = "Page officielle",
|
||||||
@@ -317,7 +334,7 @@ internal class TestDataSeeder(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private readonly static Creator LeffetCreator = new()
|
private readonly static ContentCreator LeffetCreator = new()
|
||||||
{
|
{
|
||||||
Name = "leffet",
|
Name = "leffet",
|
||||||
Title = "Page officielle",
|
Title = "Page officielle",
|
||||||
@@ -348,7 +365,7 @@ internal class TestDataSeeder(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private readonly static Creator MathieuCaron = new()
|
private readonly static ContentCreator MathieuCaron = new()
|
||||||
{
|
{
|
||||||
Name = "mathieucaron",
|
Name = "mathieucaron",
|
||||||
Title = "Page officielle",
|
Title = "Page officielle",
|
||||||
@@ -378,7 +395,7 @@ internal class TestDataSeeder(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private readonly Creator[] _creators =
|
private readonly ContentCreator[] _creators =
|
||||||
[
|
[
|
||||||
HutopyCreator,
|
HutopyCreator,
|
||||||
ArpsCreator,
|
ArpsCreator,
|
||||||
|
|||||||
Reference in New Issue
Block a user