diff --git a/.github/workflows/backend-ci.yml b/.github/workflows/backend-ci.yml index c24e237..9dafe77 100644 --- a/.github/workflows/backend-ci.yml +++ b/.github/workflows/backend-ci.yml @@ -28,7 +28,7 @@ jobs: - name: dotnet build and publish run: | cd backend - dotnet publish --configuration Release --artifacts-path ./publish/ + dotnet publish --configuration Release --artifacts-path ./publish/ backend.sln # Deploy to Azure WebApp - name: Deploy to Azure WebApp diff --git a/backend/README.md b/README.md similarity index 100% rename from backend/README.md rename to README.md diff --git a/Stripe.md b/Stripe.md new file mode 100644 index 0000000..5b99738 --- /dev/null +++ b/Stripe.md @@ -0,0 +1,30 @@ +# Stripe + +## Events Workflow + +### Membership + +1. checkout.session.completed + - Store StripeSubscriptionId, UserId, CreatorId, TierId + - Save a new Subscription entity with the status "Pending" + +2. invoice.payment_succeeded + - Grant access (set Subscription.Active = true or similar) + - Record transaction or set StartDate + - Notify Creator (e.g., new member) + +3. customer.subscription.updated + - Update `EndDate = CancelAt ?? CanceledAt` + +4. customer.subscription.deleted + - Revoke access + - Mark Subscription as inactive/ended + +### Tips + +1. checkout.session.completed + - Store TipId, StripeSessionId, TipperId, CreatorId + - PaymentIntentStatus == "paid" + - Status = "Paid" + - Notify creator + - Record transaction diff --git a/backend/.github/FUNDING.yml b/backend/.github/FUNDING.yml deleted file mode 100644 index 27f72a1..0000000 --- a/backend/.github/FUNDING.yml +++ /dev/null @@ -1,13 +0,0 @@ -# These are supported funding model platforms - -github: JasonTaylorDev # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] -# patreon: # Replace with a single Patreon username -# open_collective: # Replace with a single Open Collective username -# ko_fi: # Replace with a single Ko-fi username -# tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel -# community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry -# liberapay: # Replace with a single Liberapay username -# issuehunt: # Replace with a single IssueHunt username -# otechie: # Replace with a single Otechie username -# lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry -# custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] diff --git a/backend/Common/Domain/Entity.cs b/backend/Common/Domain/Entity.cs new file mode 100644 index 0000000..071cfca --- /dev/null +++ b/backend/Common/Domain/Entity.cs @@ -0,0 +1,10 @@ +namespace Hutopy.Common.Domain; + +public abstract class Entity +{ + public Guid Id { get; init; } + public Guid CreatedBy { get; init; } + public DateTimeOffset CreatedAt { get; init; } + public Guid? DeletedBy { get; set; } + public DateTimeOffset? DeletedAt { get; set; } +} diff --git a/backend/src/Web/DependencyInjection.cs b/backend/DependencyInjection.cs similarity index 94% rename from backend/src/Web/DependencyInjection.cs rename to backend/DependencyInjection.cs index 78dfcd3..83ced38 100644 --- a/backend/src/Web/DependencyInjection.cs +++ b/backend/DependencyInjection.cs @@ -1,14 +1,12 @@ using System.Text; -using Azure.Identity; -using Hutopy.Web.Features.Users.Data; -using Microsoft.AspNetCore.Authentication.Cookies; +using Hutopy.Modules.Identity.Data; using Microsoft.AspNetCore.Authentication.Facebook; using Microsoft.AspNetCore.Authentication.Google; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Mvc; using Microsoft.IdentityModel.Tokens; -namespace Hutopy.Web; +namespace Hutopy; public static class DependencyInjection { @@ -19,7 +17,7 @@ public static class DependencyInjection services.AddHttpContextAccessor(); services.AddHealthChecks() - .AddDbContextCheck(); + .AddDbContextCheck(); services.AddHttpClient(); diff --git a/backend/Directory.Build.props b/backend/Directory.Build.props deleted file mode 100644 index f92f3f6..0000000 --- a/backend/Directory.Build.props +++ /dev/null @@ -1,10 +0,0 @@ - - - - net9.0 - false - enable - enable - default - - \ No newline at end of file diff --git a/backend/Hutopy.sln.DotSettings b/backend/Folder.DotSettings similarity index 100% rename from backend/Hutopy.sln.DotSettings rename to backend/Folder.DotSettings diff --git a/backend/src/Web/GlobalUsings.cs b/backend/GlobalUsings.cs similarity index 100% rename from backend/src/Web/GlobalUsings.cs rename to backend/GlobalUsings.cs diff --git a/backend/src/Web/Web.csproj b/backend/Hutopy.csproj similarity index 51% rename from backend/src/Web/Web.csproj rename to backend/Hutopy.csproj index 13c4ef0..dc7f253 100644 --- a/backend/src/Web/Web.csproj +++ b/backend/Hutopy.csproj @@ -1,33 +1,48 @@  - Hutopy.Web - Hutopy.Web + net9.0 + false + enable + enable + default de6d03c4-8b1c-49e2-a8ca-c38cd4dc7d85 - - - - - - - - - - - - - - + + + + + + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - - - + + + + + + + + + + + + + diff --git a/backend/Hutopy.sln b/backend/Hutopy.sln deleted file mode 100644 index 8acad1f..0000000 --- a/backend/Hutopy.sln +++ /dev/null @@ -1,43 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.0.31903.59 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{6ED356A7-8B47-4613-AD01-C85CF28491BD}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{E2DA20AA-28D1-455C-BF50-C49A8F831633}" - ProjectSection(SolutionItems) = preProject - .editorconfig = .editorconfig - .gitignore = .gitignore - Directory.Build.props = Directory.Build.props - global.json = global.json - README.md = README.md - start-infrastructure.sh = start-infrastructure.sh - azure-pipelines.yml = azure-pipelines.yml - update-databases.sh = update-databases.sh - create-sql-scripts.sh = create-sql-scripts.sh - EndProjectSection -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Web", "src\Web\Web.csproj", "{4E4EE20C-F06A-4A1B-851F-C5577796941C}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {4E4EE20C-F06A-4A1B-851F-C5577796941C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4E4EE20C-F06A-4A1B-851F-C5577796941C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4E4EE20C-F06A-4A1B-851F-C5577796941C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4E4EE20C-F06A-4A1B-851F-C5577796941C}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {4E4EE20C-F06A-4A1B-851F-C5577796941C} = {6ED356A7-8B47-4613-AD01-C85CF28491BD} - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {3CB609D9-5D54-4C11-A371-DAAC8B74E430} - EndGlobalSection -EndGlobal diff --git a/backend/src/Web/Common/BlobStorage/BlobStructure.txt b/backend/Infrastructure/BlobStorage/BlobStructure.txt similarity index 100% rename from backend/src/Web/Common/BlobStorage/BlobStructure.txt rename to backend/Infrastructure/BlobStorage/BlobStructure.txt diff --git a/backend/Infrastructure/BlobStorage/Contracts/CommonFileNames.cs b/backend/Infrastructure/BlobStorage/Contracts/CommonFileNames.cs new file mode 100644 index 0000000..4ee35f9 --- /dev/null +++ b/backend/Infrastructure/BlobStorage/Contracts/CommonFileNames.cs @@ -0,0 +1,7 @@ +namespace Hutopy.Infrastructure.BlobStorage.Contracts; + +public static class CommonFileNames +{ + public const string ProfilePicture = "profilePicture"; + public const string BannerPicture = "bannerPicture"; +} diff --git a/backend/src/Web/Common/BlobStorage/ContainerNames.cs b/backend/Infrastructure/BlobStorage/Contracts/ContainerNames.cs similarity index 68% rename from backend/src/Web/Common/BlobStorage/ContainerNames.cs rename to backend/Infrastructure/BlobStorage/Contracts/ContainerNames.cs index faedbe3..9ea3329 100644 --- a/backend/src/Web/Common/BlobStorage/ContainerNames.cs +++ b/backend/Infrastructure/BlobStorage/Contracts/ContainerNames.cs @@ -1,4 +1,4 @@ -namespace Hutopy.Web.Common.BlobStorage; +namespace Hutopy.Infrastructure.BlobStorage.Contracts; public static class ContainerNames { diff --git a/backend/src/Web/Common/BlobStorage/ContentTypes.cs b/backend/Infrastructure/BlobStorage/Contracts/ContentTypes.cs similarity index 88% rename from backend/src/Web/Common/BlobStorage/ContentTypes.cs rename to backend/Infrastructure/BlobStorage/Contracts/ContentTypes.cs index a31a010..1540ccd 100644 --- a/backend/src/Web/Common/BlobStorage/ContentTypes.cs +++ b/backend/Infrastructure/BlobStorage/Contracts/ContentTypes.cs @@ -1,6 +1,6 @@ using System.Text; -namespace Hutopy.Web.Common.BlobStorage; +namespace Hutopy.Infrastructure.BlobStorage.Contracts; public static class ContentTypes { @@ -39,11 +39,6 @@ public static class ContentTypes // Check for HTML content by looking for "" or "" tags string content = Encoding.UTF8.GetString(buffer); - if (content.Contains("")) - { - return true; - } - - return false; + return content.Contains(""); } } diff --git a/backend/Infrastructure/BlobStorage/Contracts/IBlobStorage.cs b/backend/Infrastructure/BlobStorage/Contracts/IBlobStorage.cs new file mode 100644 index 0000000..5ea333f --- /dev/null +++ b/backend/Infrastructure/BlobStorage/Contracts/IBlobStorage.cs @@ -0,0 +1,32 @@ +namespace Hutopy.Infrastructure.BlobStorage.Contracts; + +public interface IBlobStorage +{ + /// + /// Upload a file to blob storage. + /// + /// The name of the container where the file is stored. + /// The blob name (path within the container, include the file name). + /// + /// The content type. + /// The cancellation token + /// + Task UploadFileAsync( + string containerName, + string blobName, + Stream stream, + string contentType, + CancellationToken ct = default); + + /// + /// Download a file to blob storage. + /// + /// The blob name (path within the container). + /// The name of the container where the file is stored. (users) + /// The cancellation token for the request + /// + Task DownloadFileAsync( + string containerName, + string blobName, + CancellationToken ct = default); +} diff --git a/backend/Infrastructure/BlobStorage/Contracts/SubDirectoryNames.cs b/backend/Infrastructure/BlobStorage/Contracts/SubDirectoryNames.cs new file mode 100644 index 0000000..cd2fb40 --- /dev/null +++ b/backend/Infrastructure/BlobStorage/Contracts/SubDirectoryNames.cs @@ -0,0 +1,8 @@ +namespace Hutopy.Infrastructure.BlobStorage.Contracts; + +public static class SubDirectoryNames +{ + public const string Profile = "profile"; + public const string Contents = "contents"; + public const string Albums = "albums"; +} diff --git a/backend/src/Web/Common/BlobStorage/AzureBlobStorage.cs b/backend/Infrastructure/BlobStorage/Services/AzureBlobStorage.cs similarity index 91% rename from backend/src/Web/Common/BlobStorage/AzureBlobStorage.cs rename to backend/Infrastructure/BlobStorage/Services/AzureBlobStorage.cs index 0814f05..f25d5c6 100644 --- a/backend/src/Web/Common/BlobStorage/AzureBlobStorage.cs +++ b/backend/Infrastructure/BlobStorage/Services/AzureBlobStorage.cs @@ -1,10 +1,11 @@ using Azure; using Azure.Storage.Blobs; using Azure.Storage.Blobs.Models; +using Hutopy.Infrastructure.BlobStorage.Contracts; -namespace Hutopy.Web.Common.BlobStorage; +namespace Hutopy.Infrastructure.BlobStorage.Services; -public class AzureBlobStorage +public class AzureBlobStorage : IBlobStorage { private const long MaxUploadSize = 10 * 1024 * 1024; // 10 MB in bytes @@ -27,8 +28,12 @@ public class AzureBlobStorage /// The content type. /// The cancellation token /// - public async Task UploadFileAsync(string containerName, string blobName, Stream stream, - string contentType, CancellationToken ct = default) + public async Task UploadFileAsync( + string containerName, + string blobName, + Stream stream, + string contentType, + CancellationToken ct = default) { // Read the file stream into a memory stream to determine the length // WATCH FOR MEMORY USAGE USING THE MEMORY STREAM. @@ -113,7 +118,9 @@ public class AzureBlobStorage /// The name of the container where the file is stored. (users) /// The cancellation token for the request /// - public async Task DownloadFileAsync(string containerName, string blobName, + public async Task DownloadFileAsync( + string containerName, + string blobName, CancellationToken ct = default) { try diff --git a/backend/src/Web/Features/Users/WebsiteOptions.cs b/backend/Infrastructure/Configuration/WebsiteOptions.cs similarity index 75% rename from backend/src/Web/Features/Users/WebsiteOptions.cs rename to backend/Infrastructure/Configuration/WebsiteOptions.cs index 5d491e8..6c2c238 100644 --- a/backend/src/Web/Features/Users/WebsiteOptions.cs +++ b/backend/Infrastructure/Configuration/WebsiteOptions.cs @@ -1,6 +1,4 @@ -using System; - -namespace Hutopy.Web.Features.Users; +namespace Hutopy.Infrastructure.Configuration; public class WebsiteOptions { diff --git a/backend/Infrastructure/DependencyInjection.cs b/backend/Infrastructure/DependencyInjection.cs new file mode 100644 index 0000000..3fb959b --- /dev/null +++ b/backend/Infrastructure/DependencyInjection.cs @@ -0,0 +1,40 @@ +using Hutopy.Infrastructure.BlobStorage.Contracts; +using Hutopy.Infrastructure.BlobStorage.Services; +using Hutopy.Infrastructure.Configuration; +using Hutopy.Infrastructure.Emailer.Configuration; +using Hutopy.Infrastructure.Emailer.Contracts; +using Hutopy.Infrastructure.Emailer.Services; +using Hutopy.Infrastructure.Payments.Stripe.Configuration; +using Hutopy.Infrastructure.Payments.Stripe.Services; +using Hutopy.Modules.Memberships.Contracts; +using Hutopy.Modules.Tipping.Contracts; + +namespace Hutopy.Infrastructure; + +public static class DependencyInjection +{ + public static WebApplicationBuilder AddInfrastructureModule( + this WebApplicationBuilder builder) + { + builder.Services.Configure( + builder.Configuration.GetRequiredSection(WebsiteOptions.SectionName)); + + builder.Services.AddTransient(); + + builder.Services.AddTransient(); + builder.Services.AddTransient(); + builder.Services.AddTransient(); + builder.Services.AddTransient(); + builder.Services.Configure( + builder.Configuration.GetSection(StripeOptions.ConfigurationSection)); + + builder.Services.Configure( + builder.Configuration.GetSection(EmailerOptions.ConfigurationSection)); + builder.Services.AddTransient(); + //builder.Services.AddTransient(); + + builder.Services.AddHttpClient(); + + return builder; + } +} diff --git a/backend/Infrastructure/Emailer/Configuration/EmailerOptions.cs b/backend/Infrastructure/Emailer/Configuration/EmailerOptions.cs new file mode 100644 index 0000000..fc82315 --- /dev/null +++ b/backend/Infrastructure/Emailer/Configuration/EmailerOptions.cs @@ -0,0 +1,9 @@ +namespace Hutopy.Infrastructure.Emailer.Configuration; + +public class EmailerOptions +{ + public const string ConfigurationSection = "Emailer"; + + public string ApiKey { get; set; } = default!; + public string FromEmail { get; set; } = default!; +} diff --git a/backend/Infrastructure/Emailer/Contracts/IEmailSender.cs b/backend/Infrastructure/Emailer/Contracts/IEmailSender.cs new file mode 100644 index 0000000..f9107ac --- /dev/null +++ b/backend/Infrastructure/Emailer/Contracts/IEmailSender.cs @@ -0,0 +1,6 @@ +namespace Hutopy.Infrastructure.Emailer.Contracts; + +public interface IEmailSender +{ + Task SendEmailAsync(string email, string subject, string message); +} diff --git a/backend/src/Web/Features/Users/Services/IEmailSender.cs b/backend/Infrastructure/Emailer/Services/LoggerEmailSender.cs similarity index 61% rename from backend/src/Web/Features/Users/Services/IEmailSender.cs rename to backend/Infrastructure/Emailer/Services/LoggerEmailSender.cs index 56151b8..7c8c46f 100644 --- a/backend/src/Web/Features/Users/Services/IEmailSender.cs +++ b/backend/Infrastructure/Emailer/Services/LoggerEmailSender.cs @@ -1,22 +1,15 @@ -using System.Threading.Tasks; +using Hutopy.Infrastructure.Emailer.Contracts; -namespace Hutopy.Web.Features.Users.Services; +namespace Hutopy.Infrastructure.Emailer.Services; -public interface IEmailSender -{ - Task SendEmailAsync(string email, string subject, string message); -} - -public class EmailSender(ILogger logger) +public class LoggerEmailSender(ILogger logger) : IEmailSender { - public async Task SendEmailAsync(string email, string subject, string message) { - try + try { logger.LogInformation("Sending email to {Email} with subject: {Subject}", email, subject); - // TODO: Implement actual email sending logic await Task.Delay(1000); logger.LogInformation("Email sent successfully to {Email}", email); } diff --git a/backend/Infrastructure/Emailer/Services/PostmarkEmailSender.cs b/backend/Infrastructure/Emailer/Services/PostmarkEmailSender.cs new file mode 100644 index 0000000..4a14252 --- /dev/null +++ b/backend/Infrastructure/Emailer/Services/PostmarkEmailSender.cs @@ -0,0 +1,37 @@ +using Hutopy.Infrastructure.Emailer.Configuration; +using Hutopy.Infrastructure.Emailer.Contracts; +using Microsoft.Extensions.Options; +using PostmarkDotNet; + +namespace Hutopy.Infrastructure.Emailer.Services; + +public class PostmarkEmailSender : IEmailSender +{ + private readonly PostmarkClient _client; + private readonly EmailerOptions _options; + + public PostmarkEmailSender(IOptions options) + { + _options = options.Value; + _client = new PostmarkClient(_options.ApiKey); + } + + public async Task SendEmailAsync(string email, string subject, string message) + { + PostmarkResponse? sendResult = await _client.SendMessageAsync(new PostmarkMessage + { + From = _options.FromEmail, + To = email, + Subject = subject, + HtmlBody = message, + TrackOpens = true, + MessageStream = "outbound" // Optional: use "broadcast" for bulk + }); + + if (sendResult.Status != PostmarkStatus.Success) + { + throw new InvalidOperationException( + $"Postmark failed to send email: {sendResult.Message}"); + } + } +} diff --git a/backend/Infrastructure/Emailer/Services/ResendEmailSender.cs b/backend/Infrastructure/Emailer/Services/ResendEmailSender.cs new file mode 100644 index 0000000..b400dd4 --- /dev/null +++ b/backend/Infrastructure/Emailer/Services/ResendEmailSender.cs @@ -0,0 +1,46 @@ +using System.Net.Http.Headers; +using System.Text; +using System.Text.Json; +using Hutopy.Infrastructure.Emailer.Configuration; +using Hutopy.Infrastructure.Emailer.Contracts; +using Microsoft.Extensions.Options; + +namespace Hutopy.Infrastructure.Emailer.Services; + +public class ResendEmailSender : IEmailSender +{ + private static readonly Uri EndpointUri = new("https://api.resend.com/emails"); + private readonly HttpClient _httpClient; + private readonly EmailerOptions _options; + + public ResendEmailSender( + IHttpClientFactory httpClientFactory, + IOptions options) + { + _httpClient = httpClientFactory.CreateClient(); + _options = options.Value; + + _httpClient.DefaultRequestHeaders.Authorization = + new AuthenticationHeaderValue("Bearer", _options.ApiKey); + + _httpClient.DefaultRequestHeaders.Accept.Add( + new MediaTypeWithQualityHeaderValue("application/json")); + } + + public async Task SendEmailAsync(string toEmail, string subject, string htmlMessage) + { + var payload = new { from = _options.FromEmail, to = toEmail, subject, html = htmlMessage }; + + string json = JsonSerializer.Serialize(payload); + StringContent content = new(json, Encoding.UTF8, "application/json"); + + HttpResponseMessage response = await _httpClient.PostAsync(EndpointUri, content); + + if (!response.IsSuccessStatusCode) + { + string body = await response.Content.ReadAsStringAsync(); + throw new InvalidOperationException( + $"Resend email failed: {response.StatusCode} - {body}"); + } + } +} diff --git a/backend/Infrastructure/Payments/Stripe/Configuration/StripeOptions.cs b/backend/Infrastructure/Payments/Stripe/Configuration/StripeOptions.cs new file mode 100644 index 0000000..8cfff03 --- /dev/null +++ b/backend/Infrastructure/Payments/Stripe/Configuration/StripeOptions.cs @@ -0,0 +1,14 @@ +using System.ComponentModel.DataAnnotations; + +namespace Hutopy.Infrastructure.Payments.Stripe.Configuration; + +public class StripeOptions +{ + public const string ConfigurationSection = "Stripe"; + + [Required] public required string SecretKey { get; init; } + + [Required] public required string WebhookSecret { get; init; } + + [Required] [Range(0, 1)] public required decimal HutopyRate { get; init; } +} diff --git a/backend/Infrastructure/Payments/Stripe/Services/MembershipCancellationProcessor.cs b/backend/Infrastructure/Payments/Stripe/Services/MembershipCancellationProcessor.cs new file mode 100644 index 0000000..4d8725d --- /dev/null +++ b/backend/Infrastructure/Payments/Stripe/Services/MembershipCancellationProcessor.cs @@ -0,0 +1,28 @@ +using Hutopy.Modules.Memberships.Contracts; +using Stripe; + +namespace Hutopy.Infrastructure.Payments.Stripe.Services; + +public sealed class MembershipCancellationProcessor + : IMembershipCancellationProcessor +{ + public async Task CancelAsync( + string subscriptionId, + CancellationToken ct = default) + { + SubscriptionService subscriptionService = new(); + + // Stripe - Cancel Subscription immediately + // var subscription = await subscriptionService.CancelAsync( + // subscriptionId, + // cancellationToken: ct); + + // Stripe - Cancel Subscription AtPeriodEnd + Subscription? subscription = await subscriptionService.UpdateAsync( + subscriptionId, + new SubscriptionUpdateOptions { CancelAtPeriodEnd = true }, + cancellationToken: ct); + + return subscription.CancelAt ?? subscription.CanceledAt; + } +} diff --git a/backend/Infrastructure/Payments/Stripe/Services/MembershipPaymentProcessor.cs b/backend/Infrastructure/Payments/Stripe/Services/MembershipPaymentProcessor.cs new file mode 100644 index 0000000..fd54310 --- /dev/null +++ b/backend/Infrastructure/Payments/Stripe/Services/MembershipPaymentProcessor.cs @@ -0,0 +1,65 @@ +using Hutopy.Infrastructure.Payments.Stripe.Configuration; +using Hutopy.Modules.Creators.Contracts; +using Hutopy.Modules.Memberships.Contracts; +using Microsoft.Extensions.Options; +using Stripe; +using Stripe.Checkout; + +namespace Hutopy.Infrastructure.Payments.Stripe.Services; + +public class MembershipPaymentProcessor( + IOptions stripeOptions) +: IMembershipPaymentProcessor +{ + public async Task CreateCheckoutSessionAsync( + Guid userId, + CreatorReference creatorReference, + Guid tierId, + string priceId, + string successUrl, + string cancelUrl) + { + StripeConfiguration.ApiKey = stripeOptions.Value.SecretKey; + + // 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", userId.ToString() } } + }); + + // Create Checkout Session for the subscription + var sessionService = new SessionService(); + var session = await sessionService.CreateAsync( + new SessionCreateOptions + { + Customer = customer.Id, + PaymentMethodTypes = ["card"], + LineItems = + [ + new SessionLineItemOptions { Price = priceId, Quantity = 1 } + ], + Mode = "subscription", + SubscriptionData = new SessionSubscriptionDataOptions + { + ApplicationFeePercent = stripeOptions.Value.HutopyRate, + TransferData = new SessionSubscriptionDataTransferDataOptions { Destination = creatorReference.StripeAccountId } + }, + SuccessUrl = successUrl, // Redirect after successful payment + CancelUrl = cancelUrl, // Redirect after canceled payment + Metadata = new Dictionary + { + { "userId", userId.ToString() }, + { "creatorId", creatorReference.Id.ToString() }, + { "creatorName", creatorReference.Name }, + { "tierId", tierId.ToString() } + } + }); + + return new MembershipCheckoutSession( + session.Id, + session.Url); + } + +} diff --git a/backend/Infrastructure/Payments/Stripe/Services/MembershipTierProcessor.cs b/backend/Infrastructure/Payments/Stripe/Services/MembershipTierProcessor.cs new file mode 100644 index 0000000..e2889e9 --- /dev/null +++ b/backend/Infrastructure/Payments/Stripe/Services/MembershipTierProcessor.cs @@ -0,0 +1,43 @@ +using Hutopy.Infrastructure.Payments.Stripe.Configuration; +using Hutopy.Modules.Memberships.Contracts; +using Microsoft.Extensions.Options; +using Stripe; + +namespace Hutopy.Infrastructure.Payments.Stripe.Services; + +public sealed class MembershipTierProcessor( + IOptions stripeOptions) + : IMembershipTierProcessor +{ + public async Task CreateAsync( + Guid creatorId, + Guid tierId, + string productName, + string currencyCode, + decimal amount) + { + StripeConfiguration.ApiKey = stripeOptions.Value.SecretKey; + + // Create the product + var productService = new ProductService(); + var product = await productService.CreateAsync( + new ProductCreateOptions + { + Name = productName, + Metadata = { { "creatorId", creatorId.ToString() }, { "tierId", tierId.ToString() } } + }); + + // Create the price for the product + var priceService = new PriceService(); + await priceService.CreateAsync( + new PriceCreateOptions + { + Product = product.Id, + UnitAmountDecimal = amount * 100, // Convert amount to cents + Currency = currencyCode, + Recurring = new PriceRecurringOptions { Interval = "month" } + }); + + return product.Id; + } +} diff --git a/backend/Infrastructure/Payments/Stripe/Services/StripeTipProcessor.cs b/backend/Infrastructure/Payments/Stripe/Services/StripeTipProcessor.cs new file mode 100644 index 0000000..c335cd7 --- /dev/null +++ b/backend/Infrastructure/Payments/Stripe/Services/StripeTipProcessor.cs @@ -0,0 +1,78 @@ +using Hutopy.Infrastructure.Payments.Stripe.Configuration; +using Hutopy.Modules.Creators.Contracts; +using Hutopy.Modules.Tipping.Contracts; +using Microsoft.Extensions.Options; +using Stripe; +using Stripe.Checkout; + +namespace Hutopy.Infrastructure.Payments.Stripe.Services; + +public class StripeTipProcessor( + IOptions stripeOptions) + : ITipProcessor +{ + public async Task CreateCheckoutSessionAsync( + Guid tipId, + CreatorReference creator, + decimal amount, + string currency, + string message, + string successUrl, + string cancelUrl, + CancellationToken ct = default) + { + StripeConfiguration.ApiKey = stripeOptions.Value.SecretKey; + + // Create Stripe customer for the user if not already created + var customerService = new CustomerService(); + var customer = await customerService.CreateAsync( + new CustomerCreateOptions(), + cancellationToken: ct); + + // Create paymentIntent for the user + var sessionService = new SessionService(); + var session = await sessionService.CreateAsync( + new SessionCreateOptions + { + ClientReferenceId = tipId.ToString(), + Customer = customer.Id, + PaymentMethodTypes = ["card"], + LineItems = + [ + new SessionLineItemOptions + { + PriceData = new SessionLineItemPriceDataOptions + { + Currency = currency, + UnitAmountDecimal = amount, // Amount in cents + ProductData = new SessionLineItemPriceDataProductDataOptions + { + Name = $"Tip for {creator.Name}", // or any descriptive name for the tip + Metadata = new Dictionary { { "creatorId", creator.Id.ToString() } } + } + }, + Quantity = 1 + } + ], + Mode = "payment", + PaymentIntentData = new SessionPaymentIntentDataOptions + { + ApplicationFeeAmount = + Convert.ToInt64(amount * 100 * stripeOptions.Value.HutopyRate), // Platform fee + TransferData = new SessionPaymentIntentDataTransferDataOptions + { + Destination = creator.StripeAccountId // Creator's Stripe account ID + } + }, + SuccessUrl = successUrl, // Redirect after successful payment + CancelUrl = cancelUrl, // Redirect after canceled payment + Metadata = new Dictionary + { + { "creatorId", creator.Id.ToString() }, { "creatorName", creator.Name }, { "message", message }, + } + }, + cancellationToken: ct); + + return new TipCheckoutSession(session.Id, session.Url); + } +} diff --git a/backend/src/Web/Common/Security/ClaimsPrincipalExtensions.cs b/backend/Infrastructure/Security/ClaimsPrincipalExtensions.cs similarity index 83% rename from backend/src/Web/Common/Security/ClaimsPrincipalExtensions.cs rename to backend/Infrastructure/Security/ClaimsPrincipalExtensions.cs index 07ac0c3..3e29e5c 100644 --- a/backend/src/Web/Common/Security/ClaimsPrincipalExtensions.cs +++ b/backend/Infrastructure/Security/ClaimsPrincipalExtensions.cs @@ -1,6 +1,6 @@ using System.Security.Claims; -namespace Hutopy.Web.Common.Security; +namespace Hutopy.Infrastructure.Security; public static class ClaimsPrincipalExtensions { @@ -43,8 +43,7 @@ public static class ClaimsPrincipalExtensions { var claim = claims.FindFirst(key); - if (claim is null) return default; - return claims.GetRequiredClaim(key); + return claim is null ? null : claims.GetRequiredClaim(key); } private static object GetRequiredClaim(this ClaimsPrincipal claims, string key) @@ -53,11 +52,6 @@ public static class ClaimsPrincipalExtensions if (claim is null) throw new MissingClaimException(key); - if (typeof(TValue) == typeof(Guid)) - { - return Guid.Parse(claim.Value); - } - - return Convert.ChangeType(claim.Value, typeof(TValue)); + return typeof(TValue) == typeof(Guid) ? Guid.Parse(claim.Value) : Convert.ChangeType(claim.Value, typeof(TValue)); } } diff --git a/backend/src/Web/Common/Security/GenerateJwtToken.cs b/backend/Infrastructure/Security/GenerateJwtToken.cs similarity index 86% rename from backend/src/Web/Common/Security/GenerateJwtToken.cs rename to backend/Infrastructure/Security/GenerateJwtToken.cs index ed301b6..d03402c 100644 --- a/backend/src/Web/Common/Security/GenerateJwtToken.cs +++ b/backend/Infrastructure/Security/GenerateJwtToken.cs @@ -3,7 +3,7 @@ using System.Security.Claims; using System.Text; using Microsoft.IdentityModel.Tokens; -namespace Hutopy.Web.Common.Security; +namespace Hutopy.Infrastructure.Security; public static class JwtTokenHelper { @@ -22,23 +22,22 @@ public static class JwtTokenHelper var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key)); var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256); - var claims = new List(new[] - { + var claims = new List([ new Claim(JwtRegisteredClaimNames.Sub, userId), 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) - }); + ]); if (alias is not null) { - claims.Add(new(KnownClaims.Alias, alias)); + claims.Add(new Claim(KnownClaims.Alias, alias)); } if (portraitUrl is not null) { - claims.Add(new(KnownClaims.PortraitUrl, portraitUrl)); + claims.Add(new Claim(KnownClaims.PortraitUrl, portraitUrl)); } var token = new JwtSecurityToken( diff --git a/backend/src/Web/Common/Security/KnownClaims.cs b/backend/Infrastructure/Security/KnownClaims.cs similarity index 75% rename from backend/src/Web/Common/Security/KnownClaims.cs rename to backend/Infrastructure/Security/KnownClaims.cs index 9c33719..9bcb515 100644 --- a/backend/src/Web/Common/Security/KnownClaims.cs +++ b/backend/Infrastructure/Security/KnownClaims.cs @@ -1,4 +1,4 @@ -namespace Hutopy.Web.Common.Security; +namespace Hutopy.Infrastructure.Security; public static class KnownClaims { diff --git a/backend/Infrastructure/Security/MissingClaimException.cs b/backend/Infrastructure/Security/MissingClaimException.cs new file mode 100644 index 0000000..f7b49ce --- /dev/null +++ b/backend/Infrastructure/Security/MissingClaimException.cs @@ -0,0 +1,5 @@ +namespace Hutopy.Infrastructure.Security; + +public class MissingClaimException( + string claimName) + : Exception($"Claim '{claimName}' is missing."); diff --git a/backend/src/Web/Common/Security/PasswordGenerator.cs b/backend/Infrastructure/Security/PasswordGenerator.cs similarity index 98% rename from backend/src/Web/Common/Security/PasswordGenerator.cs rename to backend/Infrastructure/Security/PasswordGenerator.cs index 715243a..57c15d6 100644 --- a/backend/src/Web/Common/Security/PasswordGenerator.cs +++ b/backend/Infrastructure/Security/PasswordGenerator.cs @@ -1,7 +1,7 @@ using System.Security.Cryptography; using System.Text; -namespace Hutopy.Web.Common.Security; +namespace Hutopy.Infrastructure.Security; // If we need to add special characters we can alternate between 2 pools. public static class PasswordGenerator diff --git a/backend/src/Web/Common/Security/RefreshTokenGenerator.cs b/backend/Infrastructure/Security/RefreshTokenGenerator.cs similarity index 62% rename from backend/src/Web/Common/Security/RefreshTokenGenerator.cs rename to backend/Infrastructure/Security/RefreshTokenGenerator.cs index cad0a93..69d6e8e 100644 --- a/backend/src/Web/Common/Security/RefreshTokenGenerator.cs +++ b/backend/Infrastructure/Security/RefreshTokenGenerator.cs @@ -1,14 +1,13 @@ using System.Security.Cryptography; -namespace Hutopy.Web.Common.Security; +namespace Hutopy.Infrastructure.Security; public static class RefreshTokenGenerator { public static string Next() { var randomNumber = new byte[32]; - using var rng = RandomNumberGenerator.Create(); - rng.GetBytes(randomNumber); + RandomNumberGenerator.Fill(randomNumber); return Convert.ToBase64String(randomNumber); } -} +} diff --git a/backend/src/Web/Common/YouTube/YouTubeUrlHelper.cs b/backend/Infrastructure/YouTube/YouTubeUrlHelper.cs similarity index 98% rename from backend/src/Web/Common/YouTube/YouTubeUrlHelper.cs rename to backend/Infrastructure/YouTube/YouTubeUrlHelper.cs index 9e1d0be..a0db920 100644 --- a/backend/src/Web/Common/YouTube/YouTubeUrlHelper.cs +++ b/backend/Infrastructure/YouTube/YouTubeUrlHelper.cs @@ -1,6 +1,6 @@ using System.Text.RegularExpressions; -namespace Hutopy.Web.Common.YouTube; +namespace Hutopy.Infrastructure.YouTube; public static class YouTubeUrlHelper { diff --git a/backend/Modules/Contents/Data/Album.cs b/backend/Modules/Contents/Data/Album.cs new file mode 100644 index 0000000..430f912 --- /dev/null +++ b/backend/Modules/Contents/Data/Album.cs @@ -0,0 +1,11 @@ +using System.ComponentModel.DataAnnotations; +using Hutopy.Common.Domain; + +namespace Hutopy.Modules.Contents.Data; + +public class Album : Entity +{ + public bool IsDeleted { get; private set; } // private set → EF updates it + [MaxLength(255)] public required string Title { get; set; } + public IList Photos { get; set; } = new List(); +} diff --git a/backend/Modules/Contents/Data/AlbumPhoto.cs b/backend/Modules/Contents/Data/AlbumPhoto.cs new file mode 100644 index 0000000..70b2454 --- /dev/null +++ b/backend/Modules/Contents/Data/AlbumPhoto.cs @@ -0,0 +1,15 @@ +using System.ComponentModel.DataAnnotations; +using Hutopy.Common.Domain; + +namespace Hutopy.Modules.Contents.Data; + +public class AlbumPhoto : Entity +{ + public bool IsDeleted { get; private set; } // private set → EF updates it + public Guid AlbumId { get; set; } + public Album Album { get; init; } = null!; + [MaxLength(2048)] public required string OriginalUrl { get; set; } + [MaxLength(2048)] public required string ThumbnailUrl { get; set; } + [MaxLength(256)] public string? Caption { get; set; } + public int Order { get; set; } +} diff --git a/backend/Modules/Contents/Data/ContentsDbContext.cs b/backend/Modules/Contents/Data/ContentsDbContext.cs new file mode 100644 index 0000000..570da43 --- /dev/null +++ b/backend/Modules/Contents/Data/ContentsDbContext.cs @@ -0,0 +1,56 @@ +namespace Hutopy.Modules.Contents.Data; + +public class ContentsDbContext( + DbContextOptions options) + : DbContext(options) +{ + public const string SchemaName = "Content"; + + public DbSet Albums => Set(); + public DbSet AlbumPhotos => Set(); + + protected override void OnModelCreating( + ModelBuilder modelBuilder) + { + modelBuilder.HasDefaultSchema(SchemaName); + + // Album configuration + modelBuilder + .Entity() + .Property(c => c.CreatedAt) + .ValueGeneratedOnAdd() + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + modelBuilder + .Entity() + .Property(c => c.IsDeleted) + .HasComputedColumnSql("\"DeletedAt\" IS NOT NULL", stored: true); + + modelBuilder + .Entity() + .HasQueryFilter(a => !a.IsDeleted); + + // AlbumPhoto configuration + modelBuilder + .Entity() + .Property(c => c.CreatedAt) + .ValueGeneratedOnAdd() + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + modelBuilder + .Entity() + .Property(c => c.IsDeleted) + .HasComputedColumnSql("\"DeletedAt\" IS NOT NULL", stored: true); + + modelBuilder + .Entity() + .HasOne(ap => ap.Album) + .WithMany(a => a.Photos) + .HasForeignKey(ap => ap.AlbumId) + .IsRequired(); + + modelBuilder + .Entity() + .HasQueryFilter(ap => !ap.IsDeleted); + } +} diff --git a/backend/Modules/Contents/DependencyInjection.cs b/backend/Modules/Contents/DependencyInjection.cs new file mode 100644 index 0000000..336b92c --- /dev/null +++ b/backend/Modules/Contents/DependencyInjection.cs @@ -0,0 +1,27 @@ +using Hutopy.Modules.Contents.Data; + +namespace Hutopy.Modules.Contents; + +public static class DependencyInjection +{ + public static WebApplicationBuilder AddContentModule( + this WebApplicationBuilder builder, + Action? configureAction = null) + { + builder.Services.AddDbContext(configureAction); + + return builder; + } + + public static async Task UseContentModuleAsync( + this IApplicationBuilder app, + CancellationToken cancellationToken = default) + { + var scopeFactory = app.ApplicationServices.GetRequiredService(); + using var scope = scopeFactory.CreateScope(); + await using var context = scope.ServiceProvider.GetRequiredService(); + await context.Database.MigrateAsync(cancellationToken); + + return app; + } +} diff --git a/backend/src/Web/Features/Contents/Handlers/AddPhotoToAlbum.cs b/backend/Modules/Contents/Features/AddPhotoToAlbum.cs similarity index 94% rename from backend/src/Web/Features/Contents/Handlers/AddPhotoToAlbum.cs rename to backend/Modules/Contents/Features/AddPhotoToAlbum.cs index 19af320..eb3c61c 100644 --- a/backend/src/Web/Features/Contents/Handlers/AddPhotoToAlbum.cs +++ b/backend/Modules/Contents/Features/AddPhotoToAlbum.cs @@ -1,10 +1,10 @@ -using Hutopy.Web.Features.Contents.Data; -using Hutopy.Web.Common.Security; -using Hutopy.Web.Common.BlobStorage; +using Hutopy.Infrastructure.BlobStorage.Contracts; +using Hutopy.Infrastructure.Security; +using Hutopy.Modules.Contents.Data; using SixLabors.ImageSharp; using SixLabors.ImageSharp.Processing; -namespace Hutopy.Web.Features.Contents.Handlers; +namespace Hutopy.Modules.Contents.Features; [PublicAPI] public record AddPhotoToAlbumRequest( @@ -23,13 +23,13 @@ public record AddPhotoToAlbumResponse( public sealed class AddPhotoToAlbumRequestValidator : Validator { private const int MaxFileSizeBytes = 10 * 1024 * 1024; // 10MB - private static readonly string[] AllowedImageTypes = - { + private static readonly string[] AllowedImageTypes = + [ "image/jpeg", "image/png", "image/gif", "image/webp" - }; + ]; public AddPhotoToAlbumRequestValidator() { @@ -56,8 +56,8 @@ public sealed class AddPhotoToAlbumRequestValidator : Validator { private const int MaxThumbnailWidth = 500; @@ -133,7 +133,7 @@ public class AddPhotoToAlbumHandler( { await SendStringAsync("Invalid image format", 400, cancellation: ct); } - catch (Exception ex) + catch (Exception) { await SendStringAsync("Error processing image", 500, cancellation: ct); } diff --git a/backend/src/Web/Features/Contents/Handlers/CreateAlbum.cs b/backend/Modules/Contents/Features/CreateAlbum.cs similarity index 91% rename from backend/src/Web/Features/Contents/Handlers/CreateAlbum.cs rename to backend/Modules/Contents/Features/CreateAlbum.cs index 50b9f9b..c0593d7 100644 --- a/backend/src/Web/Features/Contents/Handlers/CreateAlbum.cs +++ b/backend/Modules/Contents/Features/CreateAlbum.cs @@ -1,7 +1,7 @@ -using Hutopy.Web.Features.Contents.Data; -using Hutopy.Web.Common.Security; +using Hutopy.Infrastructure.Security; +using Hutopy.Modules.Contents.Data; -namespace Hutopy.Web.Features.Contents.Handlers; +namespace Hutopy.Modules.Contents.Features; [PublicAPI] public record CreateAlbumRequest( @@ -34,7 +34,7 @@ public sealed class CreateAlbumRequestValidator : Validator [PublicAPI] public class CreateAlbumHandler( - ContentDbContext context) + ContentsDbContext context) : Endpoint { public override void Configure() diff --git a/backend/src/Web/Features/Contents/Handlers/GetAlbum.cs b/backend/Modules/Contents/Features/GetAlbum.cs similarity index 88% rename from backend/src/Web/Features/Contents/Handlers/GetAlbum.cs rename to backend/Modules/Contents/Features/GetAlbum.cs index 591a05a..e2ee516 100644 --- a/backend/src/Web/Features/Contents/Handlers/GetAlbum.cs +++ b/backend/Modules/Contents/Features/GetAlbum.cs @@ -1,10 +1,6 @@ -using FastEndpoints; -using JetBrains.Annotations; -using Microsoft.EntityFrameworkCore; -using Hutopy.Web.Features.Contents.Data; -using Hutopy.Web.Common.Security; +using Hutopy.Modules.Contents.Data; -namespace Hutopy.Web.Features.Contents.Handlers; +namespace Hutopy.Modules.Contents.Features; [PublicAPI] public record GetAlbumRequest( @@ -39,7 +35,7 @@ public sealed class GetAlbumRequestValidator : Validator [PublicAPI] public class GetAlbumHandler( - ContentDbContext context) + ContentsDbContext context) : Endpoint { public override void Configure() diff --git a/backend/src/Web/Features/Contents/Handlers/RemoveAlbum.cs b/backend/Modules/Contents/Features/RemoveAlbum.cs similarity index 85% rename from backend/src/Web/Features/Contents/Handlers/RemoveAlbum.cs rename to backend/Modules/Contents/Features/RemoveAlbum.cs index d635642..20c5e13 100644 --- a/backend/src/Web/Features/Contents/Handlers/RemoveAlbum.cs +++ b/backend/Modules/Contents/Features/RemoveAlbum.cs @@ -1,10 +1,7 @@ -using FastEndpoints; -using JetBrains.Annotations; -using Microsoft.EntityFrameworkCore; -using Hutopy.Web.Features.Contents.Data; -using Hutopy.Web.Common.Security; +using Hutopy.Infrastructure.Security; +using Hutopy.Modules.Contents.Data; -namespace Hutopy.Web.Features.Contents.Handlers; +namespace Hutopy.Modules.Contents.Features; [PublicAPI] public record RemoveAlbumRequest( @@ -23,7 +20,7 @@ public sealed class RemoveAlbumRequestValidator : Validator [PublicAPI] public class RemoveAlbumHandler( - ContentDbContext context) + ContentsDbContext context) : Endpoint { public override void Configure() @@ -66,4 +63,4 @@ public class RemoveAlbumHandler( await SendNoContentAsync(ct); } -} \ No newline at end of file +} diff --git a/backend/src/Web/Features/Contents/Handlers/RemovePhotoFromAlbum.cs b/backend/Modules/Contents/Features/RemovePhotoFromAlbum.cs similarity index 87% rename from backend/src/Web/Features/Contents/Handlers/RemovePhotoFromAlbum.cs rename to backend/Modules/Contents/Features/RemovePhotoFromAlbum.cs index 717bce5..d93370b 100644 --- a/backend/src/Web/Features/Contents/Handlers/RemovePhotoFromAlbum.cs +++ b/backend/Modules/Contents/Features/RemovePhotoFromAlbum.cs @@ -1,10 +1,7 @@ -using FastEndpoints; -using JetBrains.Annotations; -using Microsoft.EntityFrameworkCore; -using Hutopy.Web.Features.Contents.Data; -using Hutopy.Web.Common.Security; +using Hutopy.Infrastructure.Security; +using Hutopy.Modules.Contents.Data; -namespace Hutopy.Web.Features.Contents.Handlers; +namespace Hutopy.Modules.Contents.Features; [PublicAPI] public record RemovePhotoFromAlbumRequest( @@ -28,7 +25,7 @@ public sealed class RemovePhotoFromAlbumRequestValidator : Validator { public override void Configure() diff --git a/backend/Modules/Contents/Migrations/20250609212411_Initial.Designer.cs b/backend/Modules/Contents/Migrations/20250609212411_Initial.Designer.cs new file mode 100644 index 0000000..4f4f44c --- /dev/null +++ b/backend/Modules/Contents/Migrations/20250609212411_Initial.Designer.cs @@ -0,0 +1,134 @@ +// +using System; +using Hutopy.Modules.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.Modules.Contents.Migrations +{ + [DbContext(typeof(ContentsDbContext))] + [Migration("20250609212411_Initial")] + partial class Initial + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasDefaultSchema("Content") + .HasAnnotation("ProductVersion", "9.0.3") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Hutopy.Modules.Contents.Data.Album", 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("IsDeleted") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("boolean") + .HasComputedColumnSql("\"DeletedAt\" IS NOT NULL", true); + + b.Property("Title") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.ToTable("Albums", "Content"); + }); + + modelBuilder.Entity("Hutopy.Modules.Contents.Data.AlbumPhoto", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AlbumId") + .HasColumnType("uuid"); + + b.Property("Caption") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + 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("IsDeleted") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("boolean") + .HasComputedColumnSql("\"DeletedAt\" IS NOT NULL", true); + + b.Property("Order") + .HasColumnType("integer"); + + b.Property("OriginalUrl") + .IsRequired() + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.Property("ThumbnailUrl") + .IsRequired() + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.HasKey("Id"); + + b.HasIndex("AlbumId"); + + b.ToTable("AlbumPhotos", "Content"); + }); + + modelBuilder.Entity("Hutopy.Modules.Contents.Data.AlbumPhoto", b => + { + b.HasOne("Hutopy.Modules.Contents.Data.Album", "Album") + .WithMany("Photos") + .HasForeignKey("AlbumId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Album"); + }); + + modelBuilder.Entity("Hutopy.Modules.Contents.Data.Album", b => + { + b.Navigation("Photos"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/backend/src/Web/Features/Contents/Data/Migrations/20250423173651_AddAlbumAndPhotos.cs b/backend/Modules/Contents/Migrations/20250609212411_Initial.cs similarity index 83% rename from backend/src/Web/Features/Contents/Data/Migrations/20250423173651_AddAlbumAndPhotos.cs rename to backend/Modules/Contents/Migrations/20250609212411_Initial.cs index 5226759..1b9c57c 100644 --- a/backend/src/Web/Features/Contents/Data/Migrations/20250423173651_AddAlbumAndPhotos.cs +++ b/backend/Modules/Contents/Migrations/20250609212411_Initial.cs @@ -3,28 +3,29 @@ using Microsoft.EntityFrameworkCore.Migrations; #nullable disable -namespace Hutopy.Web.Features.Contents.Data.Migrations +namespace Hutopy.Modules.Contents.Migrations { /// - public partial class AddAlbumAndPhotos : Migration + public partial class Initial : Migration { /// protected override void Up(MigrationBuilder migrationBuilder) { + migrationBuilder.EnsureSchema( + name: "Content"); + migrationBuilder.CreateTable( name: "Albums", schema: "Content", columns: table => new { Id = table.Column(type: "uuid", nullable: false), + IsDeleted = table.Column(type: "boolean", nullable: false, computedColumnSql: "\"DeletedAt\" IS NOT NULL", stored: true), + Title = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), CreatedBy = table.Column(type: "uuid", nullable: false), CreatedAt = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "CURRENT_TIMESTAMP"), DeletedBy = table.Column(type: "uuid", nullable: true), - DeletedAt = table.Column(type: "timestamp with time zone", nullable: true), - IsDeleted = table.Column(type: "boolean", nullable: false, computedColumnSql: "\"DeletedAt\" IS NOT NULL", stored: true), - Title = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), - Description = table.Column(type: "character varying(1000)", maxLength: 1000, nullable: true), - CoverPhotoUrl = table.Column(type: "character varying(2048)", maxLength: 2048, nullable: true) + DeletedAt = table.Column(type: "timestamp with time zone", nullable: true) }, constraints: table => { @@ -37,15 +38,16 @@ namespace Hutopy.Web.Features.Contents.Data.Migrations columns: table => new { Id = table.Column(type: "uuid", nullable: false), + IsDeleted = table.Column(type: "boolean", nullable: false, computedColumnSql: "\"DeletedAt\" IS NOT NULL", stored: true), + AlbumId = table.Column(type: "uuid", nullable: false), + OriginalUrl = table.Column(type: "character varying(2048)", maxLength: 2048, nullable: false), + ThumbnailUrl = table.Column(type: "character varying(2048)", maxLength: 2048, nullable: false), + Caption = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), + Order = table.Column(type: "integer", nullable: false), CreatedBy = table.Column(type: "uuid", nullable: false), CreatedAt = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "CURRENT_TIMESTAMP"), DeletedBy = table.Column(type: "uuid", nullable: true), - DeletedAt = table.Column(type: "timestamp with time zone", nullable: true), - IsDeleted = table.Column(type: "boolean", nullable: false, computedColumnSql: "\"DeletedAt\" IS NOT NULL", stored: true), - AlbumId = table.Column(type: "uuid", nullable: false), - PhotoUrl = table.Column(type: "character varying(2048)", maxLength: 2048, nullable: false), - Caption = table.Column(type: "character varying(255)", maxLength: 255, nullable: true), - Order = table.Column(type: "integer", nullable: false) + DeletedAt = table.Column(type: "timestamp with time zone", nullable: true) }, constraints: table => { diff --git a/backend/Modules/Contents/Migrations/ContentsDbContextModelSnapshot.cs b/backend/Modules/Contents/Migrations/ContentsDbContextModelSnapshot.cs new file mode 100644 index 0000000..991cdae --- /dev/null +++ b/backend/Modules/Contents/Migrations/ContentsDbContextModelSnapshot.cs @@ -0,0 +1,131 @@ +// +using System; +using Hutopy.Modules.Contents.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Hutopy.Modules.Contents.Migrations +{ + [DbContext(typeof(ContentsDbContext))] + partial class ContentsDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasDefaultSchema("Content") + .HasAnnotation("ProductVersion", "9.0.3") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Hutopy.Modules.Contents.Data.Album", 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("IsDeleted") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("boolean") + .HasComputedColumnSql("\"DeletedAt\" IS NOT NULL", true); + + b.Property("Title") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.ToTable("Albums", "Content"); + }); + + modelBuilder.Entity("Hutopy.Modules.Contents.Data.AlbumPhoto", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AlbumId") + .HasColumnType("uuid"); + + b.Property("Caption") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + 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("IsDeleted") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("boolean") + .HasComputedColumnSql("\"DeletedAt\" IS NOT NULL", true); + + b.Property("Order") + .HasColumnType("integer"); + + b.Property("OriginalUrl") + .IsRequired() + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.Property("ThumbnailUrl") + .IsRequired() + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.HasKey("Id"); + + b.HasIndex("AlbumId"); + + b.ToTable("AlbumPhotos", "Content"); + }); + + modelBuilder.Entity("Hutopy.Modules.Contents.Data.AlbumPhoto", b => + { + b.HasOne("Hutopy.Modules.Contents.Data.Album", "Album") + .WithMany("Photos") + .HasForeignKey("AlbumId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Album"); + }); + + modelBuilder.Entity("Hutopy.Modules.Contents.Data.Album", b => + { + b.Navigation("Photos"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/backend/src/Web/Features/Contents/Handlers/Models/ContentModel.cs b/backend/Modules/Contents/Models/ContentModel.cs similarity index 82% rename from backend/src/Web/Features/Contents/Handlers/Models/ContentModel.cs rename to backend/Modules/Contents/Models/ContentModel.cs index b063c2e..874be1f 100644 --- a/backend/src/Web/Features/Contents/Handlers/Models/ContentModel.cs +++ b/backend/Modules/Contents/Models/ContentModel.cs @@ -1,4 +1,4 @@ -namespace Hutopy.Web.Features.Contents.Handlers.Models; +namespace Hutopy.Modules.Contents.Models; [PublicAPI] public class ContentModel @@ -15,5 +15,4 @@ public class ContentModel public string HtmlFileUrl { get; init; } = ""; public required string[]? Urls { get; init; } public string? ThumbnailUrl { get; init; } - public IList? Reactions { get; set; } = new List(); } diff --git a/backend/src/Web/Features/Contents/Handlers/Models/FollowModel.cs b/backend/Modules/Contents/Models/FollowModel.cs similarity index 67% rename from backend/src/Web/Features/Contents/Handlers/Models/FollowModel.cs rename to backend/Modules/Contents/Models/FollowModel.cs index 7f722f1..223941e 100644 --- a/backend/src/Web/Features/Contents/Handlers/Models/FollowModel.cs +++ b/backend/Modules/Contents/Models/FollowModel.cs @@ -1,4 +1,4 @@ -namespace Hutopy.Web.Features.Contents.Handlers.Models; +namespace Hutopy.Modules.Contents.Models; [PublicAPI] public record FollowModel( diff --git a/backend/Modules/Creators/Configuration/CreatorOptions.cs b/backend/Modules/Creators/Configuration/CreatorOptions.cs new file mode 100644 index 0000000..500720b --- /dev/null +++ b/backend/Modules/Creators/Configuration/CreatorOptions.cs @@ -0,0 +1,8 @@ +namespace Hutopy.Modules.Creators.Configuration; + +public class CreatorOptions +{ + public const string ConfigurationSection = "Creators"; + + public TimeSpan SlugReservationDuration { get; set; } +} diff --git a/backend/Modules/Creators/Contracts/CreatorReference.cs b/backend/Modules/Creators/Contracts/CreatorReference.cs new file mode 100644 index 0000000..1e3e5d2 --- /dev/null +++ b/backend/Modules/Creators/Contracts/CreatorReference.cs @@ -0,0 +1,9 @@ +namespace Hutopy.Modules.Creators.Contracts; + +public record CreatorReference( + Guid Id, + string Name, + string? PortraitUrl, + bool OnboardingComplete, + bool AcceptCharges, + string? StripeAccountId); diff --git a/backend/Modules/Creators/Contracts/ICreatorLookup.cs b/backend/Modules/Creators/Contracts/ICreatorLookup.cs new file mode 100644 index 0000000..8f3c4cd --- /dev/null +++ b/backend/Modules/Creators/Contracts/ICreatorLookup.cs @@ -0,0 +1,6 @@ +namespace Hutopy.Modules.Creators.Contracts; + +public interface ICreatorLookup +{ + Task GetCreatorAsync(Guid creatorId, CancellationToken cancellationToken = default); +} diff --git a/backend/Modules/Creators/Data/Creator.cs b/backend/Modules/Creators/Data/Creator.cs new file mode 100644 index 0000000..7f32a86 --- /dev/null +++ b/backend/Modules/Creators/Data/Creator.cs @@ -0,0 +1,32 @@ +using System.ComponentModel.DataAnnotations; + +namespace Hutopy.Modules.Creators.Data; + +public class Creator +{ + public Guid Id { get; set; } + + public Guid CreatedBy { get; set; } + public DateTimeOffset CreatedAt { get; init; } + public Guid? DeletedBy { get; set; } + public DateTimeOffset? DeletedAt { get; set; } + + /// + /// Soft‑delete flag (false by default, true once DeletedAt is set) + /// + public bool IsDeleted { get; private set; } // private set → EF updates it + + [MaxLength(2048)] public string? BannerUrl { get; set; } + [MaxLength(2048)] public string? PortraitUrl { get; set; } + public bool Verified { get; set; } + [MaxLength(256)] public required string Name { get; set; } + [MaxLength(128)] public required string Slug { get; set; } + [MaxLength(256)] public string? Title { get; set; } + + [MaxLength(21)] public string? StripeAccountId { get; set; } + public bool IsStripeDetailsSubmitted { get; set; } + public bool IsStripePayoutReady { get; set; } + public bool IsStripeChargesEnabled { get; set; } + public Socials Socials { get; set; } = new(); + public Presentation Presentation { get; set; } = new() { Description = "Welcome to my profile!" }; +} diff --git a/backend/Modules/Creators/Data/CreatorsDbContext.cs b/backend/Modules/Creators/Data/CreatorsDbContext.cs new file mode 100644 index 0000000..d9f347a --- /dev/null +++ b/backend/Modules/Creators/Data/CreatorsDbContext.cs @@ -0,0 +1,46 @@ +namespace Hutopy.Modules.Creators.Data; + +public class CreatorsDbContext( + DbContextOptions options) + : DbContext(options) +{ + public const string SchemaName = "Creators"; + + public DbSet Creators => Set(); + public DbSet Slugs => Set(); + + protected override void OnModelCreating( + ModelBuilder modelBuilder) + { + modelBuilder.HasDefaultSchema(SchemaName); + + modelBuilder + .Entity() + .Property(x => x.NormalizedName) + .HasComputedColumnSql("LOWER(\"Name\")", stored: true); + + modelBuilder + .Entity() + .HasIndex(x => x.NormalizedName) + .IsUnique(); + + modelBuilder + .Entity() + .Property(c => c.IsDeleted) + .HasComputedColumnSql("\"DeletedAt\" IS NOT NULL", stored: true); // bool + + modelBuilder + .Entity() + .OwnsOne(x => x.Socials) + .ToTable(nameof(Socials)); + + modelBuilder + .Entity() + .OwnsOne(x => x.Presentation) + .ToTable(nameof(Presentation)); + + modelBuilder + .Entity() + .HasQueryFilter(c => !c.IsDeleted); + } +} diff --git a/backend/Modules/Creators/Data/Presentation.cs b/backend/Modules/Creators/Data/Presentation.cs new file mode 100644 index 0000000..703f618 --- /dev/null +++ b/backend/Modules/Creators/Data/Presentation.cs @@ -0,0 +1,11 @@ +using System.ComponentModel.DataAnnotations; + +namespace Hutopy.Modules.Creators.Data; + +public class Presentation +{ + public string Description { get; set; } = null!; + [MaxLength(2048)] public string? VideoUrl { get; set; } + [MaxLength(256)] public string? PhoneNumber { get; set; } + [MaxLength(256)] public string? Email { get; set; } +} diff --git a/backend/src/Web/Features/Contents/Data/Slugs.cs b/backend/Modules/Creators/Data/Slugs.cs similarity index 90% rename from backend/src/Web/Features/Contents/Data/Slugs.cs rename to backend/Modules/Creators/Data/Slugs.cs index 803de2d..f42c5d2 100644 --- a/backend/src/Web/Features/Contents/Data/Slugs.cs +++ b/backend/Modules/Creators/Data/Slugs.cs @@ -1,6 +1,6 @@ using System.ComponentModel.DataAnnotations; -namespace Hutopy.Web.Features.Contents.Data; +namespace Hutopy.Modules.Creators.Data; public class Slugs { diff --git a/backend/Modules/Creators/Data/Socials.cs b/backend/Modules/Creators/Data/Socials.cs new file mode 100644 index 0000000..6d1a35f --- /dev/null +++ b/backend/Modules/Creators/Data/Socials.cs @@ -0,0 +1,15 @@ +using System.ComponentModel.DataAnnotations; + +namespace Hutopy.Modules.Creators.Data; + +public class Socials +{ + [MaxLength(2048)] public string? FacebookUrl { get; set; } + [MaxLength(2048)] public string? InstagramUrl { get; set; } + [MaxLength(2048)] public string? XUrl { get; set; } + [MaxLength(2048)] public string? LinkedInUrl { get; set; } + [MaxLength(2048)] public string? TikTokUrl { get; set; } + [MaxLength(2048)] public string? YoutubeUrl { get; set; } + [MaxLength(2048)] public string? RedditUrl { get; set; } + [MaxLength(2048)] public string? WebsiteUrl { get; set; } +} \ No newline at end of file diff --git a/backend/Modules/Creators/DependencyInjection.cs b/backend/Modules/Creators/DependencyInjection.cs new file mode 100644 index 0000000..5b33ce8 --- /dev/null +++ b/backend/Modules/Creators/DependencyInjection.cs @@ -0,0 +1,35 @@ +using Hutopy.Modules.Creators.Configuration; +using Hutopy.Modules.Creators.Contracts; +using Hutopy.Modules.Creators.Data; +using Hutopy.Modules.Creators.Services; + +namespace Hutopy.Modules.Creators; + +public static class DependencyInjection +{ + public static WebApplicationBuilder AddCreatorModule( + this WebApplicationBuilder builder, + Action? configureAction = null) + { + builder.Services.Configure( + builder.Configuration.GetSection(CreatorOptions.ConfigurationSection)); + builder.Services.AddScoped(); + + builder.Services.AddDbContext(configureAction); + builder.Services.AddTransient(); + + return builder; + } + + public static async Task UseCreatorModuleAsync( + this IApplicationBuilder app, + CancellationToken cancellationToken = default) + { + var scopeFactory = app.ApplicationServices.GetRequiredService(); + using var scope = scopeFactory.CreateScope(); + await using var context = scope.ServiceProvider.GetRequiredService(); + await context.Database.MigrateAsync(cancellationToken: cancellationToken); + + return app; + } +} diff --git a/backend/Modules/Creators/Features/ChangeBanner.cs b/backend/Modules/Creators/Features/ChangeBanner.cs new file mode 100644 index 0000000..90b91f4 --- /dev/null +++ b/backend/Modules/Creators/Features/ChangeBanner.cs @@ -0,0 +1,60 @@ +using Hutopy.Infrastructure.BlobStorage.Contracts; +using Hutopy.Modules.Creators.Data; + +namespace Hutopy.Modules.Creators.Features; + +[PublicAPI] +public static class ChangeBanner +{ + public record Request( + Guid CreatorId, + IFormFile File); + + public record Response( + string BlobUrl); + + public class Handler( + CreatorsDbContext context, + IBlobStorage blobStorage) + : Endpoint + { + public override void Configure() + { + Post("/api/creators/{CreatorId}/banner"); + Options(o => o.WithTags("Creators")); + AllowFileUploads(); + } + + public override async Task HandleAsync( + Request request, + CancellationToken ct) + { + var creator = await context + .Creators + .SingleOrDefaultAsync( + c => c.Id == request.CreatorId, + cancellationToken: ct); + + if (creator is null) + { + await SendNotFoundAsync(ct); + return; + } + + var blobUrl = await blobStorage.UploadFileAsync( + ContainerNames.Creators, + $"{request.CreatorId}/{SubDirectoryNames.Profile}/{CommonFileNames.BannerPicture}", + request.File.OpenReadStream(), + request.File.ContentType, + ct); + + creator.BannerUrl = $"{blobUrl}?t={DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()}"; + + await context.SaveChangesAsync(ct); + + await SendOkAsync( + new Response(blobUrl), + ct); + } + } +} diff --git a/backend/src/Web/Features/Contents/Handlers/ChangeEmail.cs b/backend/Modules/Creators/Features/ChangeEmail.cs similarity index 86% rename from backend/src/Web/Features/Contents/Handlers/ChangeEmail.cs rename to backend/Modules/Creators/Features/ChangeEmail.cs index aabe6d1..80a1556 100644 --- a/backend/src/Web/Features/Contents/Handlers/ChangeEmail.cs +++ b/backend/Modules/Creators/Features/ChangeEmail.cs @@ -1,10 +1,7 @@ -using FluentValidation; -using JetBrains.Annotations; -using Microsoft.EntityFrameworkCore; -using Hutopy.Web.Features.Contents.Data; -using Hutopy.Web.Common.Security; +using Hutopy.Infrastructure.Security; +using Hutopy.Modules.Creators.Data; -namespace Hutopy.Web.Features.Contents.Handlers; +namespace Hutopy.Modules.Creators.Features; [PublicAPI] public record ChangeEmailRequest( @@ -28,7 +25,7 @@ public sealed class ChangeEmailRequestValidator : Validator [PublicAPI] public class ChangeEmailHandler( - ContentDbContext context) + CreatorsDbContext context) : Endpoint { public override void Configure() @@ -67,4 +64,4 @@ public class ChangeEmailHandler( await SendOkAsync(ct); } -} \ No newline at end of file +} diff --git a/backend/src/Web/Features/Contents/Handlers/ChangeLogo.cs b/backend/Modules/Creators/Features/ChangeLogo.cs similarity index 89% rename from backend/src/Web/Features/Contents/Handlers/ChangeLogo.cs rename to backend/Modules/Creators/Features/ChangeLogo.cs index d45fd29..2aa7aea 100644 --- a/backend/src/Web/Features/Contents/Handlers/ChangeLogo.cs +++ b/backend/Modules/Creators/Features/ChangeLogo.cs @@ -1,7 +1,7 @@ -using Hutopy.Web.Common.BlobStorage; -using Hutopy.Web.Features.Contents.Data; +using Hutopy.Infrastructure.BlobStorage.Contracts; +using Hutopy.Modules.Creators.Data; -namespace Hutopy.Web.Features.Contents.Handlers; +namespace Hutopy.Modules.Creators.Features; [PublicAPI] public record ChangeLogoRequest( @@ -29,8 +29,8 @@ public sealed class ChangeLogoRequestValidator : Validator [PublicAPI] public class ChangeLogoHandler( - ContentDbContext context, - AzureBlobStorage blobStorage) + CreatorsDbContext context, + IBlobStorage blobStorage) : Endpoint { public override void Configure() diff --git a/backend/src/Web/Features/Contents/Handlers/ChangeName.cs b/backend/Modules/Creators/Features/ChangeName.cs similarity index 89% rename from backend/src/Web/Features/Contents/Handlers/ChangeName.cs rename to backend/Modules/Creators/Features/ChangeName.cs index 77fff48..9a737c0 100644 --- a/backend/src/Web/Features/Contents/Handlers/ChangeName.cs +++ b/backend/Modules/Creators/Features/ChangeName.cs @@ -1,6 +1,6 @@ -using Hutopy.Web.Features.Contents.Data; +using Hutopy.Modules.Creators.Data; -namespace Hutopy.Web.Features.Contents.Handlers; +namespace Hutopy.Modules.Creators.Features; [PublicAPI] public record ChangeNameRequest( @@ -21,7 +21,7 @@ internal sealed class ChangeNameRequestValidator [PublicAPI] public class ChangeNameHandler( - ContentDbContext context) + CreatorsDbContext context) : Endpoint { public override void Configure() diff --git a/backend/src/Web/Features/Contents/Handlers/ChangePhoneNumber.cs b/backend/Modules/Creators/Features/ChangePhoneNumber.cs similarity index 86% rename from backend/src/Web/Features/Contents/Handlers/ChangePhoneNumber.cs rename to backend/Modules/Creators/Features/ChangePhoneNumber.cs index bd6fcb1..70c49f0 100644 --- a/backend/src/Web/Features/Contents/Handlers/ChangePhoneNumber.cs +++ b/backend/Modules/Creators/Features/ChangePhoneNumber.cs @@ -1,10 +1,7 @@ -using FluentValidation; -using JetBrains.Annotations; -using Microsoft.EntityFrameworkCore; -using Hutopy.Web.Features.Contents.Data; -using Hutopy.Web.Common.Security; +using Hutopy.Infrastructure.Security; +using Hutopy.Modules.Creators.Data; -namespace Hutopy.Web.Features.Contents.Handlers; +namespace Hutopy.Modules.Creators.Features; [PublicAPI] public record ChangePhoneNumberRequest( @@ -28,7 +25,7 @@ public sealed class ChangePhoneNumberRequestValidator : Validator { public override void Configure() @@ -67,4 +64,4 @@ public class ChangePhoneNumberHandler( await SendOkAsync(ct); } -} \ No newline at end of file +} diff --git a/backend/src/Web/Features/Contents/Handlers/ChangePresentationInfos.cs b/backend/Modules/Creators/Features/ChangePresentationInfos.cs similarity index 92% rename from backend/src/Web/Features/Contents/Handlers/ChangePresentationInfos.cs rename to backend/Modules/Creators/Features/ChangePresentationInfos.cs index c9a880d..626ab9f 100644 --- a/backend/src/Web/Features/Contents/Handlers/ChangePresentationInfos.cs +++ b/backend/Modules/Creators/Features/ChangePresentationInfos.cs @@ -1,7 +1,7 @@ -using Hutopy.Web.Common.YouTube; -using Hutopy.Web.Features.Contents.Data; +using Hutopy.Infrastructure.YouTube; +using Hutopy.Modules.Creators.Data; -namespace Hutopy.Web.Features.Contents.Handlers; +namespace Hutopy.Modules.Creators.Features; [PublicAPI] public record ChangePresentationInfosRequest( @@ -32,7 +32,7 @@ public sealed class ChangePresentationInfosRequestValidator : Validator { public override void Configure() diff --git a/backend/src/Web/Features/Contents/Handlers/ChangeSlug.cs b/backend/Modules/Creators/Features/ChangeSlug.cs similarity index 94% rename from backend/src/Web/Features/Contents/Handlers/ChangeSlug.cs rename to backend/Modules/Creators/Features/ChangeSlug.cs index 78ef9ad..6244a55 100644 --- a/backend/src/Web/Features/Contents/Handlers/ChangeSlug.cs +++ b/backend/Modules/Creators/Features/ChangeSlug.cs @@ -1,7 +1,7 @@ -using Hutopy.Web.Common.Security; -using Hutopy.Web.Features.Contents.Data; +using Hutopy.Infrastructure.Security; +using Hutopy.Modules.Creators.Data; -namespace Hutopy.Web.Features.Contents.Handlers; +namespace Hutopy.Modules.Creators.Features; [PublicAPI] public record ChangeSlugRequest( @@ -26,7 +26,7 @@ internal sealed class ChangeSlugRequestValidator [PublicAPI] public class ChangeSlugHandler( - ContentDbContext context) + CreatorsDbContext context) : Endpoint { public override void Configure() diff --git a/backend/src/Web/Features/Contents/Handlers/ChangeSocials.cs b/backend/Modules/Creators/Features/ChangeSocials.cs similarity index 91% rename from backend/src/Web/Features/Contents/Handlers/ChangeSocials.cs rename to backend/Modules/Creators/Features/ChangeSocials.cs index 5b86c3e..4ccb9e3 100644 --- a/backend/src/Web/Features/Contents/Handlers/ChangeSocials.cs +++ b/backend/Modules/Creators/Features/ChangeSocials.cs @@ -1,6 +1,6 @@ -using Hutopy.Web.Features.Contents.Data; +using Hutopy.Modules.Creators.Data; -namespace Hutopy.Web.Features.Contents.Handlers; +namespace Hutopy.Modules.Creators.Features; [PublicAPI] public record ChangeSocialsRequest( @@ -16,7 +16,7 @@ public record ChangeSocialsRequest( [PublicAPI] public class ChangeSocialsHandler( - ContentDbContext context) + CreatorsDbContext context) : Endpoint { public override void Configure() diff --git a/backend/src/Web/Features/Contents/Handlers/ChangeTitle.cs b/backend/Modules/Creators/Features/ChangeTitle.cs similarity index 85% rename from backend/src/Web/Features/Contents/Handlers/ChangeTitle.cs rename to backend/Modules/Creators/Features/ChangeTitle.cs index 16a357a..fecb2dc 100644 --- a/backend/src/Web/Features/Contents/Handlers/ChangeTitle.cs +++ b/backend/Modules/Creators/Features/ChangeTitle.cs @@ -1,6 +1,6 @@ -using Hutopy.Web.Features.Contents.Data; +using Hutopy.Modules.Creators.Data; -namespace Hutopy.Web.Features.Contents.Handlers; +namespace Hutopy.Modules.Creators.Features; [PublicAPI] public record ChangeTitleRequest( @@ -9,7 +9,7 @@ public record ChangeTitleRequest( [PublicAPI] public class ChangeTitleHandler( - ContentDbContext context) + CreatorsDbContext context) : Endpoint { public override void Configure() diff --git a/backend/Modules/Creators/Features/CheckStatusStripe.cs b/backend/Modules/Creators/Features/CheckStatusStripe.cs new file mode 100644 index 0000000..3195572 --- /dev/null +++ b/backend/Modules/Creators/Features/CheckStatusStripe.cs @@ -0,0 +1,68 @@ +using Hutopy.Infrastructure.Payments.Stripe.Configuration; +using Hutopy.Infrastructure.Security; +using Hutopy.Modules.Creators.Data; +using Microsoft.Extensions.Options; +using Stripe; + +namespace Hutopy.Modules.Creators.Features; + +[PublicAPI] +public record CheckStatusStripeResponse( + bool IsStripeAccountPresent, + bool IsStripeOnboardingComplete, + bool IsStripeChargesEnabled, + bool IsStripePayoutReady +); + +public class CheckStatusStripeIdHandler( + IOptionsSnapshot stripeOptions, + CreatorsDbContext dbContext) + : EndpointWithoutRequest +{ + public override void Configure() + { + Post("/api/stripe/check-status"); + Options(o => o.WithTags("Creators")); + } + + public override async Task HandleAsync( + CancellationToken ct) + { + // 1. Get the creator's information + Guid creatorId = HttpContext.User.GetUserId(); + + // 2. Get or create the creator + Creator? creator = await dbContext.Creators.SingleOrDefaultAsync(c => c.Id == creatorId, ct); + if (creator is null) + { + await SendNotFoundAsync(ct); + return; + } + + // 3. The Creator is not being onboarded + if (string.IsNullOrWhiteSpace(creator.StripeAccountId)) + { + await SendErrorsAsync(cancellation: ct); + return; + } + + // 4. Update Creator's stripe account information + StripeConfiguration.ApiKey = stripeOptions.Value.SecretKey; + AccountService accountService = new(); + Account? account = await accountService.GetAsync(creator.StripeAccountId, cancellationToken: ct); + creator.IsStripePayoutReady = account.PayoutsEnabled; + creator.IsStripeChargesEnabled = account.ChargesEnabled; + creator.IsStripeDetailsSubmitted = account.DetailsSubmitted; + await dbContext.SaveChangesAsync(ct); + + // 6. Return the account link URL to the client + await SendOkAsync( + new CheckStatusStripeResponse( + creator.StripeAccountId != null, + creator.IsStripeDetailsSubmitted, + creator.IsStripeChargesEnabled, + creator.IsStripePayoutReady + ), + ct); + } +} diff --git a/backend/Modules/Creators/Features/ConnectStripe.cs b/backend/Modules/Creators/Features/ConnectStripe.cs new file mode 100644 index 0000000..2793878 --- /dev/null +++ b/backend/Modules/Creators/Features/ConnectStripe.cs @@ -0,0 +1,90 @@ +using Hutopy.Infrastructure.Configuration; +using Hutopy.Infrastructure.Payments.Stripe.Configuration; +using Hutopy.Infrastructure.Security; +using Hutopy.Modules.Creators.Data; +using Microsoft.Extensions.Options; +using Stripe; + +namespace Hutopy.Modules.Creators.Features; + +[PublicAPI] +public record ConnectStripeResponse( + string Url); + +public class ConnectStripeIdHandler( + IOptionsSnapshot websiteOptions, + IOptionsSnapshot stripeOptions, + CreatorsDbContext dbContext) + : EndpointWithoutRequest +{ + public override void Configure() + { + Post("/api/stripe/connect"); + Options(o => o.WithTags("Creators")); + } + + public override async Task HandleAsync( + CancellationToken ct) + { + // 1. Get the creator's information + Guid creatorId = HttpContext.User.GetUserId(); + string email = HttpContext.User.GetEmail(); + + // 2. Get or create the creator + Creator? creator = await dbContext + .Creators + .SingleOrDefaultAsync( + c => c.Id == creatorId, + ct); + + if (creator is null) + { + await SendNotFoundAsync(ct); + return; + } + + // 3. Create a Stripe account + StripeConfiguration.ApiKey = stripeOptions.Value.SecretKey; + AccountService accountService = new(); + if (string.IsNullOrWhiteSpace(creator.StripeAccountId)) + { + Account? account = await accountService.CreateAsync( + new AccountCreateOptions + { + Type = "express", + Capabilities = new AccountCapabilitiesOptions + { + Transfers = new AccountCapabilitiesTransfersOptions { Requested = true } + }, + Email = email + }, + cancellationToken: ct); + + // 5. Update the creator's Stripe account ID + creator.StripeAccountId = account.Id; + await dbContext.SaveChangesAsync(ct); + } + + // 4. Check if the creator already has a Stripe account + if (creator is { IsStripeDetailsSubmitted: true, IsStripeChargesEnabled: true, IsStripePayoutReady: true }) + { + await SendErrorsAsync(cancellation: ct); + return; + } + + // 5. Create an account link + AccountLinkService accountLinkService = new(); + AccountLink? accountLink = await accountLinkService.CreateAsync( + new AccountLinkCreateOptions + { + Account = creator.StripeAccountId, + RefreshUrl = $"{websiteOptions.Value.FrontendBaseUrl}/profile?stripe=retry", + ReturnUrl = $"{websiteOptions.Value.FrontendBaseUrl}/profile?stripe=complete", + Type = "account_onboarding" + }, + cancellationToken: ct); + + // 6. Return the account link URL to the client + await SendOkAsync(new ConnectStripeResponse(accountLink.Url), ct); + } +} diff --git a/backend/src/Web/Features/Contents/Handlers/CreateCreator.cs b/backend/Modules/Creators/Features/CreateCreator.cs similarity index 91% rename from backend/src/Web/Features/Contents/Handlers/CreateCreator.cs rename to backend/Modules/Creators/Features/CreateCreator.cs index 05ae23d..d32096d 100644 --- a/backend/src/Web/Features/Contents/Handlers/CreateCreator.cs +++ b/backend/Modules/Creators/Features/CreateCreator.cs @@ -1,7 +1,7 @@ -using Hutopy.Web.Common.Security; -using Hutopy.Web.Features.Contents.Data; +using Hutopy.Infrastructure.Security; +using Hutopy.Modules.Creators.Data; -namespace Hutopy.Web.Features.Contents.Handlers; +namespace Hutopy.Modules.Creators.Features; [PublicAPI] public record CreateCreatorRequest( @@ -27,7 +27,7 @@ public sealed class CreateCreatorRequestValidator : Validator { public override void Configure() @@ -74,7 +74,7 @@ public sealed class CreateCreatorHandler( await SendOkAsync(ct); } - catch (Exception e) + catch (Exception) { await transaction.RollbackAsync(ct); } diff --git a/backend/src/Web/Features/Contents/Handlers/GetCreatorById.cs b/backend/Modules/Creators/Features/GetCreatorById.cs similarity index 90% rename from backend/src/Web/Features/Contents/Handlers/GetCreatorById.cs rename to backend/Modules/Creators/Features/GetCreatorById.cs index d7e630c..c78edfd 100644 --- a/backend/src/Web/Features/Contents/Handlers/GetCreatorById.cs +++ b/backend/Modules/Creators/Features/GetCreatorById.cs @@ -1,6 +1,6 @@ -using Hutopy.Web.Features.Contents.Data; +using Hutopy.Modules.Creators.Data; -namespace Hutopy.Web.Features.Contents.Handlers; +namespace Hutopy.Modules.Creators.Features; [PublicAPI] public sealed class GetCreatorByIdRequest @@ -22,7 +22,7 @@ public sealed class GetCreatorByIdRequestValidator [PublicAPI] public class GetCreatorByIdHandler( - ContentDbContext context) + CreatorsDbContext context) : Endpoint { public override void Configure() diff --git a/backend/src/Web/Features/Contents/Handlers/GetCreatorBySlug.cs b/backend/Modules/Creators/Features/GetCreatorBySlug.cs similarity index 86% rename from backend/src/Web/Features/Contents/Handlers/GetCreatorBySlug.cs rename to backend/Modules/Creators/Features/GetCreatorBySlug.cs index ba6f087..cbfc4b4 100644 --- a/backend/src/Web/Features/Contents/Handlers/GetCreatorBySlug.cs +++ b/backend/Modules/Creators/Features/GetCreatorBySlug.cs @@ -1,7 +1,7 @@ -using Hutopy.Web.Common.Security; -using Hutopy.Web.Features.Contents.Data; +using Hutopy.Infrastructure.Security; +using Hutopy.Modules.Creators.Data; -namespace Hutopy.Web.Features.Contents.Handlers; +namespace Hutopy.Modules.Creators.Features; [PublicAPI] public sealed class GetCreatorBySlugRequest @@ -22,11 +22,11 @@ public record GetCreatorBySlugResponse public bool AcceptDonation { get; init; } public string? BannerUrl { get; init; } public string? PortraitUrl { get; init; } - public string Slug { get; init; } - public string Name { get; init; } + public required string Slug { get; init; } + public required string Name { get; init; } public string? Title { get; init; } - public Socials Socials { get; init; } - public Presentation Presentation { get; init; } + public Socials? Socials { get; init; } + public Presentation? Presentation { get; init; } } [UsedImplicitly] @@ -43,7 +43,7 @@ public sealed class GetCreatorBySlugRequestValidator [PublicAPI] public class GetCreatorBySlugHandler( - ContentDbContext context) + CreatorsDbContext context) : Endpoint { public override void Configure() @@ -73,12 +73,12 @@ public class GetCreatorBySlugHandler( DeletedAt = c.DeletedAt, IsDeleted = c.IsDeleted, Verified = c.Verified, - AcceptDonation = c.AcceptDonation, BannerUrl = c.BannerUrl, PortraitUrl = c.PortraitUrl, Slug = c.Slug, Name = c.Name, Title = c.Title, + AcceptDonation = c.IsStripeChargesEnabled && c.IsStripePayoutReady, Socials = c.Socials, Presentation = c.Presentation }) diff --git a/backend/src/Web/Features/Contents/Handlers/GetCreatorProfile.cs b/backend/Modules/Creators/Features/GetCreatorProfile.cs similarity index 61% rename from backend/src/Web/Features/Contents/Handlers/GetCreatorProfile.cs rename to backend/Modules/Creators/Features/GetCreatorProfile.cs index fcb61e3..36ba562 100644 --- a/backend/src/Web/Features/Contents/Handlers/GetCreatorProfile.cs +++ b/backend/Modules/Creators/Features/GetCreatorProfile.cs @@ -1,7 +1,7 @@ -using Hutopy.Web.Common.Security; -using Hutopy.Web.Features.Contents.Data; +using Hutopy.Infrastructure.Security; +using Hutopy.Modules.Creators.Data; -namespace Hutopy.Web.Features.Contents.Handlers; +namespace Hutopy.Modules.Creators.Features; [PublicAPI] public sealed class GetCreatorProfileResponse @@ -16,26 +16,29 @@ public sealed class GetCreatorProfileResponse public required string Slug { get; set; } public string? Title { get; set; } public bool Verified { get; set; } - public bool AcceptDonation { get; set; } + public bool IsStripeAccountPresent { get; set; } + public bool IsStripeDetailsSubmitted { get; set; } + public bool IsStripePayoutReady { get; set; } + public bool IsStripeChargesEnabled { get; set; } public required Presentation Presentation { get; set; } public required Socials Socials { get; set; } } [PublicAPI] public class GetCreatorProfileHandler( - ContentDbContext context) + CreatorsDbContext context) : EndpointWithoutRequest { public override void Configure() { Get("/api/creators/profile"); - Options((o => o.WithTags("Creators"))); + Options(o => o.WithTags("Creators")); } public override async Task HandleAsync( CancellationToken ct) { - var creator = await context + GetCreatorProfileResponse? creator = await context .Creators .IgnoreQueryFilters() .Where(c => c.Id == HttpContext.User.GetUserId()) @@ -52,13 +55,22 @@ public class GetCreatorProfileHandler( Slug = c.Slug, Title = c.Title, Verified = c.Verified, - AcceptDonation = c.AcceptDonation, + IsStripeAccountPresent = !string.IsNullOrWhiteSpace(c.StripeAccountId), + IsStripeDetailsSubmitted = c.IsStripeDetailsSubmitted, + IsStripeChargesEnabled = c.IsStripeChargesEnabled, + IsStripePayoutReady = c.IsStripePayoutReady, Presentation = c.Presentation, - Socials = c.Socials, + Socials = c.Socials }) .SingleOrDefaultAsync(ct); - if (creator is null) await SendNotFoundAsync(ct); - else await SendAsync(creator, cancellation: ct); + if (creator is null) + { + await SendNotFoundAsync(ct); + } + else + { + await SendAsync(creator, cancellation: ct); + } } } diff --git a/backend/src/Web/Features/Contents/Handlers/RemoveCreator.cs b/backend/Modules/Creators/Features/RemoveCreator.cs similarity index 89% rename from backend/src/Web/Features/Contents/Handlers/RemoveCreator.cs rename to backend/Modules/Creators/Features/RemoveCreator.cs index 8a4bd1c..d4306f1 100644 --- a/backend/src/Web/Features/Contents/Handlers/RemoveCreator.cs +++ b/backend/Modules/Creators/Features/RemoveCreator.cs @@ -1,7 +1,7 @@ -using Hutopy.Web.Common.Security; -using Hutopy.Web.Features.Contents.Data; +using Hutopy.Infrastructure.Security; +using Hutopy.Modules.Creators.Data; -namespace Hutopy.Web.Features.Contents.Handlers; +namespace Hutopy.Modules.Creators.Features; [PublicAPI] public record RemoveCreatorRequest( @@ -21,7 +21,7 @@ public sealed class RemoveCreatorRequestValidator : Validator { public override void Configure() diff --git a/backend/src/Web/Features/Contents/Handlers/ReserveSlug.cs b/backend/Modules/Creators/Features/ReserveSlug.cs similarity index 90% rename from backend/src/Web/Features/Contents/Handlers/ReserveSlug.cs rename to backend/Modules/Creators/Features/ReserveSlug.cs index e923650..6f61bd6 100644 --- a/backend/src/Web/Features/Contents/Handlers/ReserveSlug.cs +++ b/backend/Modules/Creators/Features/ReserveSlug.cs @@ -1,11 +1,13 @@ using System.Net; using FluentValidation.Results; -using Hutopy.Web.Common.Security; -using Hutopy.Web.Features.Contents.Data; +using Hutopy.Infrastructure.Security; +using Hutopy.Modules.Creators.Configuration; +using Hutopy.Modules.Creators.Data; +using Hutopy.Modules.Creators.Services; using Microsoft.Extensions.Options; using Npgsql; -namespace Hutopy.Web.Features.Contents.Handlers; +namespace Hutopy.Modules.Creators.Features; [PublicAPI] public record ReserveSlugRequest @@ -28,15 +30,15 @@ public sealed class ReserveSlugRequestValidator : Validator [PublicAPI] public sealed class ReserveSlug( - ContentDbContext context, - IOptions opts, + CreatorsDbContext context, + IOptions opts, SlugPurger slugPurger) : Endpoint { public override void Configure() { Post("/api/creators/@{Slug}/reserve"); - Options(o => o.WithTags("Contents")); + Options(o => o.WithTags("Creators")); } public override async Task HandleAsync( diff --git a/backend/src/Web/Features/Contents/Handlers/RestoreCreator.cs b/backend/Modules/Creators/Features/RestoreCreator.cs similarity index 90% rename from backend/src/Web/Features/Contents/Handlers/RestoreCreator.cs rename to backend/Modules/Creators/Features/RestoreCreator.cs index f735afa..91333b6 100644 --- a/backend/src/Web/Features/Contents/Handlers/RestoreCreator.cs +++ b/backend/Modules/Creators/Features/RestoreCreator.cs @@ -1,7 +1,7 @@ -using Hutopy.Web.Common.Security; -using Hutopy.Web.Features.Contents.Data; +using Hutopy.Infrastructure.Security; +using Hutopy.Modules.Creators.Data; -namespace Hutopy.Web.Features.Contents.Handlers; +namespace Hutopy.Modules.Creators.Features; [PublicAPI] public record RestoreCreatorRequest( @@ -21,7 +21,7 @@ public sealed class RestoreCreatorRequestValidator : Validator { public override void Configure() diff --git a/backend/Modules/Creators/Features/RevokeStripe.cs b/backend/Modules/Creators/Features/RevokeStripe.cs new file mode 100644 index 0000000..cc2269f --- /dev/null +++ b/backend/Modules/Creators/Features/RevokeStripe.cs @@ -0,0 +1,48 @@ +using Hutopy.Infrastructure.Security; +using Hutopy.Modules.Creators.Data; + +namespace Hutopy.Modules.Creators.Features; + +[PublicAPI] +public class RemoveStripeHandler( + CreatorsDbContext dbContext) + : EndpointWithoutRequest +{ + public override void Configure() + { + Delete("/api/stripe"); + Options(o => o.WithTags("Creators")); + } + + public override async Task HandleAsync(CancellationToken ct) + { + // 1. Get the creator's ID from the authenticated user + Guid creatorId = HttpContext.User.GetUserId(); + + // 2. Retrieve the creator from the database + Creator? creator = await dbContext + .Creators + .SingleOrDefaultAsync( + c => c.Id == creatorId, + ct); + + // 3. If the creator doesn't exist or has no Stripe account linked, return 404 + if (creator is null || string.IsNullOrWhiteSpace(creator.StripeAccountId)) + { + await SendNotFoundAsync(ct); + return; + } + + // 4. Remove Stripe configuration + creator.StripeAccountId = null; + creator.IsStripeDetailsSubmitted = false; + creator.IsStripeChargesEnabled = false; + creator.IsStripePayoutReady = false; + + // 5. Persist changes + await dbContext.SaveChangesAsync(ct); + + // 6. Respond with success + await SendOkAsync(ct); + } +} diff --git a/backend/src/Web/Features/Contents/Data/Migrations/20250423153323_AddPresentation.Designer.cs b/backend/Modules/Creators/Migrations/20250609203815_Initial.Designer.cs similarity index 57% rename from backend/src/Web/Features/Contents/Data/Migrations/20250423153323_AddPresentation.Designer.cs rename to backend/Modules/Creators/Migrations/20250609203815_Initial.Designer.cs index 82cd03e..cedee10 100644 --- a/backend/src/Web/Features/Contents/Data/Migrations/20250423153323_AddPresentation.Designer.cs +++ b/backend/Modules/Creators/Migrations/20250609203815_Initial.Designer.cs @@ -1,6 +1,6 @@ // using System; -using Hutopy.Web.Features.Contents.Data; +using Hutopy.Modules.Creators.Data; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations; @@ -9,75 +9,24 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; #nullable disable -namespace Hutopy.Web.Features.Contents.Data.Migrations +namespace Hutopy.Modules.Creators.Migrations { - [DbContext(typeof(ContentDbContext))] - [Migration("20250423153323_AddPresentation")] - partial class AddPresentation + [DbContext(typeof(CreatorsDbContext))] + [Migration("20250609203815_Initial")] + partial class Initial { /// protected override void BuildTargetModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder - .HasDefaultSchema("Content") + .HasDefaultSchema("Creators") .HasAnnotation("ProductVersion", "9.0.3") .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("CreatorId") - .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(2048) - .HasColumnType("character varying(2048)"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)"); - - b.PrimitiveCollection("Urls") - .HasColumnType("text[]"); - - b.HasKey("Id"); - - b.HasIndex("CreatorId"); - - b.ToTable("Contents", "Content"); - }); - - modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.Creator", b => + modelBuilder.Entity("Hutopy.Modules.Creators.Data.Creator", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -107,10 +56,19 @@ namespace Hutopy.Web.Features.Contents.Data.Migrations .HasColumnType("boolean") .HasComputedColumnSql("\"DeletedAt\" IS NOT NULL", true); + b.Property("IsStripeChargesEnabled") + .HasColumnType("boolean"); + + b.Property("IsStripeOnboardingComplete") + .HasColumnType("boolean"); + + b.Property("IsStripePayoutReady") + .HasColumnType("boolean"); + b.Property("Name") .IsRequired() - .HasMaxLength(255) - .HasColumnType("character varying(255)"); + .HasMaxLength(256) + .HasColumnType("character varying(256)"); b.Property("PortraitUrl") .HasMaxLength(2048) @@ -121,19 +79,23 @@ namespace Hutopy.Web.Features.Contents.Data.Migrations .HasMaxLength(128) .HasColumnType("character varying(128)"); + b.Property("StripeAccountId") + .HasMaxLength(21) + .HasColumnType("character varying(21)"); + b.Property("Title") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); + .HasMaxLength(256) + .HasColumnType("character varying(256)"); b.Property("Verified") .HasColumnType("boolean"); b.HasKey("Id"); - b.ToTable("Creators", "Content"); + b.ToTable("Creators", "Creators"); }); - modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.Slugs", b => + modelBuilder.Entity("Hutopy.Modules.Creators.Data.Slugs", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -155,7 +117,7 @@ namespace Hutopy.Web.Features.Contents.Data.Migrations .ValueGeneratedOnAddOrUpdate() .HasMaxLength(128) .HasColumnType("character varying(128)") - .HasComputedColumnSql("LOWER( \"Content\".\"Slugs\".\"Name\")", true); + .HasComputedColumnSql("LOWER(\"Name\")", true); b.Property("ReservedUntil") .HasColumnType("timestamp with time zone"); @@ -168,69 +130,27 @@ namespace Hutopy.Web.Features.Contents.Data.Migrations b.HasIndex("NormalizedName") .IsUnique(); - b.ToTable("Slugs", "Content"); + b.ToTable("Slugs", "Creators"); }); - modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.Content", b => + modelBuilder.Entity("Hutopy.Modules.Creators.Data.Creator", b => { - b.HasOne("Hutopy.Web.Features.Contents.Data.Creator", "Creator") - .WithMany() - .HasForeignKey("CreatorId"); - - 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.Presentation", "Presentation", b1 => + b.OwnsOne("Hutopy.Modules.Creators.Data.Presentation", "Presentation", b1 => { b1.Property("CreatorId") .HasColumnType("uuid"); b1.Property("Description") .IsRequired() - .HasMaxLength(2000) - .HasColumnType("character varying(2000)"); + .HasColumnType("text"); b1.Property("Email") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); + .HasMaxLength(256) + .HasColumnType("character varying(256)"); b1.Property("PhoneNumber") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); + .HasMaxLength(256) + .HasColumnType("character varying(256)"); b1.Property("VideoUrl") .HasMaxLength(2048) @@ -238,13 +158,13 @@ namespace Hutopy.Web.Features.Contents.Data.Migrations b1.HasKey("CreatorId"); - b1.ToTable("Presentation", "Content"); + b1.ToTable("Presentation", "Creators"); b1.WithOwner() .HasForeignKey("CreatorId"); }); - b.OwnsOne("Hutopy.Web.Features.Contents.Data.Socials", "Socials", b1 => + b.OwnsOne("Hutopy.Modules.Creators.Data.Socials", "Socials", b1 => { b1.Property("CreatorId") .HasColumnType("uuid"); @@ -283,7 +203,7 @@ namespace Hutopy.Web.Features.Contents.Data.Migrations b1.HasKey("CreatorId"); - b1.ToTable("Socials", "Content"); + b1.ToTable("Socials", "Creators"); b1.WithOwner() .HasForeignKey("CreatorId"); diff --git a/backend/Modules/Creators/Migrations/20250609203815_Initial.cs b/backend/Modules/Creators/Migrations/20250609203815_Initial.cs new file mode 100644 index 0000000..65e79ab --- /dev/null +++ b/backend/Modules/Creators/Migrations/20250609203815_Initial.cs @@ -0,0 +1,141 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Hutopy.Modules.Creators.Migrations +{ + /// + public partial class Initial : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.EnsureSchema( + name: "Creators"); + + migrationBuilder.CreateTable( + name: "Creators", + schema: "Creators", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + CreatedBy = table.Column(type: "uuid", nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + DeletedBy = table.Column(type: "uuid", nullable: true), + DeletedAt = table.Column(type: "timestamp with time zone", nullable: true), + IsDeleted = table.Column(type: "boolean", nullable: false, computedColumnSql: "\"DeletedAt\" IS NOT NULL", stored: true), + BannerUrl = table.Column(type: "character varying(2048)", maxLength: 2048, nullable: true), + PortraitUrl = table.Column(type: "character varying(2048)", maxLength: 2048, nullable: true), + Verified = table.Column(type: "boolean", nullable: false), + Name = table.Column(type: "character varying(256)", maxLength: 256, nullable: false), + Slug = table.Column(type: "character varying(128)", maxLength: 128, nullable: false), + Title = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), + StripeAccountId = table.Column(type: "character varying(21)", maxLength: 21, nullable: true), + IsStripeOnboardingComplete = table.Column(type: "boolean", nullable: false), + IsStripePayoutReady = table.Column(type: "boolean", nullable: false), + IsStripeChargesEnabled = table.Column(type: "boolean", nullable: false), + AcceptDonation = table.Column(type: "boolean", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Creators", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Slugs", + schema: "Creators", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + CreatedBy = table.Column(type: "uuid", nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + UsedBy = table.Column(type: "uuid", nullable: true), + Name = table.Column(type: "character varying(128)", maxLength: 128, nullable: false), + NormalizedName = table.Column(type: "character varying(128)", maxLength: 128, nullable: false, computedColumnSql: "LOWER(\"Name\")", stored: true), + ReservedUntil = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Slugs", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Presentation", + schema: "Creators", + columns: table => new + { + CreatorId = table.Column(type: "uuid", nullable: false), + Description = table.Column(type: "text", nullable: false), + VideoUrl = table.Column(type: "character varying(2048)", maxLength: 2048, nullable: true), + PhoneNumber = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), + Email = table.Column(type: "character varying(256)", maxLength: 256, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Presentation", x => x.CreatorId); + table.ForeignKey( + name: "FK_Presentation_Creators_CreatorId", + column: x => x.CreatorId, + principalSchema: "Creators", + principalTable: "Creators", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Socials", + schema: "Creators", + columns: table => new + { + CreatorId = table.Column(type: "uuid", nullable: false), + FacebookUrl = table.Column(type: "character varying(2048)", maxLength: 2048, nullable: true), + InstagramUrl = table.Column(type: "character varying(2048)", maxLength: 2048, nullable: true), + XUrl = table.Column(type: "character varying(2048)", maxLength: 2048, nullable: true), + LinkedInUrl = table.Column(type: "character varying(2048)", maxLength: 2048, nullable: true), + TikTokUrl = table.Column(type: "character varying(2048)", maxLength: 2048, nullable: true), + YoutubeUrl = table.Column(type: "character varying(2048)", maxLength: 2048, nullable: true), + RedditUrl = table.Column(type: "character varying(2048)", maxLength: 2048, nullable: true), + WebsiteUrl = table.Column(type: "character varying(2048)", maxLength: 2048, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Socials", x => x.CreatorId); + table.ForeignKey( + name: "FK_Socials_Creators_CreatorId", + column: x => x.CreatorId, + principalSchema: "Creators", + principalTable: "Creators", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_Slugs_NormalizedName", + schema: "Creators", + table: "Slugs", + column: "NormalizedName", + unique: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Presentation", + schema: "Creators"); + + migrationBuilder.DropTable( + name: "Slugs", + schema: "Creators"); + + migrationBuilder.DropTable( + name: "Socials", + schema: "Creators"); + + migrationBuilder.DropTable( + name: "Creators", + schema: "Creators"); + } + } +} diff --git a/backend/Modules/Creators/Migrations/20250610200446_AddStripe.Designer.cs b/backend/Modules/Creators/Migrations/20250610200446_AddStripe.Designer.cs new file mode 100644 index 0000000..303e74d --- /dev/null +++ b/backend/Modules/Creators/Migrations/20250610200446_AddStripe.Designer.cs @@ -0,0 +1,218 @@ +// +using System; +using Hutopy.Modules.Creators.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.Modules.Creators.Migrations +{ + [DbContext(typeof(CreatorsDbContext))] + [Migration("20250610200446_AddStripe")] + partial class AddStripe + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasDefaultSchema("Creators") + .HasAnnotation("ProductVersion", "9.0.3") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Hutopy.Modules.Creators.Data.Creator", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BannerUrl") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedBy") + .HasColumnType("uuid"); + + b.Property("IsDeleted") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("boolean") + .HasComputedColumnSql("\"DeletedAt\" IS NOT NULL", true); + + b.Property("IsStripeChargesEnabled") + .HasColumnType("boolean"); + + b.Property("IsStripeDetailsSubmitted") + .HasColumnType("boolean"); + + b.Property("IsStripePayoutReady") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PortraitUrl") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("StripeAccountId") + .HasMaxLength(21) + .HasColumnType("character varying(21)"); + + b.Property("Title") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Verified") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.ToTable("Creators", "Creators"); + }); + + modelBuilder.Entity("Hutopy.Modules.Creators.Data.Slugs", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("NormalizedName") + .IsRequired() + .ValueGeneratedOnAddOrUpdate() + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasComputedColumnSql("LOWER(\"Name\")", true); + + b.Property("ReservedUntil") + .HasColumnType("timestamp with time zone"); + + b.Property("UsedBy") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique(); + + b.ToTable("Slugs", "Creators"); + }); + + modelBuilder.Entity("Hutopy.Modules.Creators.Data.Creator", b => + { + b.OwnsOne("Hutopy.Modules.Creators.Data.Presentation", "Presentation", b1 => + { + b1.Property("CreatorId") + .HasColumnType("uuid"); + + b1.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b1.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b1.Property("PhoneNumber") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b1.Property("VideoUrl") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b1.HasKey("CreatorId"); + + b1.ToTable("Presentation", "Creators"); + + b1.WithOwner() + .HasForeignKey("CreatorId"); + }); + + b.OwnsOne("Hutopy.Modules.Creators.Data.Socials", "Socials", b1 => + { + b1.Property("CreatorId") + .HasColumnType("uuid"); + + b1.Property("FacebookUrl") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b1.Property("InstagramUrl") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b1.Property("LinkedInUrl") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b1.Property("RedditUrl") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b1.Property("TikTokUrl") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b1.Property("WebsiteUrl") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b1.Property("XUrl") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b1.Property("YoutubeUrl") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b1.HasKey("CreatorId"); + + b1.ToTable("Socials", "Creators"); + + b1.WithOwner() + .HasForeignKey("CreatorId"); + }); + + b.Navigation("Presentation") + .IsRequired(); + + b.Navigation("Socials") + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/backend/src/Web/Features/Contents/Data/Migrations/20250109015556_Adds_AcceptDonation_Creator.cs b/backend/Modules/Creators/Migrations/20250610200446_AddStripe.cs similarity index 53% rename from backend/src/Web/Features/Contents/Data/Migrations/20250109015556_Adds_AcceptDonation_Creator.cs rename to backend/Modules/Creators/Migrations/20250610200446_AddStripe.cs index 339404e..14177e5 100644 --- a/backend/src/Web/Features/Contents/Data/Migrations/20250109015556_Adds_AcceptDonation_Creator.cs +++ b/backend/Modules/Creators/Migrations/20250610200446_AddStripe.cs @@ -2,30 +2,42 @@ #nullable disable -namespace Hutopy.Web.Features.Contents.Data.Migrations +namespace Hutopy.Modules.Creators.Migrations { /// - public partial class Adds_AcceptDonation_Creator : Migration + public partial class AddStripe : Migration { /// protected override void Up(MigrationBuilder migrationBuilder) { - migrationBuilder.AddColumn( + migrationBuilder.DropColumn( name: "AcceptDonation", - schema: "Content", + schema: "Creators", + table: "Creators"); + + migrationBuilder.RenameColumn( + name: "IsStripeOnboardingComplete", + schema: "Creators", table: "Creators", - type: "boolean", - nullable: false, - defaultValue: false); + newName: "IsStripeDetailsSubmitted"); } /// protected override void Down(MigrationBuilder migrationBuilder) { - migrationBuilder.DropColumn( + migrationBuilder.RenameColumn( + name: "IsStripeDetailsSubmitted", + schema: "Creators", + table: "Creators", + newName: "IsStripeOnboardingComplete"); + + migrationBuilder.AddColumn( name: "AcceptDonation", - schema: "Content", - table: "Creators"); + schema: "Creators", + table: "Creators", + type: "boolean", + nullable: false, + defaultValue: false); } } } diff --git a/backend/Modules/Creators/Migrations/CreatorsDbContextModelSnapshot.cs b/backend/Modules/Creators/Migrations/CreatorsDbContextModelSnapshot.cs new file mode 100644 index 0000000..5c14656 --- /dev/null +++ b/backend/Modules/Creators/Migrations/CreatorsDbContextModelSnapshot.cs @@ -0,0 +1,215 @@ +// +using System; +using Hutopy.Modules.Creators.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Hutopy.Modules.Creators.Migrations +{ + [DbContext(typeof(CreatorsDbContext))] + partial class CreatorsDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasDefaultSchema("Creators") + .HasAnnotation("ProductVersion", "9.0.3") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Hutopy.Modules.Creators.Data.Creator", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BannerUrl") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedBy") + .HasColumnType("uuid"); + + b.Property("IsDeleted") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("boolean") + .HasComputedColumnSql("\"DeletedAt\" IS NOT NULL", true); + + b.Property("IsStripeChargesEnabled") + .HasColumnType("boolean"); + + b.Property("IsStripeDetailsSubmitted") + .HasColumnType("boolean"); + + b.Property("IsStripePayoutReady") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PortraitUrl") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("StripeAccountId") + .HasMaxLength(21) + .HasColumnType("character varying(21)"); + + b.Property("Title") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Verified") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.ToTable("Creators", "Creators"); + }); + + modelBuilder.Entity("Hutopy.Modules.Creators.Data.Slugs", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("NormalizedName") + .IsRequired() + .ValueGeneratedOnAddOrUpdate() + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasComputedColumnSql("LOWER(\"Name\")", true); + + b.Property("ReservedUntil") + .HasColumnType("timestamp with time zone"); + + b.Property("UsedBy") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique(); + + b.ToTable("Slugs", "Creators"); + }); + + modelBuilder.Entity("Hutopy.Modules.Creators.Data.Creator", b => + { + b.OwnsOne("Hutopy.Modules.Creators.Data.Presentation", "Presentation", b1 => + { + b1.Property("CreatorId") + .HasColumnType("uuid"); + + b1.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b1.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b1.Property("PhoneNumber") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b1.Property("VideoUrl") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b1.HasKey("CreatorId"); + + b1.ToTable("Presentation", "Creators"); + + b1.WithOwner() + .HasForeignKey("CreatorId"); + }); + + b.OwnsOne("Hutopy.Modules.Creators.Data.Socials", "Socials", b1 => + { + b1.Property("CreatorId") + .HasColumnType("uuid"); + + b1.Property("FacebookUrl") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b1.Property("InstagramUrl") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b1.Property("LinkedInUrl") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b1.Property("RedditUrl") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b1.Property("TikTokUrl") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b1.Property("WebsiteUrl") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b1.Property("XUrl") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b1.Property("YoutubeUrl") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b1.HasKey("CreatorId"); + + b1.ToTable("Socials", "Creators"); + + b1.WithOwner() + .HasForeignKey("CreatorId"); + }); + + b.Navigation("Presentation") + .IsRequired(); + + b.Navigation("Socials") + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/backend/Modules/Creators/Services/CreatorLookup.cs b/backend/Modules/Creators/Services/CreatorLookup.cs new file mode 100644 index 0000000..7a0686e --- /dev/null +++ b/backend/Modules/Creators/Services/CreatorLookup.cs @@ -0,0 +1,26 @@ +using Hutopy.Modules.Creators.Contracts; +using Hutopy.Modules.Creators.Data; + +namespace Hutopy.Modules.Creators.Services; + +public sealed class CreatorLookup( + CreatorsDbContext context) + : ICreatorLookup +{ + public async Task GetCreatorAsync(Guid creatorId, CancellationToken cancellationToken) + { + Creator? creator = await context + .Creators + .FirstOrDefaultAsync(c => c.Id == creatorId, cancellationToken); + + return creator is null + ? null + : new CreatorReference( + creator.Id, + creator.Name, + creator.PortraitUrl, + creator.IsStripeDetailsSubmitted, + creator.IsStripeChargesEnabled, + creator.StripeAccountId); + } +} diff --git a/backend/src/Web/Features/Contents/Data/SlugPurger.cs b/backend/Modules/Creators/Services/SlugPurger.cs similarity index 59% rename from backend/src/Web/Features/Contents/Data/SlugPurger.cs rename to backend/Modules/Creators/Services/SlugPurger.cs index 4cbd7fd..4b880e5 100644 --- a/backend/src/Web/Features/Contents/Data/SlugPurger.cs +++ b/backend/Modules/Creators/Services/SlugPurger.cs @@ -1,15 +1,17 @@ -namespace Hutopy.Web.Features.Contents.Data; +using Hutopy.Modules.Creators.Data; -public class SlugPurger(ContentDbContext context) +namespace Hutopy.Modules.Creators.Services; + +public class SlugPurger(CreatorsDbContext context) { - private static readonly SemaphoreSlim _semaphore = new(1, 1); - private static DateTimeOffset _lastPurgeTime = DateTimeOffset.MinValue; - private static readonly TimeSpan _minTimeBetweenPurges = TimeSpan.FromSeconds(10); + private static readonly SemaphoreSlim Semaphore = new(1, 1); + private static DateTimeOffset s_lastPurgeTime = DateTimeOffset.MinValue; + private static readonly TimeSpan MinTimeBetweenPurges = TimeSpan.FromSeconds(10); public async Task PurgeExpiredSlugsAsync(CancellationToken ct) { // Try to acquire the semaphore - if (!await _semaphore.WaitAsync(0, ct)) + if (!await Semaphore.WaitAsync(0, ct)) { // Another purge operation is in progress, skip this one return; @@ -18,7 +20,7 @@ public class SlugPurger(ContentDbContext context) try { var now = DateTimeOffset.UtcNow; - if (now - _lastPurgeTime < _minTimeBetweenPurges) + if (now - s_lastPurgeTime < MinTimeBetweenPurges) { // Not enough time has passed since the last purge return; @@ -31,11 +33,11 @@ public class SlugPurger(ContentDbContext context) .ExecuteDeleteAsync(ct); // Update the last purge time regardless of whether we found expired slugs or not - _lastPurgeTime = now; + s_lastPurgeTime = now; } finally { - _semaphore.Release(); + Semaphore.Release(); } } } diff --git a/backend/src/Web/Features/Users/JwtOptions.cs b/backend/Modules/Identity/Configuration/JwtOptions.cs similarity index 88% rename from backend/src/Web/Features/Users/JwtOptions.cs rename to backend/Modules/Identity/Configuration/JwtOptions.cs index b5241cb..a8c0bd6 100644 --- a/backend/src/Web/Features/Users/JwtOptions.cs +++ b/backend/Modules/Identity/Configuration/JwtOptions.cs @@ -1,4 +1,4 @@ -namespace Hutopy.Web.Features.Users; +namespace Hutopy.Modules.Identity.Configuration; public record JwtOptions { diff --git a/backend/Modules/Identity/Contracts/IUserLookup.cs b/backend/Modules/Identity/Contracts/IUserLookup.cs new file mode 100644 index 0000000..ad0c0ca --- /dev/null +++ b/backend/Modules/Identity/Contracts/IUserLookup.cs @@ -0,0 +1,6 @@ +namespace Hutopy.Modules.Identity.Contracts; + +public interface IUserLookup +{ + Task GetUserAsync(Guid userId, CancellationToken cancellationToken = default); +} diff --git a/backend/src/Web/Features/Users/KnownRoles.cs b/backend/Modules/Identity/Contracts/KnownRoles.cs similarity index 75% rename from backend/src/Web/Features/Users/KnownRoles.cs rename to backend/Modules/Identity/Contracts/KnownRoles.cs index 20c9ce2..addad34 100644 --- a/backend/src/Web/Features/Users/KnownRoles.cs +++ b/backend/Modules/Identity/Contracts/KnownRoles.cs @@ -1,4 +1,4 @@ -namespace Hutopy.Web.Features.Users; +namespace Hutopy.Modules.Identity.Contracts; public static class KnownRoles { diff --git a/backend/Modules/Identity/Contracts/UserReference.cs b/backend/Modules/Identity/Contracts/UserReference.cs new file mode 100644 index 0000000..f08ce18 --- /dev/null +++ b/backend/Modules/Identity/Contracts/UserReference.cs @@ -0,0 +1,6 @@ +namespace Hutopy.Modules.Identity.Contracts; + +public record UserReference( + Guid Id, + string Fullname, + string? PortraitUrl); diff --git a/backend/src/Web/Features/Users/Data/ApplicationDbContext.cs b/backend/Modules/Identity/Data/IdentityDbContext.cs similarity index 63% rename from backend/src/Web/Features/Users/Data/ApplicationDbContext.cs rename to backend/Modules/Identity/Data/IdentityDbContext.cs index 0e8d0c1..d4bd96b 100644 --- a/backend/src/Web/Features/Users/Data/ApplicationDbContext.cs +++ b/backend/Modules/Identity/Data/IdentityDbContext.cs @@ -1,12 +1,13 @@ using Microsoft.AspNetCore.Identity.EntityFrameworkCore; -namespace Hutopy.Web.Features.Users.Data +namespace Hutopy.Modules.Identity.Data { - public class ApplicationDbContext( - DbContextOptions options) - : IdentityDbContext(options) + public class IdentityDbContext( + DbContextOptions options) + : IdentityDbContext(options) { public const string SchemaName = "Identity"; + protected override void OnModelCreating(ModelBuilder modelBuilder) { diff --git a/backend/Modules/Identity/Data/IdentityService.cs b/backend/Modules/Identity/Data/IdentityService.cs new file mode 100644 index 0000000..9174dcb --- /dev/null +++ b/backend/Modules/Identity/Data/IdentityService.cs @@ -0,0 +1,60 @@ +using System.Security.Claims; +using Hutopy.Modules.Identity.Models; + +namespace Hutopy.Modules.Identity.Data; + +public class IdentityService( + UserManager userManager, + IHttpContextAccessor contextAccessor +) +{ + public async Task GetCurrentUserAsync() + { + var currentUserId = contextAccessor.HttpContext?.User.FindFirst(ClaimTypes.NameIdentifier)?.Value; + if (string.IsNullOrEmpty(currentUserId)) + { + return null; + } + + UserModel? ret; + var user = await userManager.FindByIdAsync(currentUserId); + + if (user == null) ret = null; + else + { + var userModel = new UserModel + { + Id = user.Id, + Username = user.UserName ?? string.Empty, + PhoneNumber = user.PhoneNumber ?? string.Empty, + Email = user.Email ?? string.Empty, + PortraitUrl = user.PortraitUrl, + Alias = user.Alias, + Firstname = user.Firstname, + Lastname = user.Lastname, + BirthDate = user.BirthDate, + Address = user.Address + }; + + ret = userModel; + } + + return ret; + } + + public async Task> GetCurrentUserRolesAsync() + { + var currentUserModel = await GetCurrentUserAsync(); + + if (currentUserModel is null) return []; + + var currentUser = await userManager.FindByIdAsync(currentUserModel.Id.ToString()); + + if (currentUser is null) return []; + + var userRoles = await userManager.GetRolesAsync(currentUser); + + return userRoles; + } + +} diff --git a/backend/Modules/Identity/Data/Role.cs b/backend/Modules/Identity/Data/Role.cs new file mode 100644 index 0000000..ce387a4 --- /dev/null +++ b/backend/Modules/Identity/Data/Role.cs @@ -0,0 +1,9 @@ +using Microsoft.AspNetCore.Identity; + +namespace Hutopy.Modules.Identity.Data; + +public class Role : IdentityRole +{ + public Role() { } + public Role(string roleName) : base(roleName) { } +} diff --git a/backend/Modules/Identity/Data/User.cs b/backend/Modules/Identity/Data/User.cs new file mode 100644 index 0000000..43c028d --- /dev/null +++ b/backend/Modules/Identity/Data/User.cs @@ -0,0 +1,20 @@ +using System.ComponentModel.DataAnnotations; +using Microsoft.AspNetCore.Identity; + +namespace Hutopy.Modules.Identity.Data; + +public class User : IdentityUser +{ + [MaxLength(256)] public string? Alias { get; set; } + [MaxLength(256)] public string? Firstname { get; set; } + [MaxLength(256)] public string? Lastname { get; set; } + public DateTime? BirthDate { get; set; } + [MaxLength(256)] public string? Address { get; set; } + [MaxLength(2048)] public string? PortraitUrl { get; set; } + [MaxLength(256)] public string? GoogleId { get; set; } + [MaxLength(256)] public string? FacebookId { get; set; } + [MaxLength(44)] public string? RefreshToken { get; set; } + public DateTime RefreshTokenExpiryTime { get; set; } + public string Fullname => $"{Lastname}, {Firstname}"; +} + diff --git a/backend/src/Web/Features/Users/Data/IdentityUserManager.cs b/backend/Modules/Identity/Data/UserManager.cs similarity index 52% rename from backend/src/Web/Features/Users/Data/IdentityUserManager.cs rename to backend/Modules/Identity/Data/UserManager.cs index 20b9c7c..a8ca648 100644 --- a/backend/src/Web/Features/Users/Data/IdentityUserManager.cs +++ b/backend/Modules/Identity/Data/UserManager.cs @@ -1,19 +1,19 @@ using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Options; -namespace Hutopy.Web.Features.Users.Data; +namespace Hutopy.Modules.Identity.Data; -public sealed class IdentityUserManager( - IUserStore store, +public sealed class UserManager( + IUserStore store, IOptions optionsAccessor, - IPasswordHasher passwordHasher, - IEnumerable> userValidators, - IEnumerable> passwordValidators, + IPasswordHasher passwordHasher, + IEnumerable> userValidators, + IEnumerable> passwordValidators, ILookupNormalizer keyNormalizer, IdentityErrorDescriber errors, IServiceProvider services, - ILogger> logger) - : UserManager( + ILogger> logger) + : UserManager( store, optionsAccessor, passwordHasher, diff --git a/backend/Modules/Identity/DependencyInjection.cs b/backend/Modules/Identity/DependencyInjection.cs new file mode 100644 index 0000000..08582fe --- /dev/null +++ b/backend/Modules/Identity/DependencyInjection.cs @@ -0,0 +1,72 @@ +using Hutopy.Modules.Identity.Configuration; +using Hutopy.Modules.Identity.Contracts; +using Hutopy.Modules.Identity.Data; +using Hutopy.Modules.Identity.Services; +using Microsoft.AspNetCore.Identity; + +namespace Hutopy.Modules.Identity; + +public static class DependencyInjection +{ + public static WebApplicationBuilder AddIdentityModule( + this WebApplicationBuilder builder, + Action? configureAction = null) + { + builder.Services.AddDbContext(configureAction); + + builder.Services.Configure( + builder.Configuration.GetRequiredSection(JwtOptions.SectionName)); + + builder.Services.AddAuthentication() + .AddBearerToken(IdentityConstants.BearerScheme); + + builder.Services.AddAuthorizationBuilder(); + + builder.Services + .AddIdentityCore() + .AddUserManager() + .AddRoles() + .AddEntityFrameworkStores() + .AddApiEndpoints() + .AddDefaultTokenProviders(); + + // Singleton services + builder.Services.AddSingleton(TimeProvider.System); + + // Scoped services + builder.Services.AddScoped(); + builder.Services.AddTransient(); + + return builder; + } + + public static async Task UseIdentityModuleAsync( + this IApplicationBuilder app, + CancellationToken cancellationToken = default) + { + var scopeFactory = app.ApplicationServices.GetRequiredService(); + using var scope = scopeFactory.CreateScope(); + await using var context = scope.ServiceProvider.GetRequiredService(); + await context.Database.MigrateAsync(cancellationToken: cancellationToken); + + var roleManager = scope.ServiceProvider.GetRequiredService>(); + await TrySeedAsync(roleManager); + + return app; + } + + private static async Task TrySeedAsync(RoleManager roleManager) + { + var administratorRole = new Role(KnownRoles.Administrator); + if (roleManager.Roles.All(r => r.Name != administratorRole.Name)) + { + await roleManager.CreateAsync(administratorRole); + } + + var roleCreator = new Role(KnownRoles.Creator); + if (roleManager.Roles.All(r => r.Name != roleCreator.Name)) + { + await roleManager.CreateAsync(roleCreator); + } + } +} diff --git a/backend/src/Web/Features/Users/Handlers/ChangeAddress.cs b/backend/Modules/Identity/Handlers/ChangeAddress.cs similarity index 84% rename from backend/src/Web/Features/Users/Handlers/ChangeAddress.cs rename to backend/Modules/Identity/Handlers/ChangeAddress.cs index a45c727..0ceadb8 100644 --- a/backend/src/Web/Features/Users/Handlers/ChangeAddress.cs +++ b/backend/Modules/Identity/Handlers/ChangeAddress.cs @@ -1,7 +1,7 @@ -using Hutopy.Web.Common.Security; -using Hutopy.Web.Features.Users.Data; +using Hutopy.Infrastructure.Security; +using Hutopy.Modules.Identity.Data; -namespace Hutopy.Web.Features.Users.Handlers; +namespace Hutopy.Modules.Identity.Handlers; [PublicAPI] public record ChangeAddressRequest( @@ -9,7 +9,7 @@ public record ChangeAddressRequest( [PublicAPI] public class ChangeAddressHandler( - IdentityUserManager userManager) + UserManager userManager) : Endpoint { public override void Configure() diff --git a/backend/src/Web/Features/Users/Handlers/ChangeAlias.cs b/backend/Modules/Identity/Handlers/ChangeAlias.cs similarity index 83% rename from backend/src/Web/Features/Users/Handlers/ChangeAlias.cs rename to backend/Modules/Identity/Handlers/ChangeAlias.cs index c697987..64730e0 100644 --- a/backend/src/Web/Features/Users/Handlers/ChangeAlias.cs +++ b/backend/Modules/Identity/Handlers/ChangeAlias.cs @@ -1,7 +1,7 @@ -using Hutopy.Web.Common.Security; -using Hutopy.Web.Features.Users.Data; +using Hutopy.Infrastructure.Security; +using Hutopy.Modules.Identity.Data; -namespace Hutopy.Web.Features.Users.Handlers; +namespace Hutopy.Modules.Identity.Handlers; [PublicAPI] public record ChangeAliasRequest( @@ -9,7 +9,7 @@ public record ChangeAliasRequest( [PublicAPI] public class ChangeAliasHandler( - IdentityUserManager userManager) + UserManager userManager) : Endpoint { public override void Configure() diff --git a/backend/src/Web/Features/Users/Handlers/ChangeBirthDate.cs b/backend/Modules/Identity/Handlers/ChangeBirthDate.cs similarity index 84% rename from backend/src/Web/Features/Users/Handlers/ChangeBirthDate.cs rename to backend/Modules/Identity/Handlers/ChangeBirthDate.cs index 7fce284..533a874 100644 --- a/backend/src/Web/Features/Users/Handlers/ChangeBirthDate.cs +++ b/backend/Modules/Identity/Handlers/ChangeBirthDate.cs @@ -1,7 +1,7 @@ -using Hutopy.Web.Common.Security; -using Hutopy.Web.Features.Users.Data; +using Hutopy.Infrastructure.Security; +using Hutopy.Modules.Identity.Data; -namespace Hutopy.Web.Features.Users.Handlers; +namespace Hutopy.Modules.Identity.Handlers; [PublicAPI] public record ChangeBirthDateRequest( @@ -9,7 +9,7 @@ public record ChangeBirthDateRequest( [PublicAPI] public class ChangeBirthDateHandler( - IdentityUserManager userManager) + UserManager userManager) : Endpoint { public override void Configure() diff --git a/backend/src/Web/Features/Users/Handlers/ChangeEmail.cs b/backend/Modules/Identity/Handlers/ChangeEmail.cs similarity index 85% rename from backend/src/Web/Features/Users/Handlers/ChangeEmail.cs rename to backend/Modules/Identity/Handlers/ChangeEmail.cs index ca1f1c8..e1ecc58 100644 --- a/backend/src/Web/Features/Users/Handlers/ChangeEmail.cs +++ b/backend/Modules/Identity/Handlers/ChangeEmail.cs @@ -1,7 +1,7 @@ -using Hutopy.Web.Common.Security; -using Hutopy.Web.Features.Users.Data; +using Hutopy.Infrastructure.Security; +using Hutopy.Modules.Identity.Data; -namespace Hutopy.Web.Features.Users.Handlers; +namespace Hutopy.Modules.Identity.Handlers; [PublicAPI] public record ChangeEmailRequest( @@ -9,7 +9,7 @@ public record ChangeEmailRequest( [PublicAPI] public class ChangeEmailHandler( - IdentityUserManager userManager) + UserManager userManager) : Endpoint { public override void Configure() diff --git a/backend/src/Web/Features/Users/Handlers/ChangeFullname.cs b/backend/Modules/Identity/Handlers/ChangeFullname.cs similarity index 85% rename from backend/src/Web/Features/Users/Handlers/ChangeFullname.cs rename to backend/Modules/Identity/Handlers/ChangeFullname.cs index 7a9a4f6..c7a66e1 100644 --- a/backend/src/Web/Features/Users/Handlers/ChangeFullname.cs +++ b/backend/Modules/Identity/Handlers/ChangeFullname.cs @@ -1,7 +1,7 @@ -using Hutopy.Web.Common.Security; -using Hutopy.Web.Features.Users.Data; +using Hutopy.Infrastructure.Security; +using Hutopy.Modules.Identity.Data; -namespace Hutopy.Web.Features.Users.Handlers; +namespace Hutopy.Modules.Identity.Handlers; [PublicAPI] public record ChangeFullnameRequest( @@ -10,7 +10,7 @@ public record ChangeFullnameRequest( [PublicAPI] public class ChangeFullnameHandler( - IdentityUserManager userManager) + UserManager userManager) : Endpoint { public override void Configure() diff --git a/backend/src/Web/Features/Users/Handlers/ChangePhone.cs b/backend/Modules/Identity/Handlers/ChangePhone.cs similarity index 85% rename from backend/src/Web/Features/Users/Handlers/ChangePhone.cs rename to backend/Modules/Identity/Handlers/ChangePhone.cs index 91c3597..833b33c 100644 --- a/backend/src/Web/Features/Users/Handlers/ChangePhone.cs +++ b/backend/Modules/Identity/Handlers/ChangePhone.cs @@ -1,7 +1,7 @@ -using Hutopy.Web.Common.Security; -using Hutopy.Web.Features.Users.Data; +using Hutopy.Infrastructure.Security; +using Hutopy.Modules.Identity.Data; -namespace Hutopy.Web.Features.Users.Handlers; +namespace Hutopy.Modules.Identity.Handlers; [PublicAPI] public record ChangePhoneRequest( @@ -9,7 +9,7 @@ public record ChangePhoneRequest( [PublicAPI] public class ChangePhoneHandler( - IdentityUserManager userManager) + UserManager userManager) : Endpoint { public override void Configure() diff --git a/backend/src/Web/Features/Users/Handlers/ChangePortrait.cs b/backend/Modules/Identity/Handlers/ChangePortrait.cs similarity index 87% rename from backend/src/Web/Features/Users/Handlers/ChangePortrait.cs rename to backend/Modules/Identity/Handlers/ChangePortrait.cs index 676db7c..7869585 100644 --- a/backend/src/Web/Features/Users/Handlers/ChangePortrait.cs +++ b/backend/Modules/Identity/Handlers/ChangePortrait.cs @@ -1,8 +1,8 @@ -using Hutopy.Web.Common.BlobStorage; -using Hutopy.Web.Common.Security; -using Hutopy.Web.Features.Users.Data; +using Hutopy.Infrastructure.BlobStorage.Contracts; +using Hutopy.Infrastructure.Security; +using Hutopy.Modules.Identity.Data; -namespace Hutopy.Web.Features.Users.Handlers; +namespace Hutopy.Modules.Identity.Handlers; [PublicAPI] public record ChangePortraitRequest( @@ -25,8 +25,8 @@ public sealed class ChangePortraitRequestValidator : Validator { public override void Configure() diff --git a/backend/src/Web/Features/Users/Handlers/ForgotPassword.cs b/backend/Modules/Identity/Handlers/ForgotPassword.cs similarity index 89% rename from backend/src/Web/Features/Users/Handlers/ForgotPassword.cs rename to backend/Modules/Identity/Handlers/ForgotPassword.cs index 12ee765..e958aad 100644 --- a/backend/src/Web/Features/Users/Handlers/ForgotPassword.cs +++ b/backend/Modules/Identity/Handlers/ForgotPassword.cs @@ -1,11 +1,11 @@ -using Hutopy.Web.Features.Users; -using Hutopy.Web.Features.Users.Data; -using Hutopy.Web.Features.Users.Services; -using Microsoft.Extensions.Options; using System.Text; using System.Web; +using Hutopy.Infrastructure.Configuration; +using Hutopy.Infrastructure.Emailer.Contracts; +using Hutopy.Modules.Identity.Data; +using Microsoft.Extensions.Options; -namespace Hutopy.Web.Features.Users.Handlers; +namespace Hutopy.Modules.Identity.Handlers; [PublicAPI] public record ForgotPasswordRequest( @@ -13,9 +13,8 @@ public record ForgotPasswordRequest( [PublicAPI] public class ForgotPasswordHandler( - IdentityUserManager userManager, + UserManager userManager, IEmailSender emailSender, - ILogger logger, IOptionsSnapshot options) : Endpoint { diff --git a/backend/src/Web/Features/Users/Handlers/GetCurrentUser.cs b/backend/Modules/Identity/Handlers/GetCurrentUser.cs similarity index 90% rename from backend/src/Web/Features/Users/Handlers/GetCurrentUser.cs rename to backend/Modules/Identity/Handlers/GetCurrentUser.cs index 41fb072..a8fd411 100644 --- a/backend/src/Web/Features/Users/Handlers/GetCurrentUser.cs +++ b/backend/Modules/Identity/Handlers/GetCurrentUser.cs @@ -1,7 +1,7 @@ -using Hutopy.Web.Features.Users.Handlers.Models; -using Hutopy.Web.Features.Users.Data; +using Hutopy.Modules.Identity.Data; +using Hutopy.Modules.Identity.Models; -namespace Hutopy.Web.Features.Users.Handlers; +namespace Hutopy.Modules.Identity.Handlers; [PublicAPI] public class GetCurrentUserQueryHandler( @@ -24,9 +24,9 @@ public class GetCurrentUserQueryHandler( await SendNotFoundAsync(cancellationToken); return; } - + var roles = await identityService.GetCurrentUserRolesAsync(); - + await SendOkAsync( new UserDto { diff --git a/backend/src/Web/Features/Users/Handlers/GetCurrentUserProfilePicture.cs b/backend/Modules/Identity/Handlers/GetCurrentUserProfilePicture.cs similarity index 84% rename from backend/src/Web/Features/Users/Handlers/GetCurrentUserProfilePicture.cs rename to backend/Modules/Identity/Handlers/GetCurrentUserProfilePicture.cs index 0d5f0fc..a26825d 100644 --- a/backend/src/Web/Features/Users/Handlers/GetCurrentUserProfilePicture.cs +++ b/backend/Modules/Identity/Handlers/GetCurrentUserProfilePicture.cs @@ -1,12 +1,12 @@ -using Hutopy.Web.Common.BlobStorage; -using Hutopy.Web.Features.Users.Data; +using Hutopy.Infrastructure.BlobStorage.Contracts; +using Hutopy.Modules.Identity.Data; -namespace Hutopy.Web.Features.Users.Handlers; +namespace Hutopy.Modules.Identity.Handlers; [PublicAPI] public class GetCurrentUserPortraitHandler( IdentityService identityService, - AzureBlobStorage blobStorage + IBlobStorage blobStorage ) : EndpointWithoutRequest { diff --git a/backend/src/Web/Features/Users/Handlers/Login.cs b/backend/Modules/Identity/Handlers/Login.cs similarity index 91% rename from backend/src/Web/Features/Users/Handlers/Login.cs rename to backend/Modules/Identity/Handlers/Login.cs index 633ff6b..ba125bc 100644 --- a/backend/src/Web/Features/Users/Handlers/Login.cs +++ b/backend/Modules/Identity/Handlers/Login.cs @@ -1,8 +1,9 @@ -using Hutopy.Web.Common.Security; -using Hutopy.Web.Features.Users.Data; +using Hutopy.Infrastructure.Security; +using Hutopy.Modules.Identity.Configuration; +using Hutopy.Modules.Identity.Data; using Microsoft.Extensions.Options; -namespace Hutopy.Web.Features.Users.Handlers; +namespace Hutopy.Modules.Identity.Handlers; [PublicAPI] public record LoginRequest( @@ -16,7 +17,7 @@ public record LoginResponse( [PublicAPI] public class LoginHandler( - IdentityUserManager userManager, + UserManager userManager, IOptionsSnapshot jwtOptions) : Endpoint { diff --git a/backend/src/Web/Features/Users/Handlers/LoginWithFacebook.cs b/backend/Modules/Identity/Handlers/LoginWithFacebook.cs similarity index 94% rename from backend/src/Web/Features/Users/Handlers/LoginWithFacebook.cs rename to backend/Modules/Identity/Handlers/LoginWithFacebook.cs index f0ebcd0..6810b34 100644 --- a/backend/src/Web/Features/Users/Handlers/LoginWithFacebook.cs +++ b/backend/Modules/Identity/Handlers/LoginWithFacebook.cs @@ -1,11 +1,11 @@ using System.Text.Json; using System.Text.Json.Serialization; -using Hutopy.Web.Common.Security; -using Hutopy.Web.Features.Users.Data; +using Hutopy.Infrastructure.Security; +using Hutopy.Modules.Identity.Configuration; +using Hutopy.Modules.Identity.Data; using Microsoft.Extensions.Options; -using IdentityUser = Hutopy.Web.Features.Users.Data.IdentityUser; -namespace Hutopy.Web.Features.Users.Handlers; +namespace Hutopy.Modules.Identity.Handlers; [PublicAPI] public class FacebookUserInfo @@ -40,7 +40,7 @@ public record LoginWithFacebookResponse( [PublicAPI] public class LoginWithFacebookHandler( IHttpClientFactory httpClientFactory, - IdentityUserManager userManager, + UserManager userManager, IOptionsSnapshot jwtOptions) : Endpoint { @@ -87,7 +87,7 @@ public class LoginWithFacebookHandler( if (user is null) { var generatedPassword = PasswordGenerator.Next(); - var generatedUser = new IdentityUser + var generatedUser = new User { UserName = userInfo.Email ?? $"fb_{userInfo.Id}", Email = userInfo.Email, diff --git a/backend/src/Web/Features/Users/Handlers/LoginWithGoogle.cs b/backend/Modules/Identity/Handlers/LoginWithGoogle.cs similarity index 94% rename from backend/src/Web/Features/Users/Handlers/LoginWithGoogle.cs rename to backend/Modules/Identity/Handlers/LoginWithGoogle.cs index a0c37e2..deabebd 100644 --- a/backend/src/Web/Features/Users/Handlers/LoginWithGoogle.cs +++ b/backend/Modules/Identity/Handlers/LoginWithGoogle.cs @@ -1,11 +1,11 @@ using System.Text.Json; using System.Text.Json.Serialization; -using Hutopy.Web.Common.Security; -using Hutopy.Web.Features.Users.Data; +using Hutopy.Infrastructure.Security; +using Hutopy.Modules.Identity.Configuration; +using Hutopy.Modules.Identity.Data; using Microsoft.Extensions.Options; -using IdentityUser = Hutopy.Web.Features.Users.Data.IdentityUser; -namespace Hutopy.Web.Features.Users.Handlers; +namespace Hutopy.Modules.Identity.Handlers; class GoogleToken { @@ -40,7 +40,7 @@ public record LoginWithGoogleResponse( [PublicAPI] public class LoginWithGoogleHandler( IHttpClientFactory httpClientFactory, - IdentityUserManager userManager, + UserManager userManager, IOptionsSnapshot jwtOptions) : Endpoint { @@ -92,7 +92,7 @@ public class LoginWithGoogleHandler( { var generatedPassword = PasswordGenerator.Next(); var refreshToken = RefreshTokenGenerator.Next(); - var generatedUser = new IdentityUser + var generatedUser = new User { UserName = userInfo.Email, Email = userInfo.Email, diff --git a/backend/src/Web/Features/Users/Handlers/RefreshToken.cs b/backend/Modules/Identity/Handlers/RefreshToken.cs similarity index 72% rename from backend/src/Web/Features/Users/Handlers/RefreshToken.cs rename to backend/Modules/Identity/Handlers/RefreshToken.cs index 7000347..c3304b9 100644 --- a/backend/src/Web/Features/Users/Handlers/RefreshToken.cs +++ b/backend/Modules/Identity/Handlers/RefreshToken.cs @@ -1,8 +1,9 @@ -using Hutopy.Web.Common.Security; -using Hutopy.Web.Features.Users.Data; +using Hutopy.Infrastructure.Security; +using Hutopy.Modules.Identity.Configuration; +using Hutopy.Modules.Identity.Data; using Microsoft.Extensions.Options; -namespace Hutopy.Web.Features.Users.Handlers; +namespace Hutopy.Modules.Identity.Handlers; [PublicAPI] public record RefreshTokenRequest( @@ -15,7 +16,7 @@ public record RefreshTokenResponse( [PublicAPI] public class RefreshTokenHandler( - IdentityUserManager userManager, + UserManager userManager, IOptionsSnapshot jwtOptions) : Endpoint { @@ -30,7 +31,7 @@ public class RefreshTokenHandler( RefreshTokenRequest request, CancellationToken ct) { - // Find user with the refresh token + // Find the user using the refresh token var user = await userManager.Users .FirstOrDefaultAsync(u => u.RefreshToken == request.RefreshToken, ct); @@ -40,23 +41,17 @@ public class RefreshTokenHandler( return; } - // Generate new refresh token if rotation is required - string newRefreshToken; - if (jwtOptions.Value.RefreshTokenRequireRotation) + // Generate a new refresh token if rotation is required + if (jwtOptions.Value.RefreshTokenRequireRotation || user.RefreshToken is null) { - newRefreshToken = RefreshTokenGenerator.Next(); - user.RefreshToken = newRefreshToken; - } - else - { - newRefreshToken = user.RefreshToken; + user.RefreshToken = RefreshTokenGenerator.Next(); } // Update refresh token expiry time user.RefreshTokenExpiryTime = DateTime.UtcNow.Add(jwtOptions.Value.RefreshTokenLifetime); await userManager.UpdateAsync(user); - // Generate new access token + // Generate a new access token var accessToken = JwtTokenHelper.GenerateJwtToken( expiresIn: jwtOptions.Value.Lifetime, issuer: jwtOptions.Value.Issuer, @@ -70,7 +65,7 @@ public class RefreshTokenHandler( portraitUrl: user.PortraitUrl); await SendOkAsync( - new RefreshTokenResponse(accessToken, newRefreshToken), + new RefreshTokenResponse(accessToken, user.RefreshToken), cancellation: ct); } } diff --git a/backend/src/Web/Features/Users/Handlers/Register.cs b/backend/Modules/Identity/Handlers/Register.cs similarity index 85% rename from backend/src/Web/Features/Users/Handlers/Register.cs rename to backend/Modules/Identity/Handlers/Register.cs index 3039a73..dff4edb 100644 --- a/backend/src/Web/Features/Users/Handlers/Register.cs +++ b/backend/Modules/Identity/Handlers/Register.cs @@ -1,9 +1,9 @@ -using Hutopy.Web.Common.Security; -using Hutopy.Web.Features.Users.Data; +using Hutopy.Infrastructure.Security; +using Hutopy.Modules.Identity.Configuration; +using Hutopy.Modules.Identity.Data; using Microsoft.Extensions.Options; -using IdentityUser = Hutopy.Web.Features.Users.Data.IdentityUser; -namespace Hutopy.Web.Features.Users.Handlers; +namespace Hutopy.Modules.Identity.Handlers; [PublicAPI] public record RegisterRequest( @@ -18,7 +18,7 @@ public record RegisterResponse( [PublicAPI] public class RegisterHandler( - IdentityUserManager userManager, + UserManager userManager, IOptionsSnapshot jwtOptions) : Endpoint { @@ -33,7 +33,7 @@ public class RegisterHandler( RegisterRequest request, CancellationToken ct) { - // Check if user already exists + // Check if the user already exists var existingUser = await userManager.FindByEmailAsync(request.Email); if (existingUser is not null) { @@ -44,16 +44,16 @@ public class RegisterHandler( return; } - // Create refresh token + // Create a refresh token var refreshToken = RefreshTokenGenerator.Next(); - // Split name into firstname and lastname (if provided) + // Split the name into firstname and lastname (if provided) var nameParts = request.Name.Split(' ', 2); var firstname = nameParts[0]; var lastname = nameParts.Length > 1 ? nameParts[1] : string.Empty; - // Create new user - var user = new IdentityUser + // Create a new user + var user = new User { UserName = request.Email, Email = request.Email, diff --git a/backend/src/Web/Features/Users/Handlers/ResetPassword.cs b/backend/Modules/Identity/Handlers/ResetPassword.cs similarity index 90% rename from backend/src/Web/Features/Users/Handlers/ResetPassword.cs rename to backend/Modules/Identity/Handlers/ResetPassword.cs index b005eae..e65c0c7 100644 --- a/backend/src/Web/Features/Users/Handlers/ResetPassword.cs +++ b/backend/Modules/Identity/Handlers/ResetPassword.cs @@ -1,6 +1,6 @@ -using Hutopy.Web.Features.Users.Data; +using Hutopy.Modules.Identity.Data; -namespace Hutopy.Web.Features.Users.Handlers; +namespace Hutopy.Modules.Identity.Handlers; [PublicAPI] public record ResetPasswordRequest( @@ -10,7 +10,7 @@ public record ResetPasswordRequest( [PublicAPI] public class ResetPasswordHandler( - IdentityUserManager userManager) + UserManager userManager) : Endpoint { public override void Configure() diff --git a/backend/src/Web/Features/Users/Handlers/SetPassword.cs b/backend/Modules/Identity/Handlers/SetPassword.cs similarity index 88% rename from backend/src/Web/Features/Users/Handlers/SetPassword.cs rename to backend/Modules/Identity/Handlers/SetPassword.cs index 9b1f60e..12215a5 100644 --- a/backend/src/Web/Features/Users/Handlers/SetPassword.cs +++ b/backend/Modules/Identity/Handlers/SetPassword.cs @@ -1,7 +1,7 @@ -using Hutopy.Web.Common.Security; -using Hutopy.Web.Features.Users.Data; +using Hutopy.Infrastructure.Security; +using Hutopy.Modules.Identity.Data; -namespace Hutopy.Web.Features.Users.Handlers; +namespace Hutopy.Modules.Identity.Handlers; [PublicAPI] public record SetPasswordRequest( @@ -9,7 +9,7 @@ public record SetPasswordRequest( [PublicAPI] public class SetPasswordHandler( - IdentityUserManager userManager) + UserManager userManager) : Endpoint { public override void Configure() diff --git a/backend/src/Web/Features/Users/IdentityResultExtensions.cs b/backend/Modules/Identity/IdentityResultExtensions.cs similarity index 79% rename from backend/src/Web/Features/Users/IdentityResultExtensions.cs rename to backend/Modules/Identity/IdentityResultExtensions.cs index a9cad9f..a07ee1a 100644 --- a/backend/src/Web/Features/Users/IdentityResultExtensions.cs +++ b/backend/Modules/Identity/IdentityResultExtensions.cs @@ -1,7 +1,7 @@ -using Hutopy.Web.Features.Users.Models; +using Hutopy.Modules.Identity.Models; using Microsoft.AspNetCore.Identity; -namespace Hutopy.Web.Features.Users; +namespace Hutopy.Modules.Identity; public static class IdentityResultExtensions { diff --git a/backend/src/Web/Features/Users/Data/Migrations/20250417060553_AddsRefreshToken.Designer.cs b/backend/Modules/Identity/Migrations/20250609203622_Initial.Designer.cs similarity index 86% rename from backend/src/Web/Features/Users/Data/Migrations/20250417060553_AddsRefreshToken.Designer.cs rename to backend/Modules/Identity/Migrations/20250609203622_Initial.Designer.cs index 2a6a396..e64669d 100644 --- a/backend/src/Web/Features/Users/Data/Migrations/20250417060553_AddsRefreshToken.Designer.cs +++ b/backend/Modules/Identity/Migrations/20250609203622_Initial.Designer.cs @@ -1,6 +1,6 @@ // using System; -using Hutopy.Web.Features.Users.Data; +using Hutopy.Modules.Identity.Data; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations; @@ -9,11 +9,11 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; #nullable disable -namespace Hutopy.Web.Features.Users.Data.Migrations +namespace Hutopy.Modules.Identity.Migrations { - [DbContext(typeof(ApplicationDbContext))] - [Migration("20250417060553_AddsRefreshToken")] - partial class AddsRefreshToken + [DbContext(typeof(IdentityDbContext))] + [Migration("20250609203622_Initial")] + partial class Initial { /// protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -26,7 +26,7 @@ namespace Hutopy.Web.Features.Users.Data.Migrations NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - modelBuilder.Entity("Hutopy.Web.Features.Users.Data.IdentityRole", b => + modelBuilder.Entity("Hutopy.Modules.Identity.Data.Role", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -53,7 +53,7 @@ namespace Hutopy.Web.Features.Users.Data.Migrations b.ToTable("AspNetRoles", "Identity"); }); - modelBuilder.Entity("Hutopy.Web.Features.Users.Data.IdentityUser", b => + modelBuilder.Entity("Hutopy.Modules.Identity.Data.User", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -63,12 +63,12 @@ namespace Hutopy.Web.Features.Users.Data.Migrations .HasColumnType("integer"); b.Property("Address") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); + .HasMaxLength(256) + .HasColumnType("character varying(256)"); b.Property("Alias") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); + .HasMaxLength(256) + .HasColumnType("character varying(256)"); b.Property("BirthDate") .HasColumnType("timestamp with time zone"); @@ -85,20 +85,20 @@ namespace Hutopy.Web.Features.Users.Data.Migrations .HasColumnType("boolean"); b.Property("FacebookId") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); + .HasMaxLength(256) + .HasColumnType("character varying(256)"); b.Property("Firstname") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); + .HasMaxLength(256) + .HasColumnType("character varying(256)"); b.Property("GoogleId") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); + .HasMaxLength(256) + .HasColumnType("character varying(256)"); b.Property("Lastname") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); + .HasMaxLength(256) + .HasColumnType("character varying(256)"); b.Property("LockoutEnabled") .HasColumnType("boolean"); @@ -128,8 +128,8 @@ namespace Hutopy.Web.Features.Users.Data.Migrations .HasColumnType("character varying(2048)"); b.Property("RefreshToken") - .IsRequired() - .HasColumnType("text"); + .HasMaxLength(44) + .HasColumnType("character varying(44)"); b.Property("RefreshTokenExpiryTime") .HasColumnType("timestamp with time zone"); @@ -261,7 +261,7 @@ namespace Hutopy.Web.Features.Users.Data.Migrations modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => { - b.HasOne("Hutopy.Web.Features.Users.Data.IdentityRole", null) + b.HasOne("Hutopy.Modules.Identity.Data.Role", null) .WithMany() .HasForeignKey("RoleId") .OnDelete(DeleteBehavior.Cascade) @@ -270,7 +270,7 @@ namespace Hutopy.Web.Features.Users.Data.Migrations modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => { - b.HasOne("Hutopy.Web.Features.Users.Data.IdentityUser", null) + b.HasOne("Hutopy.Modules.Identity.Data.User", null) .WithMany() .HasForeignKey("UserId") .OnDelete(DeleteBehavior.Cascade) @@ -279,7 +279,7 @@ namespace Hutopy.Web.Features.Users.Data.Migrations modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => { - b.HasOne("Hutopy.Web.Features.Users.Data.IdentityUser", null) + b.HasOne("Hutopy.Modules.Identity.Data.User", null) .WithMany() .HasForeignKey("UserId") .OnDelete(DeleteBehavior.Cascade) @@ -288,13 +288,13 @@ namespace Hutopy.Web.Features.Users.Data.Migrations modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => { - b.HasOne("Hutopy.Web.Features.Users.Data.IdentityRole", null) + b.HasOne("Hutopy.Modules.Identity.Data.Role", null) .WithMany() .HasForeignKey("RoleId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.HasOne("Hutopy.Web.Features.Users.Data.IdentityUser", null) + b.HasOne("Hutopy.Modules.Identity.Data.User", null) .WithMany() .HasForeignKey("UserId") .OnDelete(DeleteBehavior.Cascade) @@ -303,7 +303,7 @@ namespace Hutopy.Web.Features.Users.Data.Migrations modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => { - b.HasOne("Hutopy.Web.Features.Users.Data.IdentityUser", null) + b.HasOne("Hutopy.Modules.Identity.Data.User", null) .WithMany() .HasForeignKey("UserId") .OnDelete(DeleteBehavior.Cascade) diff --git a/backend/src/Web/Features/Users/Data/Migrations/20241020183421_Initial.cs b/backend/Modules/Identity/Migrations/20250609203622_Initial.cs similarity index 94% rename from backend/src/Web/Features/Users/Data/Migrations/20241020183421_Initial.cs rename to backend/Modules/Identity/Migrations/20250609203622_Initial.cs index 76dd942..7bc43bf 100644 --- a/backend/src/Web/Features/Users/Data/Migrations/20241020183421_Initial.cs +++ b/backend/Modules/Identity/Migrations/20250609203622_Initial.cs @@ -4,7 +4,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; #nullable disable -namespace Hutopy.Web.Features.Users.Data.Migrations +namespace Hutopy.Modules.Identity.Migrations { /// public partial class Initial : Migration @@ -36,13 +36,16 @@ namespace Hutopy.Web.Features.Users.Data.Migrations columns: table => new { Id = table.Column(type: "uuid", nullable: false), - Alias = table.Column(type: "character varying(255)", maxLength: 255, nullable: true), - Firstname = table.Column(type: "character varying(255)", maxLength: 255, nullable: true), - Lastname = table.Column(type: "character varying(255)", maxLength: 255, nullable: true), + Alias = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), + Firstname = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), + Lastname = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), BirthDate = table.Column(type: "timestamp with time zone", nullable: true), - Address = table.Column(type: "character varying(255)", maxLength: 255, nullable: true), - PortraitUrl = table.Column(type: "character varying(255)", maxLength: 255, nullable: true), - GoogleId = table.Column(type: "character varying(255)", maxLength: 255, nullable: true), + Address = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), + PortraitUrl = table.Column(type: "character varying(2048)", maxLength: 2048, nullable: true), + GoogleId = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), + FacebookId = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), + RefreshToken = table.Column(type: "character varying(44)", maxLength: 44, nullable: true), + RefreshTokenExpiryTime = table.Column(type: "timestamp with time zone", nullable: false), UserName = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), NormalizedUserName = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), Email = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), diff --git a/backend/src/Web/Features/Users/Data/Migrations/ApplicationDbContextModelSnapshot.cs b/backend/Modules/Identity/Migrations/IdentityDbContextModelSnapshot.cs similarity index 86% rename from backend/src/Web/Features/Users/Data/Migrations/ApplicationDbContextModelSnapshot.cs rename to backend/Modules/Identity/Migrations/IdentityDbContextModelSnapshot.cs index 1e1c5e9..f22b773 100644 --- a/backend/src/Web/Features/Users/Data/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/backend/Modules/Identity/Migrations/IdentityDbContextModelSnapshot.cs @@ -1,6 +1,6 @@ // using System; -using Hutopy.Web.Features.Users.Data; +using Hutopy.Modules.Identity.Data; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; @@ -8,10 +8,10 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; #nullable disable -namespace Hutopy.Web.Features.Users.Data.Migrations +namespace Hutopy.Modules.Identity.Migrations { - [DbContext(typeof(ApplicationDbContext))] - partial class ApplicationDbContextModelSnapshot : ModelSnapshot + [DbContext(typeof(IdentityDbContext))] + partial class IdentityDbContextModelSnapshot : ModelSnapshot { protected override void BuildModel(ModelBuilder modelBuilder) { @@ -23,7 +23,7 @@ namespace Hutopy.Web.Features.Users.Data.Migrations NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - modelBuilder.Entity("Hutopy.Web.Features.Users.Data.IdentityRole", b => + modelBuilder.Entity("Hutopy.Modules.Identity.Data.Role", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -50,7 +50,7 @@ namespace Hutopy.Web.Features.Users.Data.Migrations b.ToTable("AspNetRoles", "Identity"); }); - modelBuilder.Entity("Hutopy.Web.Features.Users.Data.IdentityUser", b => + modelBuilder.Entity("Hutopy.Modules.Identity.Data.User", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -60,12 +60,12 @@ namespace Hutopy.Web.Features.Users.Data.Migrations .HasColumnType("integer"); b.Property("Address") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); + .HasMaxLength(256) + .HasColumnType("character varying(256)"); b.Property("Alias") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); + .HasMaxLength(256) + .HasColumnType("character varying(256)"); b.Property("BirthDate") .HasColumnType("timestamp with time zone"); @@ -82,20 +82,20 @@ namespace Hutopy.Web.Features.Users.Data.Migrations .HasColumnType("boolean"); b.Property("FacebookId") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); + .HasMaxLength(256) + .HasColumnType("character varying(256)"); b.Property("Firstname") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); + .HasMaxLength(256) + .HasColumnType("character varying(256)"); b.Property("GoogleId") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); + .HasMaxLength(256) + .HasColumnType("character varying(256)"); b.Property("Lastname") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); + .HasMaxLength(256) + .HasColumnType("character varying(256)"); b.Property("LockoutEnabled") .HasColumnType("boolean"); @@ -125,8 +125,8 @@ namespace Hutopy.Web.Features.Users.Data.Migrations .HasColumnType("character varying(2048)"); b.Property("RefreshToken") - .IsRequired() - .HasColumnType("text"); + .HasMaxLength(44) + .HasColumnType("character varying(44)"); b.Property("RefreshTokenExpiryTime") .HasColumnType("timestamp with time zone"); @@ -258,7 +258,7 @@ namespace Hutopy.Web.Features.Users.Data.Migrations modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => { - b.HasOne("Hutopy.Web.Features.Users.Data.IdentityRole", null) + b.HasOne("Hutopy.Modules.Identity.Data.Role", null) .WithMany() .HasForeignKey("RoleId") .OnDelete(DeleteBehavior.Cascade) @@ -267,7 +267,7 @@ namespace Hutopy.Web.Features.Users.Data.Migrations modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => { - b.HasOne("Hutopy.Web.Features.Users.Data.IdentityUser", null) + b.HasOne("Hutopy.Modules.Identity.Data.User", null) .WithMany() .HasForeignKey("UserId") .OnDelete(DeleteBehavior.Cascade) @@ -276,7 +276,7 @@ namespace Hutopy.Web.Features.Users.Data.Migrations modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => { - b.HasOne("Hutopy.Web.Features.Users.Data.IdentityUser", null) + b.HasOne("Hutopy.Modules.Identity.Data.User", null) .WithMany() .HasForeignKey("UserId") .OnDelete(DeleteBehavior.Cascade) @@ -285,13 +285,13 @@ namespace Hutopy.Web.Features.Users.Data.Migrations modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => { - b.HasOne("Hutopy.Web.Features.Users.Data.IdentityRole", null) + b.HasOne("Hutopy.Modules.Identity.Data.Role", null) .WithMany() .HasForeignKey("RoleId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.HasOne("Hutopy.Web.Features.Users.Data.IdentityUser", null) + b.HasOne("Hutopy.Modules.Identity.Data.User", null) .WithMany() .HasForeignKey("UserId") .OnDelete(DeleteBehavior.Cascade) @@ -300,7 +300,7 @@ namespace Hutopy.Web.Features.Users.Data.Migrations modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => { - b.HasOne("Hutopy.Web.Features.Users.Data.IdentityUser", null) + b.HasOne("Hutopy.Modules.Identity.Data.User", null) .WithMany() .HasForeignKey("UserId") .OnDelete(DeleteBehavior.Cascade) diff --git a/backend/src/Web/Features/Users/Models/Result.cs b/backend/Modules/Identity/Models/Result.cs similarity index 96% rename from backend/src/Web/Features/Users/Models/Result.cs rename to backend/Modules/Identity/Models/Result.cs index 8e34553..1cb4786 100644 --- a/backend/src/Web/Features/Users/Models/Result.cs +++ b/backend/Modules/Identity/Models/Result.cs @@ -1,4 +1,4 @@ -namespace Hutopy.Web.Features.Users.Models; +namespace Hutopy.Modules.Identity.Models; public class Result( bool succeeded, diff --git a/backend/src/Web/Features/Users/Models/RoleModel.cs b/backend/Modules/Identity/Models/RoleModel.cs similarity index 69% rename from backend/src/Web/Features/Users/Models/RoleModel.cs rename to backend/Modules/Identity/Models/RoleModel.cs index a6243e1..060e6ec 100644 --- a/backend/src/Web/Features/Users/Models/RoleModel.cs +++ b/backend/Modules/Identity/Models/RoleModel.cs @@ -1,4 +1,4 @@ -namespace Hutopy.Web.Features.Users.Models; +namespace Hutopy.Modules.Identity.Models; public class RoleModel { diff --git a/backend/src/Web/Features/Users/Handlers/Models/UserDto.cs b/backend/Modules/Identity/Models/UserDto.cs similarity index 90% rename from backend/src/Web/Features/Users/Handlers/Models/UserDto.cs rename to backend/Modules/Identity/Models/UserDto.cs index b546f29..df3da49 100644 --- a/backend/src/Web/Features/Users/Handlers/Models/UserDto.cs +++ b/backend/Modules/Identity/Models/UserDto.cs @@ -1,4 +1,4 @@ -namespace Hutopy.Web.Features.Users.Handlers.Models; +namespace Hutopy.Modules.Identity.Models; public class UserDto { diff --git a/backend/src/Web/Features/Users/Models/UserModel.cs b/backend/Modules/Identity/Models/UserModel.cs similarity index 91% rename from backend/src/Web/Features/Users/Models/UserModel.cs rename to backend/Modules/Identity/Models/UserModel.cs index 4172360..e06a780 100644 --- a/backend/src/Web/Features/Users/Models/UserModel.cs +++ b/backend/Modules/Identity/Models/UserModel.cs @@ -1,4 +1,4 @@ -namespace Hutopy.Web.Features.Users.Models; +namespace Hutopy.Modules.Identity.Models; public class UserModel { diff --git a/backend/Modules/Identity/Services/UserLookup.cs b/backend/Modules/Identity/Services/UserLookup.cs new file mode 100644 index 0000000..0e8ad68 --- /dev/null +++ b/backend/Modules/Identity/Services/UserLookup.cs @@ -0,0 +1,21 @@ +using Hutopy.Modules.Identity.Contracts; +using Hutopy.Modules.Identity.Data; + +namespace Hutopy.Modules.Identity.Services; + +public sealed class UserLookup( + UserManager userManager) + : IUserLookup +{ + public async Task GetUserAsync(Guid userId, CancellationToken cancellationToken = default) + { + var user = await userManager.FindByIdAsync(userId.ToString()); + + return user is null + ? null + : new UserReference( + user.Id, + user.Fullname, + user.PortraitUrl); + } +} diff --git a/backend/Modules/Memberships/Contracts/IMembershipCancellationProcessor.cs b/backend/Modules/Memberships/Contracts/IMembershipCancellationProcessor.cs new file mode 100644 index 0000000..9fbcf7d --- /dev/null +++ b/backend/Modules/Memberships/Contracts/IMembershipCancellationProcessor.cs @@ -0,0 +1,6 @@ +namespace Hutopy.Modules.Memberships.Contracts; + +public interface IMembershipCancellationProcessor +{ + Task CancelAsync(string subscriptionId, CancellationToken ct = default); +} diff --git a/backend/Modules/Memberships/Contracts/IMembershipNotifier.cs b/backend/Modules/Memberships/Contracts/IMembershipNotifier.cs new file mode 100644 index 0000000..31aacab --- /dev/null +++ b/backend/Modules/Memberships/Contracts/IMembershipNotifier.cs @@ -0,0 +1,26 @@ +namespace Hutopy.Modules.Memberships.Contracts; + +public interface IMembershipNotifier +{ + Task NotifyCheckoutSessionCompleted(string stripeSessionId, string stripeSubscriptionId, + string userId, + string creatorId, + string tierId, + CancellationToken cancellationToken = default); + + Task NotifyPaymentSucceedAsync( + string stripeSubscriptionId, + string hostedInvoiceUrl, + decimal amount, + string currency, + CancellationToken cancellationToken = default); + + Task NotifySubscriptionUpdatedAsync( + string subscriptionId, + DateTimeOffset? endDate, + CancellationToken cancellationToken = default); + + Task NotifySubscriptionDeletedAsync( + string subscriptionId, + CancellationToken cancellationToken = default); +} diff --git a/backend/Modules/Memberships/Contracts/IMembershipPaymentProcessor.cs b/backend/Modules/Memberships/Contracts/IMembershipPaymentProcessor.cs new file mode 100644 index 0000000..49a4adb --- /dev/null +++ b/backend/Modules/Memberships/Contracts/IMembershipPaymentProcessor.cs @@ -0,0 +1,14 @@ +using Hutopy.Modules.Creators.Contracts; + +namespace Hutopy.Modules.Memberships.Contracts; + +public interface IMembershipPaymentProcessor +{ + Task CreateCheckoutSessionAsync( + Guid userId, + CreatorReference creatorReference, + Guid tierId, + string priceId, + string successUrl, + string cancelUrl); +} diff --git a/backend/Modules/Memberships/Contracts/IMembershipTierProcessor.cs b/backend/Modules/Memberships/Contracts/IMembershipTierProcessor.cs new file mode 100644 index 0000000..d6f1ff4 --- /dev/null +++ b/backend/Modules/Memberships/Contracts/IMembershipTierProcessor.cs @@ -0,0 +1,11 @@ +namespace Hutopy.Modules.Memberships.Contracts; + +public interface IMembershipTierProcessor +{ + Task CreateAsync( + Guid creatorId, + Guid tierId, + string productName, + string currencyCode, + decimal amount); +} diff --git a/backend/Modules/Memberships/Contracts/MembershipCheckoutSession.cs b/backend/Modules/Memberships/Contracts/MembershipCheckoutSession.cs new file mode 100644 index 0000000..748eb0b --- /dev/null +++ b/backend/Modules/Memberships/Contracts/MembershipCheckoutSession.cs @@ -0,0 +1,6 @@ +namespace Hutopy.Modules.Memberships.Contracts; + +[PublicAPI] +public record MembershipCheckoutSession( + string Id, + string Url); diff --git a/backend/Modules/Memberships/Data/Membership.cs b/backend/Modules/Memberships/Data/Membership.cs new file mode 100644 index 0000000..1edc7de --- /dev/null +++ b/backend/Modules/Memberships/Data/Membership.cs @@ -0,0 +1,20 @@ +using System.ComponentModel.DataAnnotations; + +namespace Hutopy.Modules.Memberships.Data; + +public class Membership +{ + public Guid Id { get; set; } + public DateTimeOffset CreatedAt { get; set; } + public Guid UserId { get; set; } + public Guid CreatorId { get; set; } + public Guid TierId { get; set; } + public MembershipTier? MembershipTier { get; set; } + public MembershipState State { get; set; } + public DateTimeOffset? StartDate { get; set; } + public DateTimeOffset? EndDate { get; set; } + public bool IsActive => EndDate == null || EndDate > DateTimeOffset.UtcNow; + [MaxLength(256)]public string? StripeSubscriptionId { get; set; } + + public ICollection Payments { get; set; } = []; +} diff --git a/backend/Modules/Memberships/Data/MembershipState.cs b/backend/Modules/Memberships/Data/MembershipState.cs new file mode 100644 index 0000000..63c48bb --- /dev/null +++ b/backend/Modules/Memberships/Data/MembershipState.cs @@ -0,0 +1,8 @@ +namespace Hutopy.Modules.Memberships.Data; + +public enum MembershipState +{ + Pending, + Active, + Inactive +} diff --git a/backend/src/Web/Features/Memberships/Data/Tier.cs b/backend/Modules/Memberships/Data/MembershipTier.cs similarity index 64% rename from backend/src/Web/Features/Memberships/Data/Tier.cs rename to backend/Modules/Memberships/Data/MembershipTier.cs index ec35cc6..ee4b8b3 100644 --- a/backend/src/Web/Features/Memberships/Data/Tier.cs +++ b/backend/Modules/Memberships/Data/MembershipTier.cs @@ -1,13 +1,11 @@ using System.ComponentModel.DataAnnotations; +using Hutopy.Common.Domain; -namespace Hutopy.Web.Features.Memberships.Data; +namespace Hutopy.Modules.Memberships.Data; -public class Tier +public class MembershipTier : Entity { - public Guid Id { get; set; } - public DateTime CreatedAt { get; set; } public Guid CreatorId { get; set; } - public Creator Creator { get; set; } = null!; [MaxLength(128)] public string Name { get; set; } = null!; [MaxLength(4096)] public string Description { get; set; } = null!; public decimal Price { get; set; } @@ -15,5 +13,5 @@ public class Tier [MaxLength(128)] public string StripeProductId { get; set; } = null!; [MaxLength(128)] public string StripePriceId { get; set; } = null!; - public ICollection Subscriptions { get; set; } = []; + public ICollection Subscriptions { get; set; } = []; } diff --git a/backend/Modules/Memberships/Data/MembershipsDbContext.cs b/backend/Modules/Memberships/Data/MembershipsDbContext.cs new file mode 100644 index 0000000..73c2d88 --- /dev/null +++ b/backend/Modules/Memberships/Data/MembershipsDbContext.cs @@ -0,0 +1,36 @@ +namespace Hutopy.Modules.Memberships.Data; + +public sealed class MembershipsDbContext( + DbContextOptions options) + : DbContext(options) +{ + public const string SchemaName = "Memberships"; + + public DbSet MembershipTiers => Set(); + public DbSet Memberships => Set(); + public DbSet Payments => Set(); + + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.HasDefaultSchema(SchemaName); + + modelBuilder + .Entity() + .Property(c => c.CreatedAt) + .ValueGeneratedOnAdd() + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + modelBuilder + .Entity() + .Property(c => c.CreatedAt) + .ValueGeneratedOnAdd() + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + modelBuilder + .Entity() + .Property(c => c.CreatedAt) + .ValueGeneratedOnAdd() + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + } +} diff --git a/backend/Modules/Memberships/Data/Payment.cs b/backend/Modules/Memberships/Data/Payment.cs new file mode 100644 index 0000000..e7d4163 --- /dev/null +++ b/backend/Modules/Memberships/Data/Payment.cs @@ -0,0 +1,13 @@ +using System.ComponentModel.DataAnnotations; + +namespace Hutopy.Modules.Memberships.Data; + +public class Payment +{ + public Guid Id { get; set; } + public DateTimeOffset CreatedAt { get; set; } + public decimal Amount { get; set; } + [MaxLength(8)] public required string Currency { get; set; } + [MaxLength(2048)] public required string InvoiceUrl { get; set; } + +} diff --git a/backend/Modules/Memberships/DependencyInjection.cs b/backend/Modules/Memberships/DependencyInjection.cs new file mode 100644 index 0000000..9a798b5 --- /dev/null +++ b/backend/Modules/Memberships/DependencyInjection.cs @@ -0,0 +1,32 @@ +using Hutopy.Modules.Memberships.Contracts; +using Hutopy.Modules.Memberships.Data; +using Hutopy.Modules.Memberships.Services; + +namespace Hutopy.Modules.Memberships; + +public static class DependencyInjection +{ + public static WebApplicationBuilder AddMembershipModule( + this WebApplicationBuilder builder, + Action? configureAction = null) + { + builder.Services.AddDbContext(configureAction); + + builder.Services.AddTransient(); + + return builder; + } + + + public static async Task UseMembershipModuleAsync( + this IApplicationBuilder app, + CancellationToken cancellationToken = default) + { + var scopeFactory = app.ApplicationServices.GetRequiredService(); + using var scope = scopeFactory.CreateScope(); + await using var context = scope.ServiceProvider.GetRequiredService(); + await context.Database.MigrateAsync(cancellationToken: cancellationToken); + + return app; + } +} diff --git a/backend/src/Web/Features/Memberships/Handlers/CancelSubscription.cs b/backend/Modules/Memberships/Handlers/CancelMembership.cs similarity index 52% rename from backend/src/Web/Features/Memberships/Handlers/CancelSubscription.cs rename to backend/Modules/Memberships/Handlers/CancelMembership.cs index 6acc122..55f5489 100644 --- a/backend/src/Web/Features/Memberships/Handlers/CancelSubscription.cs +++ b/backend/Modules/Memberships/Handlers/CancelMembership.cs @@ -1,43 +1,44 @@ -using Hutopy.Web.Features.Memberships.Data; -using Hutopy.Web.Features.Memberships.Infrastructure; +using Hutopy.Modules.Memberships.Contracts; +using Hutopy.Modules.Memberships.Data; -namespace Hutopy.Web.Features.Memberships.Handlers; +namespace Hutopy.Modules.Memberships.Handlers; [PublicAPI] -public class CancelSubscriptionRequest +public class CancelMembershipRequest { public Guid SubscriptionId { get; set; } } -public class CancelSubscriptionHandler( - MembershipDbContext dbContext, - StripeService stripeService) - : Endpoint +public class CancelMembershipHandler( + MembershipsDbContext dbContext, + IMembershipCancellationProcessor cancellationProcessor) + : Endpoint { public override void Configure() { - Post("/api/membership/unsubscribe"); + Delete("/api/memberships"); Options(o => o.WithTags("Memberships")); } public override async Task HandleAsync( - CancelSubscriptionRequest req, + CancelMembershipRequest req, CancellationToken ct) { var subscription = await dbContext - .Subscriptions + .Memberships .FindAsync( [req.SubscriptionId], cancellationToken: ct); - if (subscription is not { EndDate: null }) + if (subscription is not { EndDate: null } + || subscription.StripeSubscriptionId is null) { await SendNotFoundAsync(ct); return; } // Cancel Stripe subscription - await stripeService.CancelSubscription(subscription.Id); + await cancellationProcessor.CancelAsync(subscription.StripeSubscriptionId, ct); // Update subscription in the system subscription.EndDate = DateTime.UtcNow; diff --git a/backend/src/Web/Features/Memberships/Handlers/CreateMembershipTier.cs b/backend/Modules/Memberships/Handlers/CreateMembershipTier.cs similarity index 67% rename from backend/src/Web/Features/Memberships/Handlers/CreateMembershipTier.cs rename to backend/Modules/Memberships/Handlers/CreateMembershipTier.cs index 5f7925f..9a09f40 100644 --- a/backend/src/Web/Features/Memberships/Handlers/CreateMembershipTier.cs +++ b/backend/Modules/Memberships/Handlers/CreateMembershipTier.cs @@ -1,7 +1,7 @@ -using Hutopy.Web.Features.Memberships.Data; -using Hutopy.Web.Features.Memberships.Infrastructure; +using Hutopy.Modules.Memberships.Contracts; +using Hutopy.Modules.Memberships.Data; -namespace Hutopy.Web.Features.Memberships.Handlers; +namespace Hutopy.Modules.Memberships.Handlers; [PublicAPI] public record struct CreateMembershipTierRequest( @@ -13,13 +13,13 @@ public record struct CreateMembershipTierRequest( [PublicAPI] public class CreateMembershipTierEndpoint( - MembershipDbContext dbContext, - StripeService stripe) + MembershipsDbContext dbContext, + IMembershipTierProcessor membershipTierProcessor) : Endpoint { public override void Configure() { - Post("/api/membership/tiers"); + Post("/api/memberships/tiers"); Options(o => o.WithTags("Memberships")); } @@ -27,9 +27,9 @@ public class CreateMembershipTierEndpoint( CreateMembershipTierRequest req, CancellationToken ct) { - var tierId = Guid.NewGuid(); + var tierId = Guid.CreateVersion7(); - var productId = await stripe.CreateProductAsync( + var productId = await membershipTierProcessor.CreateAsync( req.CreatorId, tierId, req.Name, @@ -37,7 +37,7 @@ public class CreateMembershipTierEndpoint( req.Price); // Record the new Tier - var tier = new Tier + var tier = new MembershipTier { Id = tierId, CreatorId = req.CreatorId, @@ -47,7 +47,7 @@ public class CreateMembershipTierEndpoint( StripeProductId = productId, }; - dbContext.Tiers.Add(tier); + dbContext.MembershipTiers.Add(tier); await dbContext.SaveChangesAsync(ct); diff --git a/backend/Modules/Memberships/Handlers/GetActiveMemberships.cs b/backend/Modules/Memberships/Handlers/GetActiveMemberships.cs new file mode 100644 index 0000000..1124db4 --- /dev/null +++ b/backend/Modules/Memberships/Handlers/GetActiveMemberships.cs @@ -0,0 +1,54 @@ +using Hutopy.Infrastructure.Security; +using Hutopy.Modules.Creators.Contracts; +using Hutopy.Modules.Memberships.Data; + +namespace Hutopy.Modules.Memberships.Handlers; + +[PublicAPI] +public record struct GetActiveMembershipsResponse( + Guid Id, + Guid CreatorId, + string CreatorName, + string CreatorPortraitUrl, + DateTimeOffset? StartDate, + DateTimeOffset? EndDate); + +[PublicAPI] +public class GetActiveMembershipsHandler( + ICreatorLookup creatorLookup, + MembershipsDbContext dbContext) + : EndpointWithoutRequest> +{ + public override void Configure() + { + Get("/api/memberships/active"); + Options(o => o.WithTags("Memberships")); + } + + public override async Task HandleAsync( + CancellationToken ct) + { + var subscriptions = await dbContext + .Memberships + .Where(subscription => subscription.UserId == User.GetUserId()) + .Where(subscription => subscription.State == MembershipState.Active) + .ToListAsync(ct); + + var result = await Task.WhenAll( + subscriptions.Select(async subscription => + { + var creator = await creatorLookup.GetCreatorAsync(subscription.CreatorId, ct); + + return new GetActiveMembershipsResponse( + subscription.Id, + subscription.CreatorId, + creator?.Name ?? "Unknown Creator", + creator?.PortraitUrl ?? string.Empty, + subscription.StartDate, + subscription.EndDate); + })); + + + await SendOkAsync(result, ct); + } +} diff --git a/backend/src/Web/Features/Memberships/Handlers/GetMembershipTier.cs b/backend/Modules/Memberships/Handlers/GetMembershipTiers.cs similarity index 67% rename from backend/src/Web/Features/Memberships/Handlers/GetMembershipTier.cs rename to backend/Modules/Memberships/Handlers/GetMembershipTiers.cs index a3981cc..f0cc625 100644 --- a/backend/src/Web/Features/Memberships/Handlers/GetMembershipTier.cs +++ b/backend/Modules/Memberships/Handlers/GetMembershipTiers.cs @@ -1,9 +1,9 @@ -using Hutopy.Web.Features.Memberships.Data; +using Hutopy.Modules.Memberships.Data; -namespace Hutopy.Web.Features.Memberships.Handlers; +namespace Hutopy.Modules.Memberships.Handlers; [PublicAPI] -public record GetMembershipTierRequest +public record GetMembershipTiersRequest { public Guid CreatorId { get; set; } } @@ -11,7 +11,7 @@ public record GetMembershipTierRequest [PublicAPI] public record struct TierModel( Guid Id, - DateTime CreatedAt, + DateTimeOffset CreatedAt, string Name, string Description, decimal Price, @@ -19,23 +19,23 @@ public record struct TierModel( string StripeProductId); [PublicAPI] -public class GetMembershipTierEndpoint( - MembershipDbContext dbContext) - : Endpoint> +public class GetMembershipTiersEndpoint( + MembershipsDbContext dbContext) + : Endpoint> { public override void Configure() { - Get("/api/membership/tiers/{CreatorId:guid}"); + Get("/api/memberships/tiers/{CreatorId:guid}"); Options(o => o.WithTags("Memberships")); AllowAnonymous(); } public override async Task HandleAsync( - GetMembershipTierRequest req, + GetMembershipTiersRequest req, CancellationToken ct) { var tiers = await dbContext - .Tiers + .MembershipTiers .Where(tier => tier.CreatorId == req.CreatorId) .Select(tier => new TierModel( tier.Id, diff --git a/backend/Modules/Memberships/Handlers/HandleStripe.cs b/backend/Modules/Memberships/Handlers/HandleStripe.cs new file mode 100644 index 0000000..9753670 --- /dev/null +++ b/backend/Modules/Memberships/Handlers/HandleStripe.cs @@ -0,0 +1,90 @@ +using System.Diagnostics; +using Hutopy.Infrastructure.Payments.Stripe.Configuration; +using Hutopy.Modules.Memberships.Contracts; +using Hutopy.Modules.Tipping.Contracts; +using Microsoft.Extensions.Options; +using Stripe; +using Stripe.Checkout; + +namespace Hutopy.Modules.Memberships.Handlers; + +public class StripeWebhookEndpoint( + ITipPaymentNotifier tipPaymentNotifier, + IMembershipNotifier membershipNotifier, + IOptions options) + : EndpointWithoutRequest +{ + public override void Configure() + { + Post("/api/stripe"); + AllowAnonymous(); + Options(o => o.WithTags( "Webhooks")); + } + + public override async Task HandleAsync(CancellationToken ct) + { + using var streamReader = new StreamReader(HttpContext.Request.Body); + var json = await streamReader.ReadToEndAsync(ct); + + var signatureHeader = HttpContext.Request.Headers["Stripe-Signature"]; + var stripeEvent = EventUtility.ConstructEvent(json, signatureHeader, options.Value.WebhookSecret); + + var stripeSession = stripeEvent.Data.Object as Session; + var stripeSubscription = stripeEvent.Data.Object as Subscription; + + switch (stripeEvent.Type) + { + case "checkout.session.completed": + Debug.Assert(stripeSession != null); + switch (stripeSession.Mode) + { + // Check if this is a one-time tip + case "payment" when stripeSession.PaymentIntentId != null + && stripeSession.PaymentIntent.Status == "paid": + await tipPaymentNotifier.NotifyPaymentSucceedAsync( + stripeSession.Id, + stripeSession.Invoice.HostedInvoiceUrl, + ct); + break; + // Check if this is a subscription + case "subscription" when stripeSession.SubscriptionId != null: + await membershipNotifier.NotifyPaymentSucceedAsync( + stripeSession.SubscriptionId, + stripeSession.Invoice.HostedInvoiceUrl, + stripeSession.Invoice.Total, + stripeSession.Invoice.Currency, + cancellationToken: ct); + break; + } + + break; + case "invoice.payment_succeeded": + var invoice = (stripeEvent.Data.Object as Invoice); + Debug.Assert(invoice != null); + Debug.Assert(invoice.Subscription != null); + await membershipNotifier.NotifyPaymentSucceedAsync( + invoice.SubscriptionId, + invoice.HostedInvoiceUrl, + invoice.Total, + invoice.Currency, + cancellationToken: ct); + break; + + case "customer.subscription.updated": + Debug.Assert(stripeSubscription != null); + await membershipNotifier.NotifySubscriptionUpdatedAsync( + stripeSubscription.Id, + stripeSubscription.CancelAt ?? stripeSubscription.CanceledAt, + ct); + break; + case "customer.subscription.deleted": + Debug.Assert(stripeSubscription != null); + await membershipNotifier.NotifySubscriptionDeletedAsync( + stripeSubscription.Id, + ct); + break; + } + + await SendOkAsync(ct); + } +} diff --git a/backend/src/Web/Features/Memberships/Handlers/SubscribeToCreator.cs b/backend/Modules/Memberships/Handlers/SubscribeToCreator.cs similarity index 55% rename from backend/src/Web/Features/Memberships/Handlers/SubscribeToCreator.cs rename to backend/Modules/Memberships/Handlers/SubscribeToCreator.cs index 1b9cb82..c027a75 100644 --- a/backend/src/Web/Features/Memberships/Handlers/SubscribeToCreator.cs +++ b/backend/Modules/Memberships/Handlers/SubscribeToCreator.cs @@ -1,14 +1,15 @@ -using Hutopy.Web.Common.Security; -using Hutopy.Web.Features.Memberships.Data; -using Hutopy.Web.Features.Memberships.Infrastructure; +using Hutopy.Infrastructure.Security; +using Hutopy.Modules.Creators.Contracts; +using Hutopy.Modules.Memberships.Contracts; +using Hutopy.Modules.Memberships.Data; -namespace Hutopy.Web.Features.Memberships.Handlers; +namespace Hutopy.Modules.Memberships.Handlers; [PublicAPI] public class SubscribeRequest { public Guid CreatorId { get; set; } - public Guid TierId { get; set; } + public Guid MembershipTierId { get; set; } public required string CheckoutSuccessUrl { get; init; } public required string CheckoutCancelledUrl { get; init; } } @@ -22,19 +23,20 @@ public class SubscribeValidator : Validator { public SubscribeValidator() { - RuleFor(x => x.TierId).NotEmpty(); + RuleFor(x => x.MembershipTierId).NotEmpty(); } } [PublicAPI] public class SubscribeHandler( - MembershipDbContext dbContext, - StripeService stripeService) + MembershipsDbContext dbContext, + ICreatorLookup creatorLookup, + IMembershipPaymentProcessor membershipPaymentProcessor) : Endpoint { public override void Configure() { - Post("/api/membership/subscribe"); + Post("/api/memberships/subscribe"); Options(o => o.WithTags("Memberships")); } @@ -43,23 +45,32 @@ public class SubscribeHandler( CancellationToken ct) { var tier = await dbContext - .Tiers - .Include(tier => tier.Creator) // Include the related table - .Where(tier => tier.Id == req.TierId) + .MembershipTiers + .Where(tier => tier.Id == req.MembershipTierId) .FirstOrDefaultAsync(ct); - if (tier == null) { await SendNotFoundAsync(ct); return; } + var creator = await creatorLookup.GetCreatorAsync(tier.CreatorId, ct); + if (creator == null) + { + await SendNotFoundAsync(ct); + return; + } + + if (!creator.AcceptCharges) + { + await SendErrorsAsync(StatusCodes.Status400BadRequest, ct); + return; + } + // Process Stripe subscription - var checkoutSession = await stripeService.CreateSubscriptionCheckoutSession( + var checkoutSession = await membershipPaymentProcessor.CreateCheckoutSessionAsync( User.GetUserId(), - tier.Creator.Id, - tier.Creator.Name, - tier.Creator.StripeAccountId, + creator, tier.Id, tier.StripePriceId, req.CheckoutSuccessUrl, diff --git a/backend/Modules/Memberships/Migrations/20250609212641_Initial.Designer.cs b/backend/Modules/Memberships/Migrations/20250609212641_Initial.Designer.cs new file mode 100644 index 0000000..5f1dd0e --- /dev/null +++ b/backend/Modules/Memberships/Migrations/20250609212641_Initial.Designer.cs @@ -0,0 +1,190 @@ +// +using System; +using Hutopy.Modules.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.Modules.Memberships.Migrations +{ + [DbContext(typeof(MembershipsDbContext))] + [Migration("20250609212641_Initial")] + partial class Initial + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasDefaultSchema("Memberships") + .HasAnnotation("ProductVersion", "9.0.3") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Hutopy.Modules.Memberships.Data.Membership", 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("MembershipTierId") + .HasColumnType("uuid"); + + b.Property("StartDate") + .HasColumnType("timestamp with time zone"); + + b.Property("State") + .HasColumnType("integer"); + + b.Property("StripeSubscriptionId") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("TierId") + .HasColumnType("uuid"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("MembershipTierId"); + + b.ToTable("Memberships", "Memberships"); + }); + + modelBuilder.Entity("Hutopy.Modules.Memberships.Data.MembershipTier", 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("CreatorId") + .HasColumnType("uuid"); + + b.Property("CurrencyCode") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedBy") + .HasColumnType("uuid"); + + 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.ToTable("MembershipTiers", "Memberships"); + }); + + modelBuilder.Entity("Hutopy.Modules.Memberships.Data.Payment", 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() + .HasMaxLength(8) + .HasColumnType("character varying(8)"); + + b.Property("InvoiceUrl") + .IsRequired() + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.Property("MembershipId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("MembershipId"); + + b.ToTable("Payments", "Memberships"); + }); + + modelBuilder.Entity("Hutopy.Modules.Memberships.Data.Membership", b => + { + b.HasOne("Hutopy.Modules.Memberships.Data.MembershipTier", "MembershipTier") + .WithMany("Subscriptions") + .HasForeignKey("MembershipTierId"); + + b.Navigation("MembershipTier"); + }); + + modelBuilder.Entity("Hutopy.Modules.Memberships.Data.Payment", b => + { + b.HasOne("Hutopy.Modules.Memberships.Data.Membership", null) + .WithMany("Payments") + .HasForeignKey("MembershipId"); + }); + + modelBuilder.Entity("Hutopy.Modules.Memberships.Data.Membership", b => + { + b.Navigation("Payments"); + }); + + modelBuilder.Entity("Hutopy.Modules.Memberships.Data.MembershipTier", b => + { + b.Navigation("Subscriptions"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/backend/Modules/Memberships/Migrations/20250609212641_Initial.cs b/backend/Modules/Memberships/Migrations/20250609212641_Initial.cs new file mode 100644 index 0000000..7c5597a --- /dev/null +++ b/backend/Modules/Memberships/Migrations/20250609212641_Initial.cs @@ -0,0 +1,119 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Hutopy.Modules.Memberships.Migrations +{ + /// + public partial class Initial : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.EnsureSchema( + name: "Memberships"); + + migrationBuilder.CreateTable( + name: "MembershipTiers", + schema: "Memberships", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + CreatorId = table.Column(type: "uuid", nullable: false), + Name = table.Column(type: "character varying(128)", maxLength: 128, nullable: false), + Description = table.Column(type: "character varying(4096)", maxLength: 4096, nullable: false), + Price = table.Column(type: "numeric", nullable: false), + CurrencyCode = table.Column(type: "character varying(128)", maxLength: 128, nullable: false), + StripeProductId = table.Column(type: "character varying(128)", maxLength: 128, nullable: false), + StripePriceId = table.Column(type: "character varying(128)", maxLength: 128, nullable: false), + CreatedBy = table.Column(type: "uuid", nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "CURRENT_TIMESTAMP"), + DeletedBy = table.Column(type: "uuid", nullable: true), + DeletedAt = table.Column(type: "timestamp with time zone", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_MembershipTiers", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Memberships", + schema: "Memberships", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "CURRENT_TIMESTAMP"), + UserId = table.Column(type: "uuid", nullable: false), + CreatorId = table.Column(type: "uuid", nullable: false), + TierId = table.Column(type: "uuid", nullable: false), + MembershipTierId = table.Column(type: "uuid", nullable: true), + State = table.Column(type: "integer", nullable: false), + StartDate = table.Column(type: "timestamp with time zone", nullable: true), + EndDate = table.Column(type: "timestamp with time zone", nullable: true), + StripeSubscriptionId = table.Column(type: "character varying(256)", maxLength: 256, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Memberships", x => x.Id); + table.ForeignKey( + name: "FK_Memberships_MembershipTiers_MembershipTierId", + column: x => x.MembershipTierId, + principalSchema: "Memberships", + principalTable: "MembershipTiers", + principalColumn: "Id"); + }); + + migrationBuilder.CreateTable( + name: "Payments", + schema: "Memberships", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "CURRENT_TIMESTAMP"), + Amount = table.Column(type: "numeric", nullable: false), + Currency = table.Column(type: "character varying(8)", maxLength: 8, nullable: false), + InvoiceUrl = table.Column(type: "character varying(2048)", maxLength: 2048, nullable: false), + MembershipId = table.Column(type: "uuid", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Payments", x => x.Id); + table.ForeignKey( + name: "FK_Payments_Memberships_MembershipId", + column: x => x.MembershipId, + principalSchema: "Memberships", + principalTable: "Memberships", + principalColumn: "Id"); + }); + + migrationBuilder.CreateIndex( + name: "IX_Memberships_MembershipTierId", + schema: "Memberships", + table: "Memberships", + column: "MembershipTierId"); + + migrationBuilder.CreateIndex( + name: "IX_Payments_MembershipId", + schema: "Memberships", + table: "Payments", + column: "MembershipId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Payments", + schema: "Memberships"); + + migrationBuilder.DropTable( + name: "Memberships", + schema: "Memberships"); + + migrationBuilder.DropTable( + name: "MembershipTiers", + schema: "Memberships"); + } + } +} diff --git a/backend/Modules/Memberships/Migrations/MembershipsDbContextModelSnapshot.cs b/backend/Modules/Memberships/Migrations/MembershipsDbContextModelSnapshot.cs new file mode 100644 index 0000000..5cffd45 --- /dev/null +++ b/backend/Modules/Memberships/Migrations/MembershipsDbContextModelSnapshot.cs @@ -0,0 +1,187 @@ +// +using System; +using Hutopy.Modules.Memberships.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Hutopy.Modules.Memberships.Migrations +{ + [DbContext(typeof(MembershipsDbContext))] + partial class MembershipsDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasDefaultSchema("Memberships") + .HasAnnotation("ProductVersion", "9.0.3") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Hutopy.Modules.Memberships.Data.Membership", 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("MembershipTierId") + .HasColumnType("uuid"); + + b.Property("StartDate") + .HasColumnType("timestamp with time zone"); + + b.Property("State") + .HasColumnType("integer"); + + b.Property("StripeSubscriptionId") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("TierId") + .HasColumnType("uuid"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("MembershipTierId"); + + b.ToTable("Memberships", "Memberships"); + }); + + modelBuilder.Entity("Hutopy.Modules.Memberships.Data.MembershipTier", 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("CreatorId") + .HasColumnType("uuid"); + + b.Property("CurrencyCode") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedBy") + .HasColumnType("uuid"); + + 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.ToTable("MembershipTiers", "Memberships"); + }); + + modelBuilder.Entity("Hutopy.Modules.Memberships.Data.Payment", 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() + .HasMaxLength(8) + .HasColumnType("character varying(8)"); + + b.Property("InvoiceUrl") + .IsRequired() + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.Property("MembershipId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("MembershipId"); + + b.ToTable("Payments", "Memberships"); + }); + + modelBuilder.Entity("Hutopy.Modules.Memberships.Data.Membership", b => + { + b.HasOne("Hutopy.Modules.Memberships.Data.MembershipTier", "MembershipTier") + .WithMany("Subscriptions") + .HasForeignKey("MembershipTierId"); + + b.Navigation("MembershipTier"); + }); + + modelBuilder.Entity("Hutopy.Modules.Memberships.Data.Payment", b => + { + b.HasOne("Hutopy.Modules.Memberships.Data.Membership", null) + .WithMany("Payments") + .HasForeignKey("MembershipId"); + }); + + modelBuilder.Entity("Hutopy.Modules.Memberships.Data.Membership", b => + { + b.Navigation("Payments"); + }); + + modelBuilder.Entity("Hutopy.Modules.Memberships.Data.MembershipTier", b => + { + b.Navigation("Subscriptions"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/backend/Modules/Memberships/Services/MembershipNotifier.cs b/backend/Modules/Memberships/Services/MembershipNotifier.cs new file mode 100644 index 0000000..6914554 --- /dev/null +++ b/backend/Modules/Memberships/Services/MembershipNotifier.cs @@ -0,0 +1,109 @@ +using Hutopy.Modules.Memberships.Contracts; +using Hutopy.Modules.Memberships.Data; + +namespace Hutopy.Modules.Memberships.Services; + +public class MembershipNotifier( + MembershipsDbContext dbContext) + : IMembershipNotifier +{ + public async Task NotifyCheckoutSessionCompleted( + string stripeSessionId, + string stripeSubscriptionId, + string userId, + string creatorId, + string tierId, + CancellationToken cancellationToken = default) + { + var membership = new Membership + { + Id = Guid.CreateVersion7(), + CreatedAt = DateTimeOffset.UtcNow, + UserId = Guid.Parse(userId), + CreatorId = Guid.Parse(creatorId), + TierId = Guid.Parse(tierId), + StripeSubscriptionId = stripeSubscriptionId, + State = MembershipState.Pending, + StartDate = null, + EndDate = null + }; + + dbContext.Memberships.Add(membership); + + await dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task NotifyPaymentSucceedAsync( + string stripeSubscriptionId, + string hostedInvoiceUrl, + decimal amount, + string currency, + CancellationToken cancellationToken = default) + { + var membership = await dbContext + .Memberships + .SingleOrDefaultAsync( + m => m.StripeSubscriptionId == stripeSubscriptionId, + cancellationToken: cancellationToken); + if (membership is null) + { + return; + } + + var payment = new Payment + { + Id = Guid.CreateVersion7(), + CreatedAt = DateTimeOffset.UtcNow, + Amount = amount, + Currency = currency, + InvoiceUrl = hostedInvoiceUrl + }; + + membership.State = MembershipState.Active; + membership.StartDate = DateTimeOffset.UtcNow; + membership.Payments.Add(payment); + + dbContext.Payments.Add(payment); + + await dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task NotifySubscriptionUpdatedAsync( + string subscriptionId, + DateTimeOffset? endDate, + CancellationToken cancellationToken = default) + { + var membership = await dbContext + .Memberships + .SingleOrDefaultAsync( + s => s.StripeSubscriptionId == subscriptionId, + cancellationToken: cancellationToken); + if (membership == null) + { + return; + } + + membership.EndDate = endDate; + + await dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task NotifySubscriptionDeletedAsync( + string subscriptionId, + CancellationToken cancellationToken) + { + var membership = await dbContext + .Memberships + .SingleOrDefaultAsync( + s => s.StripeSubscriptionId == subscriptionId, + cancellationToken: cancellationToken); + if (membership == null) + { + return; + } + + membership.State = MembershipState.Inactive; + + await dbContext.SaveChangesAsync(cancellationToken); + } +} diff --git a/backend/Modules/Messaging/Data/Message.cs b/backend/Modules/Messaging/Data/Message.cs new file mode 100644 index 0000000..508b6ca --- /dev/null +++ b/backend/Modules/Messaging/Data/Message.cs @@ -0,0 +1,11 @@ +using System.ComponentModel.DataAnnotations; +using Hutopy.Common.Domain; + +namespace Hutopy.Modules.Messaging.Data; + +public class Message : Entity +{ + public Guid SubjectId { get; set; } + public Guid? ParentId { get; set; } + [MaxLength(2048)] public required string Value { get; set; } +} diff --git a/backend/src/Web/Features/Messages/Data/MessagingDbContext.cs b/backend/Modules/Messaging/Data/MessagingDbContext.cs similarity index 70% rename from backend/src/Web/Features/Messages/Data/MessagingDbContext.cs rename to backend/Modules/Messaging/Data/MessagingDbContext.cs index 5f19b86..6b576e9 100644 --- a/backend/src/Web/Features/Messages/Data/MessagingDbContext.cs +++ b/backend/Modules/Messaging/Data/MessagingDbContext.cs @@ -1,13 +1,15 @@ -using Hutopy.Web.Features.Messages.Handlers.Models; +using Hutopy.Modules.Identity.Contracts; +using Hutopy.Modules.Messaging.Models; -namespace Hutopy.Web.Features.Messages.Data; +namespace Hutopy.Modules.Messaging.Data; public class MessagingDbContext( + IUserLookup userLookup, DbContextOptions options) : DbContext(options) { public const string SchemaName = "Messaging"; - + protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.HasDefaultSchema(SchemaName); @@ -21,7 +23,7 @@ public class MessagingDbContext( public DbSet Messages { get; set; } - public async Task> GetMessagesAsync( + public async Task> GetMessagesAsync( Guid subjectId, Guid? parentId, Guid? lastId, @@ -51,12 +53,27 @@ public class MessagingDbContext( .OrderByDescending(c => c.CreatedAt) .ThenByDescending(c => c.Id) .Take(pageSize) - .Select(message => message.ToDto()) .ToListAsync(cancellationToken: ct); - return messages; + + var result = await Task.WhenAll( + messages.Select(async message => + { + var writer = await userLookup.GetUserAsync(message.CreatedBy, ct); + return new MessageDto( + message.Id, + message.SubjectId, + message.CreatedBy, + writer?.Fullname ?? "Unknown User", + writer?.PortraitUrl, + message.CreatedAt, + message.ParentId, + message.Value); + })); + + return result; } - + public async Task GetMessageCountAsync( Guid subjectId, Guid? parentId, diff --git a/backend/Modules/Messaging/DependencyInjection.cs b/backend/Modules/Messaging/DependencyInjection.cs new file mode 100644 index 0000000..1ceb2b4 --- /dev/null +++ b/backend/Modules/Messaging/DependencyInjection.cs @@ -0,0 +1,27 @@ +using Hutopy.Modules.Messaging.Data; + +namespace Hutopy.Modules.Messaging; + +public static class DependencyInjection +{ + public static WebApplicationBuilder AddMessagingModule( + this WebApplicationBuilder builder, + Action? configureAction = null) + { + builder.Services.AddDbContext(configureAction); + + return builder; + } + + public static async Task UseMessagingModuleAsync( + this IApplicationBuilder app, + CancellationToken cancellationToken = default) + { + var scopeFactory = app.ApplicationServices.GetRequiredService(); + using var scope = scopeFactory.CreateScope(); + await using var context = scope.ServiceProvider.GetRequiredService(); + await context.Database.MigrateAsync(cancellationToken: cancellationToken); + + return app; + } +} diff --git a/backend/src/Web/Features/Messages/Handlers/AddMessage.cs b/backend/Modules/Messaging/Handlers/AddMessage.cs similarity index 78% rename from backend/src/Web/Features/Messages/Handlers/AddMessage.cs rename to backend/Modules/Messaging/Handlers/AddMessage.cs index e2f8f5b..99a0383 100644 --- a/backend/src/Web/Features/Messages/Handlers/AddMessage.cs +++ b/backend/Modules/Messaging/Handlers/AddMessage.cs @@ -1,9 +1,9 @@ -using Hutopy.Web.Common; -using Hutopy.Web.Common.Security; -using Hutopy.Web.Features.Messages.Data; +using Hutopy.Infrastructure.Security; +using Hutopy.Modules.Messaging.Data; -namespace Hutopy.Web.Features.Messages.Handlers; +namespace Hutopy.Modules.Messaging.Handlers; +[PublicAPI] public sealed class AddMessageRequest { public Guid? Id { get; set; } @@ -42,11 +42,9 @@ public class AddMessage( { var message = new Message { - Id = req.Id ?? GuidHelper.GenerateUuidV7(), + Id = req.Id ?? Guid.CreateVersion7(), SubjectId = req.SubjectId, CreatedBy = User.GetUserId(), - CreatedByName = User.GetAlias() ?? $"{User.GetFirstName()} {User.GetLastName()}", - CreatedByPortraitUrl = User.GetPortraitUrl(), Value = req.Message }; diff --git a/backend/src/Web/Features/Messages/Handlers/AddReply.cs b/backend/Modules/Messaging/Handlers/AddReply.cs similarity index 86% rename from backend/src/Web/Features/Messages/Handlers/AddReply.cs rename to backend/Modules/Messaging/Handlers/AddReply.cs index 04f086a..a547b40 100644 --- a/backend/src/Web/Features/Messages/Handlers/AddReply.cs +++ b/backend/Modules/Messaging/Handlers/AddReply.cs @@ -1,9 +1,9 @@ -using Hutopy.Web.Common; -using Hutopy.Web.Common.Security; -using Hutopy.Web.Features.Messages.Data; +using Hutopy.Infrastructure.Security; +using Hutopy.Modules.Messaging.Data; -namespace Hutopy.Web.Features.Messages.Handlers; +namespace Hutopy.Modules.Messaging.Handlers; +[PublicAPI] public sealed class AddReplyRequest { public Guid? Id { get; set; } @@ -47,11 +47,10 @@ internal sealed class AddReply( { var message = new Message { - Id = GuidHelper.GenerateUuidV7(), + Id = Guid.CreateVersion7(), SubjectId = req.SubjectId, ParentId = req.ParentId, CreatedBy = User.GetUserId(), - CreatedByName = User.GetName(), Value = req.Message }; diff --git a/backend/src/Web/Features/Messages/Handlers/ChangeMessage.cs b/backend/Modules/Messaging/Handlers/ChangeMessage.cs similarity index 91% rename from backend/src/Web/Features/Messages/Handlers/ChangeMessage.cs rename to backend/Modules/Messaging/Handlers/ChangeMessage.cs index d151416..f1a8d7e 100644 --- a/backend/src/Web/Features/Messages/Handlers/ChangeMessage.cs +++ b/backend/Modules/Messaging/Handlers/ChangeMessage.cs @@ -1,8 +1,7 @@ -using Hutopy.Web.Common; -using Hutopy.Web.Common.Security; -using Hutopy.Web.Features.Messages.Data; +using Hutopy.Infrastructure.Security; +using Hutopy.Modules.Messaging.Data; -namespace Hutopy.Web.Features.Messages.Handlers; +namespace Hutopy.Modules.Messaging.Handlers; public sealed class ChangeMessageRequest { diff --git a/backend/src/Web/Features/Messages/Handlers/DeleteMessage.cs b/backend/Modules/Messaging/Handlers/DeleteMessage.cs similarity index 89% rename from backend/src/Web/Features/Messages/Handlers/DeleteMessage.cs rename to backend/Modules/Messaging/Handlers/DeleteMessage.cs index a95f8e5..5fe55e0 100644 --- a/backend/src/Web/Features/Messages/Handlers/DeleteMessage.cs +++ b/backend/Modules/Messaging/Handlers/DeleteMessage.cs @@ -1,8 +1,7 @@ -using Hutopy.Web.Common; -using Hutopy.Web.Common.Security; -using Hutopy.Web.Features.Messages.Data; +using Hutopy.Infrastructure.Security; +using Hutopy.Modules.Messaging.Data; -namespace Hutopy.Web.Features.Messages.Handlers; +namespace Hutopy.Modules.Messaging.Handlers; public record DeleteMessageRequest(Guid MessageId); diff --git a/backend/src/Web/Features/Messages/Handlers/GetMessageCount.cs b/backend/Modules/Messaging/Handlers/GetMessageCount.cs similarity index 86% rename from backend/src/Web/Features/Messages/Handlers/GetMessageCount.cs rename to backend/Modules/Messaging/Handlers/GetMessageCount.cs index 0aea77a..b9d1b2e 100644 --- a/backend/src/Web/Features/Messages/Handlers/GetMessageCount.cs +++ b/backend/Modules/Messaging/Handlers/GetMessageCount.cs @@ -1,6 +1,6 @@ -using Hutopy.Web.Features.Messages.Data; +using Hutopy.Modules.Messaging.Data; -namespace Hutopy.Web.Features.Messages.Handlers; +namespace Hutopy.Modules.Messaging.Handlers; public sealed class GetMessageCountRequest { @@ -19,7 +19,7 @@ public class GetMessageCount( { public override void Configure() { - Get("/api/message-count/{SubjectId:guid}"); + Get("/api/messages/{SubjectId:guid}/count"); Options(o => o.WithTags("Messages")); AllowAnonymous(); } diff --git a/backend/src/Web/Features/Messages/Handlers/GetMessages.cs b/backend/Modules/Messaging/Handlers/GetMessages.cs similarity index 67% rename from backend/src/Web/Features/Messages/Handlers/GetMessages.cs rename to backend/Modules/Messaging/Handlers/GetMessages.cs index 1fb7236..b9d6065 100644 --- a/backend/src/Web/Features/Messages/Handlers/GetMessages.cs +++ b/backend/Modules/Messaging/Handlers/GetMessages.cs @@ -1,8 +1,9 @@ -using Hutopy.Web.Features.Messages.Data; -using Hutopy.Web.Features.Messages.Handlers.Models; +using Hutopy.Modules.Messaging.Data; +using Hutopy.Modules.Messaging.Models; -namespace Hutopy.Web.Features.Messages.Handlers; +namespace Hutopy.Modules.Messaging.Handlers; +[PublicAPI] public sealed class GetMessagesRequest { public Guid SubjectId { get; set; } @@ -10,10 +11,9 @@ public sealed class GetMessagesRequest [BindFrom("last_id")] public Guid? LastId { get; set; } } -public record struct GetMessagesResponse -{ - public required List Messages { get; init; } -} +[PublicAPI] +public record struct GetMessagesResponse( + IEnumerable Messages); public class GetMessages( MessagingDbContext context) @@ -37,11 +37,6 @@ public class GetMessages( req.PageSize, ct); - await SendAsync( - new() - { - Messages = messages - }, - cancellation: ct); + await SendOkAsync(new GetMessagesResponse(messages), ct); } } diff --git a/backend/Modules/Messaging/Handlers/GetMessagesByUser.cs b/backend/Modules/Messaging/Handlers/GetMessagesByUser.cs new file mode 100644 index 0000000..dfb4764 --- /dev/null +++ b/backend/Modules/Messaging/Handlers/GetMessagesByUser.cs @@ -0,0 +1,59 @@ +using Hutopy.Modules.Identity.Contracts; +using Hutopy.Modules.Messaging.Data; +using Hutopy.Modules.Messaging.Models; + +namespace Hutopy.Modules.Messaging.Handlers; + +[PublicAPI] +public class GetMessagesByUserRequest +{ + public Guid UserId { get; set; } +} + +[PublicAPI] +public record struct GetMessagesByUserResponse( + IEnumerable Messages); + +public class GetMessagesByUser( + IUserLookup userLookup, + MessagingDbContext context) + : Endpoint +{ + public override void Configure() + { + Get("/api/messages/user/{UserId:guid}"); + Options(o => o.WithTags("Messages")); + AllowAnonymous(); + } + + public override async Task HandleAsync( + GetMessagesByUserRequest req, + CancellationToken ct) + { + var messages = await context + .Messages + .Where(c => c.CreatedBy == req.UserId) + .Where(c => c.ParentId == null) + .ToListAsync(cancellationToken: ct); + + var result = await Task.WhenAll( + messages.Select(async message => + { + var user = await userLookup.GetUserAsync(message.CreatedBy, ct); + + return new MessageDto + { + Id = message.Id, + ParentId = message.ParentId, + CreatedAt = message.CreatedAt, + CreatedBy = message.CreatedBy, + CreatedByName = user?.Fullname ?? "Unknown User", + CreatedByPortraitUrl = user?.PortraitUrl ?? "", + SubjectId = message.SubjectId, + Value = message.Value + }; + })); + + await SendOkAsync(new GetMessagesByUserResponse(result), ct); + } +} diff --git a/backend/src/Web/Features/Messages/Handlers/GetReplies.cs b/backend/Modules/Messaging/Handlers/GetReplies.cs similarity index 68% rename from backend/src/Web/Features/Messages/Handlers/GetReplies.cs rename to backend/Modules/Messaging/Handlers/GetReplies.cs index 40b5f5f..a7d7fc2 100644 --- a/backend/src/Web/Features/Messages/Handlers/GetReplies.cs +++ b/backend/Modules/Messaging/Handlers/GetReplies.cs @@ -1,8 +1,9 @@ -using Hutopy.Web.Features.Messages.Data; -using Hutopy.Web.Features.Messages.Handlers.Models; +using Hutopy.Modules.Messaging.Data; +using Hutopy.Modules.Messaging.Models; -namespace Hutopy.Web.Features.Messages.Handlers; +namespace Hutopy.Modules.Messaging.Handlers; +[PublicAPI] public class GetRepliesRequest { public Guid SubjectId { get; set; } @@ -11,10 +12,9 @@ public class GetRepliesRequest [BindFrom("last_id")] public Guid? LastId { get; set; } } -public record struct GetRepliesResponse -{ - public required List Messages { get; init; } -} +[PublicAPI] +public record struct GetRepliesResponse( + IEnumerable Messages); public class GetReplies( MessagingDbContext context) @@ -38,11 +38,6 @@ public class GetReplies( req.PageSize, ct); - await SendAsync( - new() - { - Messages = replies, - }, - cancellation: ct); + await SendOkAsync(new GetRepliesResponse(replies), ct); } } diff --git a/backend/src/Web/Features/Messages/Data/Migrations/20241217225954_ChangeStripeId.Designer.cs b/backend/Modules/Messaging/Migrations/20250609171331_Initial.Designer.cs similarity index 72% rename from backend/src/Web/Features/Messages/Data/Migrations/20241217225954_ChangeStripeId.Designer.cs rename to backend/Modules/Messaging/Migrations/20250609171331_Initial.Designer.cs index ed928e5..392b0fe 100644 --- a/backend/src/Web/Features/Messages/Data/Migrations/20241217225954_ChangeStripeId.Designer.cs +++ b/backend/Modules/Messaging/Migrations/20250609171331_Initial.Designer.cs @@ -1,6 +1,6 @@ // using System; -using Hutopy.Web.Features.Messages.Data; +using Hutopy.Modules.Messaging.Data; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations; @@ -9,11 +9,11 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; #nullable disable -namespace Hutopy.Web.Features.Messages.Migrations +namespace Hutopy.Modules.Messaging.Migrations { [DbContext(typeof(MessagingDbContext))] - [Migration("20241217225954_ChangeStripeId")] - partial class ChangeStripeId + [Migration("20250609171331_Initial")] + partial class Initial { /// protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -21,12 +21,12 @@ namespace Hutopy.Web.Features.Messages.Migrations #pragma warning disable 612, 618 modelBuilder .HasDefaultSchema("Messaging") - .HasAnnotation("ProductVersion", "8.0.10") + .HasAnnotation("ProductVersion", "9.0.3") .HasAnnotation("Relational:MaxIdentifierLength", 63); NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - modelBuilder.Entity("Hutopy.Web.Features.Messages.Data.Message", b => + modelBuilder.Entity("Hutopy.Modules.Messaging.Data.Message", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -40,14 +40,11 @@ namespace Hutopy.Web.Features.Messages.Migrations b.Property("CreatedBy") .HasColumnType("uuid"); - b.Property("CreatedByName") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("character varying(255)"); + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); - b.Property("CreatedByPortraitUrl") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); + b.Property("DeletedBy") + .HasColumnType("uuid"); b.Property("ParentId") .HasColumnType("uuid"); diff --git a/backend/src/Web/Features/Messages/Data/Migrations/20240805012343_Initial.cs b/backend/Modules/Messaging/Migrations/20250609171331_Initial.cs similarity index 78% rename from backend/src/Web/Features/Messages/Data/Migrations/20240805012343_Initial.cs rename to backend/Modules/Messaging/Migrations/20250609171331_Initial.cs index ce0f4cd..bbf8023 100644 --- a/backend/src/Web/Features/Messages/Data/Migrations/20240805012343_Initial.cs +++ b/backend/Modules/Messaging/Migrations/20250609171331_Initial.cs @@ -3,7 +3,7 @@ using Microsoft.EntityFrameworkCore.Migrations; #nullable disable -namespace Hutopy.Web.Features.Messages.Migrations +namespace Hutopy.Modules.Messaging.Migrations { /// public partial class Initial : Migration @@ -21,12 +21,12 @@ namespace Hutopy.Web.Features.Messages.Migrations { Id = table.Column(type: "uuid", nullable: false), SubjectId = table.Column(type: "uuid", nullable: false), - CreatedBy = table.Column(type: "uuid", nullable: false), - CreatedByName = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), - CreatedByPortraitUrl = table.Column(type: "character varying(255)", maxLength: 255, nullable: true), - CreatedAt = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "CURRENT_TIMESTAMP"), ParentId = table.Column(type: "uuid", nullable: true), - Value = table.Column(type: "text", nullable: false) + Value = table.Column(type: "character varying(2048)", maxLength: 2048, nullable: false), + CreatedBy = table.Column(type: "uuid", nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "CURRENT_TIMESTAMP"), + DeletedBy = table.Column(type: "uuid", nullable: true), + DeletedAt = table.Column(type: "timestamp with time zone", nullable: true) }, constraints: table => { diff --git a/backend/src/Web/Features/Messages/Data/Migrations/MessagingDbContextModelSnapshot.cs b/backend/Modules/Messaging/Migrations/MessagingDbContextModelSnapshot.cs similarity index 77% rename from backend/src/Web/Features/Messages/Data/Migrations/MessagingDbContextModelSnapshot.cs rename to backend/Modules/Messaging/Migrations/MessagingDbContextModelSnapshot.cs index 02fc153..a93407b 100644 --- a/backend/src/Web/Features/Messages/Data/Migrations/MessagingDbContextModelSnapshot.cs +++ b/backend/Modules/Messaging/Migrations/MessagingDbContextModelSnapshot.cs @@ -1,6 +1,6 @@ // using System; -using Hutopy.Web.Features.Messages.Data; +using Hutopy.Modules.Messaging.Data; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; @@ -8,7 +8,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; #nullable disable -namespace Hutopy.Web.Features.Messages.Migrations +namespace Hutopy.Modules.Messaging.Migrations { [DbContext(typeof(MessagingDbContext))] partial class MessagingDbContextModelSnapshot : ModelSnapshot @@ -23,7 +23,7 @@ namespace Hutopy.Web.Features.Messages.Migrations NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - modelBuilder.Entity("Hutopy.Web.Features.Messages.Data.Message", b => + modelBuilder.Entity("Hutopy.Modules.Messaging.Data.Message", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -37,14 +37,11 @@ namespace Hutopy.Web.Features.Messages.Migrations b.Property("CreatedBy") .HasColumnType("uuid"); - b.Property("CreatedByName") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("character varying(255)"); + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); - b.Property("CreatedByPortraitUrl") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); + b.Property("DeletedBy") + .HasColumnType("uuid"); b.Property("ParentId") .HasColumnType("uuid"); diff --git a/backend/Modules/Messaging/Models/MessageDto.cs b/backend/Modules/Messaging/Models/MessageDto.cs new file mode 100644 index 0000000..c4edadf --- /dev/null +++ b/backend/Modules/Messaging/Models/MessageDto.cs @@ -0,0 +1,12 @@ +namespace Hutopy.Modules.Messaging.Models; + +public record struct MessageDto( + Guid Id, + Guid SubjectId, + Guid CreatedBy, + string CreatedByName, + string? CreatedByPortraitUrl, + DateTimeOffset CreatedAt, + Guid? ParentId, + string Value +); diff --git a/backend/Modules/Tipping/Contracts/ITipPaymentNotifier.cs b/backend/Modules/Tipping/Contracts/ITipPaymentNotifier.cs new file mode 100644 index 0000000..e38a08e --- /dev/null +++ b/backend/Modules/Tipping/Contracts/ITipPaymentNotifier.cs @@ -0,0 +1,6 @@ +namespace Hutopy.Modules.Tipping.Contracts; + +public interface ITipPaymentNotifier +{ + Task NotifyPaymentSucceedAsync(string stripeId, string invoiceUrl, CancellationToken ct); +} diff --git a/backend/Modules/Tipping/Contracts/ITipProcessor.cs b/backend/Modules/Tipping/Contracts/ITipProcessor.cs new file mode 100644 index 0000000..dd81ec4 --- /dev/null +++ b/backend/Modules/Tipping/Contracts/ITipProcessor.cs @@ -0,0 +1,16 @@ +using Hutopy.Modules.Creators.Contracts; + +namespace Hutopy.Modules.Tipping.Contracts; + +public interface ITipProcessor +{ + Task CreateCheckoutSessionAsync( + Guid tipId, + CreatorReference creator, + decimal amount, + string currency, + string message, + string successUrl, + string cancelUrl, + CancellationToken ct = default); +} diff --git a/backend/Modules/Tipping/Contracts/TipCheckoutSession.cs b/backend/Modules/Tipping/Contracts/TipCheckoutSession.cs new file mode 100644 index 0000000..23833fe --- /dev/null +++ b/backend/Modules/Tipping/Contracts/TipCheckoutSession.cs @@ -0,0 +1,5 @@ +namespace Hutopy.Modules.Tipping.Contracts; + +public record TipCheckoutSession( + string Id, + string Url); diff --git a/backend/Modules/Tipping/Data/Tip.cs b/backend/Modules/Tipping/Data/Tip.cs new file mode 100644 index 0000000..a9cd709 --- /dev/null +++ b/backend/Modules/Tipping/Data/Tip.cs @@ -0,0 +1,21 @@ +using System.ComponentModel.DataAnnotations; +using Hutopy.Common.Domain; + +namespace Hutopy.Modules.Tipping.Data; + +public class Tip : Entity +{ + public Guid CreatorId { get; set; } + public TipStatus Status { get; set; } + public decimal Amount { get; set; } + [MaxLength(8)] public required string Currency { get; set; } + [MaxLength(2048)] public required string Message { get; set; } + [MaxLength(256)] public required string StripeSessionId { get; set; } + [MaxLength(2048)] public string? StripeInvoiceUrl { get; set; } +} + +public enum TipStatus : short +{ + Pending = 0, + Paid = 1, +} diff --git a/backend/Modules/Tipping/Data/TippingDbContext.cs b/backend/Modules/Tipping/Data/TippingDbContext.cs new file mode 100644 index 0000000..997f8c1 --- /dev/null +++ b/backend/Modules/Tipping/Data/TippingDbContext.cs @@ -0,0 +1,22 @@ +namespace Hutopy.Modules.Tipping.Data; + +public sealed class TippingDbContext( + DbContextOptions options) + : DbContext(options) +{ + public const string SchemaName = "Tipping"; + + public DbSet Tips => Set(); + + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.HasDefaultSchema(SchemaName); + + modelBuilder + .Entity() + .Property(c => c.CreatedAt) + .ValueGeneratedOnAdd() + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + } +} diff --git a/backend/Modules/Tipping/DependencyInjection.cs b/backend/Modules/Tipping/DependencyInjection.cs new file mode 100644 index 0000000..674378b --- /dev/null +++ b/backend/Modules/Tipping/DependencyInjection.cs @@ -0,0 +1,31 @@ +using Hutopy.Modules.Messaging.Data; +using Hutopy.Modules.Tipping.Contracts; +using Hutopy.Modules.Tipping.Data; +using Hutopy.Modules.Tipping.Services; + +namespace Hutopy.Modules.Tipping; + +public static class DependencyInjection +{ + public static WebApplicationBuilder AddTippingModule( + this WebApplicationBuilder builder, + Action? configureAction = null) + { + builder.Services.AddDbContext(configureAction); + builder.Services.AddTransient(); + + return builder; + } + + public static async Task UseTippingModuleAsync( + this IApplicationBuilder app, + CancellationToken cancellationToken = default) + { + var scopeFactory = app.ApplicationServices.GetRequiredService(); + using var scope = scopeFactory.CreateScope(); + await using var context = scope.ServiceProvider.GetRequiredService(); + await context.Database.MigrateAsync(cancellationToken: cancellationToken); + + return app; + } +} diff --git a/backend/Modules/Tipping/Handlers/GetReceivedTips.cs b/backend/Modules/Tipping/Handlers/GetReceivedTips.cs new file mode 100644 index 0000000..89fceba --- /dev/null +++ b/backend/Modules/Tipping/Handlers/GetReceivedTips.cs @@ -0,0 +1,49 @@ +using Hutopy.Infrastructure.Security; +using Hutopy.Modules.Identity.Contracts; +using Hutopy.Modules.Tipping.Data; +using Hutopy.Modules.Tipping.Models; + +namespace Hutopy.Modules.Tipping.Handlers; + +[PublicAPI] +public record struct GetReceivedTipsResponse( + IEnumerable Tips); + +[PublicAPI] +public class GetReceivedTipsHandler( + IUserLookup userLookup, + TippingDbContext dbContext) + : EndpointWithoutRequest +{ + public override void Configure() + { + Get("/api/tips"); + Options(o => o.WithTags("Tips")); + } + + public override async Task HandleAsync( + CancellationToken ct) + { + var tips = await dbContext + .Tips + .Where(tip => tip.CreatorId == User.GetUserId()) + .ToListAsync(ct); + + var result = await Task.WhenAll( + tips.Select(async tip => + { + var tipper = await userLookup.GetUserAsync(tip.CreatorId, ct); + + return new TipReceivedModel( + tip.Id, + tip.CreatedAt, + tip.CreatedBy, + tipper?.Fullname ?? "Unknown User", + tip.Amount, + tip.Currency, + tip.Message); + })); + + await SendOkAsync(new GetReceivedTipsResponse(result), ct); + } +} diff --git a/backend/Modules/Tipping/Handlers/SendTip.cs b/backend/Modules/Tipping/Handlers/SendTip.cs new file mode 100644 index 0000000..7f18c72 --- /dev/null +++ b/backend/Modules/Tipping/Handlers/SendTip.cs @@ -0,0 +1,116 @@ +using Hutopy.Infrastructure.Security; +using Hutopy.Modules.Creators.Contracts; +using Hutopy.Modules.Tipping.Contracts; +using Hutopy.Modules.Tipping.Data; + +namespace Hutopy.Modules.Tipping.Handlers; + +[PublicAPI] +public record SendTipRequest( + Guid CreatorId, + decimal Amount, + string Currency, + string Message, + string CheckoutSuccessUrl, + string CheckoutCancelledUrl); + +[PublicAPI] +public record SendTipResponse( + string Id, + string Url); + +[PublicAPI] +public class SendTipRequestValidator : Validator +{ + public SendTipRequestValidator() + { + RuleFor(x => x.Amount) + .GreaterThan(0) + .WithMessage("Tip amount must be greater than 0"); + + RuleFor(x => x.CreatorId) + .NotEmpty() + .WithMessage("Creator ID is required"); + + RuleFor(x => x.CheckoutSuccessUrl) + .NotEmpty() + .WithMessage("CheckoutSuccessUrl is required"); + + RuleFor(x => x.CheckoutCancelledUrl) + .NotEmpty() + .WithMessage("CheckoutCancelledUrl is required"); + } +} + +[PublicAPI] +public class SendTipHandler( + TippingDbContext dbContext, + ITipProcessor tipProcessor, + ICreatorLookup creatorLookup) + : Endpoint +{ + private static readonly Guid AnonymousUserId = Guid.Parse("AAAAAAAA-0000-0000-0000-000000000000"); + + public override void Configure() + { + Post("/api/tips"); + Options(o => o.WithTags("Memberships")); + + AllowAnonymous(); + } + + public override async Task HandleAsync( + SendTipRequest req, + CancellationToken ct) + { + CreatorReference? creator = await creatorLookup.GetCreatorAsync(req.CreatorId, ct); + if (creator == null) + { + await SendNotFoundAsync(ct); + return; + } + + if (!creator.AcceptCharges) + { + await SendErrorsAsync(StatusCodes.Status400BadRequest, ct); + return; + } + + Guid tipId = Guid.CreateVersion7(); + + TipCheckoutSession checkout = await tipProcessor.CreateCheckoutSessionAsync( + tipId, + creator, + req.Amount, + req.Currency, + req.Message, + req.CheckoutSuccessUrl, + req.CheckoutCancelledUrl, + ct); + + Guid userId = User.Identity?.IsAuthenticated == true + ? User.GetUserId() + : AnonymousUserId; + + Tip tip = new() + { + Id = tipId, + CreatedAt = DateTimeOffset.UtcNow, + StripeSessionId = checkout.Id, + CreatedBy = userId, + CreatorId = req.CreatorId, + Status = TipStatus.Pending, + Amount = req.Amount, + Currency = req.Currency, + Message = req.Message + }; + + dbContext.Tips.Add(tip); + + await dbContext.SaveChangesAsync(ct); + + await SendAsync( + new SendTipResponse(checkout.Id, checkout.Url), + cancellation: ct); + } +} diff --git a/backend/src/Web/Features/Messages/Data/Migrations/20240805012343_Initial.Designer.cs b/backend/Modules/Tipping/Migrations/20250609171342_Initial.Designer.cs similarity index 55% rename from backend/src/Web/Features/Messages/Data/Migrations/20240805012343_Initial.Designer.cs rename to backend/Modules/Tipping/Migrations/20250609171342_Initial.Designer.cs index 31e6ba9..6b14f36 100644 --- a/backend/src/Web/Features/Messages/Data/Migrations/20240805012343_Initial.Designer.cs +++ b/backend/Modules/Tipping/Migrations/20250609171342_Initial.Designer.cs @@ -1,6 +1,6 @@ // using System; -using Hutopy.Web.Features.Messages.Data; +using Hutopy.Modules.Tipping.Data; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations; @@ -9,10 +9,10 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; #nullable disable -namespace Hutopy.Web.Features.Messages.Migrations +namespace Hutopy.Modules.Tipping.Migrations { - [DbContext(typeof(MessagingDbContext))] - [Migration("20240805012343_Initial")] + [DbContext(typeof(TippingDbContext))] + [Migration("20250609171342_Initial")] partial class Initial { /// @@ -20,18 +20,21 @@ namespace Hutopy.Web.Features.Messages.Migrations { #pragma warning disable 612, 618 modelBuilder - .HasDefaultSchema("Messaging") - .HasAnnotation("ProductVersion", "8.0.4") + .HasDefaultSchema("Tipping") + .HasAnnotation("ProductVersion", "9.0.3") .HasAnnotation("Relational:MaxIdentifierLength", 63); NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - modelBuilder.Entity("Hutopy.Web.Features.Messages.Data.Message", b => + modelBuilder.Entity("Hutopy.Modules.Tipping.Data.Tip", b => { b.Property("Id") .ValueGeneratedOnAdd() .HasColumnType("uuid"); + b.Property("Amount") + .HasColumnType("numeric"); + b.Property("CreatedAt") .ValueGeneratedOnAdd() .HasColumnType("timestamp with time zone") @@ -40,28 +43,36 @@ namespace Hutopy.Web.Features.Messages.Migrations b.Property("CreatedBy") .HasColumnType("uuid"); - b.Property("CreatedByName") + b.Property("CreatorId") + .HasColumnType("uuid"); + + b.Property("Currency") .IsRequired() - .HasMaxLength(255) - .HasColumnType("character varying(255)"); + .HasColumnType("text"); - b.Property("CreatedByPortraitUrl") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); - b.Property("ParentId") + b.Property("DeletedBy") .HasColumnType("uuid"); - b.Property("SubjectId") - .HasColumnType("uuid"); + b.Property("Message") + .IsRequired() + .HasColumnType("text"); - b.Property("Value") + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("StripeInvoiceUrl") + .HasColumnType("text"); + + b.Property("StripeSessionId") .IsRequired() .HasColumnType("text"); b.HasKey("Id"); - b.ToTable("Messages", "Messaging"); + b.ToTable("Tips", "Tipping"); }); #pragma warning restore 612, 618 } diff --git a/backend/Modules/Tipping/Migrations/20250609171342_Initial.cs b/backend/Modules/Tipping/Migrations/20250609171342_Initial.cs new file mode 100644 index 0000000..73acf9d --- /dev/null +++ b/backend/Modules/Tipping/Migrations/20250609171342_Initial.cs @@ -0,0 +1,49 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Hutopy.Modules.Tipping.Migrations +{ + /// + public partial class Initial : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.EnsureSchema( + name: "Tipping"); + + migrationBuilder.CreateTable( + name: "Tips", + schema: "Tipping", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + CreatorId = table.Column(type: "uuid", nullable: false), + Status = table.Column(type: "smallint", nullable: false), + Amount = table.Column(type: "numeric", nullable: false), + Currency = table.Column(type: "text", nullable: false), + Message = table.Column(type: "text", nullable: false), + StripeSessionId = table.Column(type: "text", nullable: false), + StripeInvoiceUrl = table.Column(type: "text", nullable: true), + CreatedBy = table.Column(type: "uuid", nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "CURRENT_TIMESTAMP"), + DeletedBy = table.Column(type: "uuid", nullable: true), + DeletedAt = table.Column(type: "timestamp with time zone", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Tips", x => x.Id); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Tips", + schema: "Tipping"); + } + } +} diff --git a/backend/Modules/Tipping/Migrations/TippingDbContextModelSnapshot.cs b/backend/Modules/Tipping/Migrations/TippingDbContextModelSnapshot.cs new file mode 100644 index 0000000..cd10df1 --- /dev/null +++ b/backend/Modules/Tipping/Migrations/TippingDbContextModelSnapshot.cs @@ -0,0 +1,77 @@ +// +using System; +using Hutopy.Modules.Tipping.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Hutopy.Modules.Tipping.Migrations +{ + [DbContext(typeof(TippingDbContext))] + partial class TippingDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasDefaultSchema("Tipping") + .HasAnnotation("ProductVersion", "9.0.3") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Hutopy.Modules.Tipping.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("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatorId") + .HasColumnType("uuid"); + + b.Property("Currency") + .IsRequired() + .HasColumnType("text"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedBy") + .HasColumnType("uuid"); + + b.Property("Message") + .IsRequired() + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("StripeInvoiceUrl") + .HasColumnType("text"); + + b.Property("StripeSessionId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Tips", "Tipping"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/backend/Modules/Tipping/Models/TipReceivedModel.cs b/backend/Modules/Tipping/Models/TipReceivedModel.cs new file mode 100644 index 0000000..6324075 --- /dev/null +++ b/backend/Modules/Tipping/Models/TipReceivedModel.cs @@ -0,0 +1,11 @@ +namespace Hutopy.Modules.Tipping.Models; + +[PublicAPI] +public record struct TipReceivedModel( + Guid Id, + DateTimeOffset CreatedAt, + Guid TipperId, + string TipperName, + decimal Amount, + string Currency, + string Message); diff --git a/backend/Modules/Tipping/Services/TipPaymentNotifier.cs b/backend/Modules/Tipping/Services/TipPaymentNotifier.cs new file mode 100644 index 0000000..2c0bdbf --- /dev/null +++ b/backend/Modules/Tipping/Services/TipPaymentNotifier.cs @@ -0,0 +1,31 @@ +using Hutopy.Modules.Tipping.Contracts; +using Hutopy.Modules.Tipping.Data; + +namespace Hutopy.Modules.Tipping.Services; + +public class TipPaymentNotifier( + TippingDbContext dbContext, + ILogger logger) + : ITipPaymentNotifier +{ + public async Task NotifyPaymentSucceedAsync( + string sessionId, + string invoiceUrl, + CancellationToken ct) + { + var tip = await dbContext.Tips.SingleOrDefaultAsync( + t => t.StripeSessionId == sessionId, + cancellationToken: ct); + + if (tip is not null) + { + tip.Status = TipStatus.Paid; + tip.StripeInvoiceUrl = invoiceUrl; + await dbContext.SaveChangesAsync(ct); + } + else + { + logger.LogError("Tip with session ID {SessionId} not found", sessionId); + } + } +} diff --git a/backend/src/Web/Program.cs b/backend/Program.cs similarity index 71% rename from backend/src/Web/Program.cs rename to backend/Program.cs index 91d42bb..6e8344e 100644 --- a/backend/src/Web/Program.cs +++ b/backend/Program.cs @@ -1,14 +1,18 @@ using Azure.Identity; -using Hutopy.Web; -using Hutopy.Web.Features.Contents; -using Hutopy.Web.Features.Contents.Data; -using Hutopy.Web.Features.Memberships; -using Hutopy.Web.Features.Memberships.Data; -using Hutopy.Web.Features.Messages; -using Hutopy.Web.Features.Messages.Data; -using Hutopy.Web.Features.Users; -using Hutopy.Web.Features.Users.Data; -using Hutopy.Web.Features.Users.Services; +using Hutopy; +using Hutopy.Infrastructure; +using Hutopy.Modules.Contents; +using Hutopy.Modules.Contents.Data; +using Hutopy.Modules.Creators; +using Hutopy.Modules.Creators.Data; +using Hutopy.Modules.Identity; +using Hutopy.Modules.Identity.Data; +using Hutopy.Modules.Memberships; +using Hutopy.Modules.Memberships.Data; +using Hutopy.Modules.Messaging; +using Hutopy.Modules.Messaging.Data; +using Hutopy.Modules.Tipping; +using Hutopy.Modules.Tipping.Data; using Microsoft.AspNetCore.HttpOverrides; using NSwag; using NSwag.Generation.AspNetCore.Processors; @@ -37,15 +41,13 @@ builder.Services.AddCors(options => }); }); -builder.Services.AddTransient(); - // Add services to the container. builder.Services.AddWebServices(); builder.Services.AddAuthorizationAndAuthentication(builder.Configuration); builder.Services.AddOpenApiDocument(( configure, - sp) => + _) => { configure.Title = "Hutopy API"; @@ -69,25 +71,31 @@ var postgresConnectionString = builder.Configuration.GetConnectionString("Postgr ?? throw new InvalidOperationException("Missing ConnectionStrings:PostgresConnection"); builder.Services.AddFastEndpoints(); +builder.AddInfrastructureModule(); builder.AddIdentityModule(options => options.UseNpgsql( postgresConnectionString, - o => o.MigrationsHistoryTable("__EFMigrationsHistory", ApplicationDbContext.SchemaName))); + o => o.MigrationsHistoryTable("__EFMigrationsHistory", IdentityDbContext.SchemaName))); +builder.AddCreatorModule(options => + options.UseNpgsql( + postgresConnectionString, + o => o.MigrationsHistoryTable("__EFMigrationsHistory", CreatorsDbContext.SchemaName))); builder.AddContentModule(options => options.UseNpgsql( postgresConnectionString, - o => o.MigrationsHistoryTable("__EFMigrationsHistory", ContentDbContext.SchemaName))); + o => o.MigrationsHistoryTable("__EFMigrationsHistory", ContentsDbContext.SchemaName))); +builder.AddMembershipModule( + options => options.UseNpgsql( + postgresConnectionString, + o => o.MigrationsHistoryTable("__EFMigrationsHistory", MembershipsDbContext.SchemaName))); +builder.AddTippingModule(options => + options.UseNpgsql( + postgresConnectionString, + o => o.MigrationsHistoryTable("__EFMigrationsHistory", TippingDbContext.SchemaName))); builder.AddMessagingModule(options => options.UseNpgsql( postgresConnectionString, o => o.MigrationsHistoryTable("__EFMigrationsHistory", MessagingDbContext.SchemaName))); -builder.AddMembershipModule( - options => options.UseNpgsql( - postgresConnectionString, - o => o.MigrationsHistoryTable("__EFMigrationsHistory", MembershipDbContext.SchemaName))); - -builder.Services.Configure(builder.Configuration.GetRequiredSection(JwtOptions.SectionName)); -builder.Services.Configure(builder.Configuration.GetRequiredSection(WebsiteOptions.SectionName)); var app = builder.Build(); @@ -101,10 +109,12 @@ app.UseAuthentication(); app.UseAuthorization(); // Initialize and seed the db. -await app.InitialiseIdentityDatabaseAsync(); -await app.InitialiseContentDbContextAsync(); -await app.InitialiseMessagingDbContextAsync(); -await app.InitialiseMembershipDbContextAsync(); +await app.UseIdentityModuleAsync(); +await app.UseCreatorModuleAsync(); +await app.UseContentModuleAsync(); +await app.UseMembershipModuleAsync(); +await app.UseTippingModuleAsync(); +await app.UseMessagingModuleAsync(); // Configure the HTTP request pipeline. if (!app.Environment.IsDevelopment()) @@ -125,10 +135,3 @@ if (app.Environment.IsDevelopment()) app.UseFastEndpoints(); app.Run(); - -namespace Hutopy.Web -{ - public abstract partial class Program - { - } -} diff --git a/backend/Properties/launchSettings.json b/backend/Properties/launchSettings.json new file mode 100644 index 0000000..36403c4 --- /dev/null +++ b/backend/Properties/launchSettings.json @@ -0,0 +1,12 @@ +{ + "profiles": { + "Hutopy.Web - DEV": { + "commandName": "Project", + "launchBrowser": false, + "applicationUrl": "https://0.0.0.0:5001;http://0.0.0.0:5000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} \ No newline at end of file diff --git a/backend/src/Web/appsettings.Development.json b/backend/appsettings.Development.json similarity index 100% rename from backend/src/Web/appsettings.Development.json rename to backend/appsettings.Development.json diff --git a/backend/src/Web/appsettings.Production.json b/backend/appsettings.Production.json similarity index 100% rename from backend/src/Web/appsettings.Production.json rename to backend/appsettings.Production.json diff --git a/backend/backend.sln b/backend/backend.sln new file mode 100644 index 0000000..6ecf54d --- /dev/null +++ b/backend/backend.sln @@ -0,0 +1,34 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hutopy", "Hutopy.csproj", "{D790B528-6968-4CCD-A25D-A108A82CBDAC}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {D790B528-6968-4CCD-A25D-A108A82CBDAC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D790B528-6968-4CCD-A25D-A108A82CBDAC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D790B528-6968-4CCD-A25D-A108A82CBDAC}.Debug|x64.ActiveCfg = Debug|Any CPU + {D790B528-6968-4CCD-A25D-A108A82CBDAC}.Debug|x64.Build.0 = Debug|Any CPU + {D790B528-6968-4CCD-A25D-A108A82CBDAC}.Debug|x86.ActiveCfg = Debug|Any CPU + {D790B528-6968-4CCD-A25D-A108A82CBDAC}.Debug|x86.Build.0 = Debug|Any CPU + {D790B528-6968-4CCD-A25D-A108A82CBDAC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D790B528-6968-4CCD-A25D-A108A82CBDAC}.Release|Any CPU.Build.0 = Release|Any CPU + {D790B528-6968-4CCD-A25D-A108A82CBDAC}.Release|x64.ActiveCfg = Release|Any CPU + {D790B528-6968-4CCD-A25D-A108A82CBDAC}.Release|x64.Build.0 = Release|Any CPU + {D790B528-6968-4CCD-A25D-A108A82CBDAC}.Release|x86.ActiveCfg = Release|Any CPU + {D790B528-6968-4CCD-A25D-A108A82CBDAC}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/backend/create-sql-scripts.sh b/backend/create-sql-scripts.sh deleted file mode 100644 index a08bbeb..0000000 --- a/backend/create-sql-scripts.sh +++ /dev/null @@ -1,37 +0,0 @@ -#!/bin/bash - -dotnet ef migrations script \ - --startup-project src/Web/Web.csproj \ - --project src/Web/Web.csproj \ - --context Hutopy.Web.Features.Users.Data.ApplicationDbContext \ - --configuration Debug \ - --output create-identity-db.sql \ - --idempotent \ - --no-build - -dotnet ef migrations script \ - --startup-project src/Web/Web.csproj \ - --project src/Web/Web.csproj \ - --context Hutopy.Web.Features.Messages.Data.MessagingDbContext \ - --configuration Debug \ - --output create-messaging-db.sql \ - --idempotent\ - --no-build - -dotnet ef migrations script \ - --startup-project src/Web/Web.csproj \ - --project src/Web/Web.csproj \ - --context Hutopy.Web.Features.Contents.Data.ContentDbContext \ - --configuration Debug \ - --output create-content-db.sql \ - --idempotent \ - --no-build - -dotnet ef migrations script \ - --startup-project src/Web/Web.csproj \ - --project src/Web/Web.csproj \ - --context Hutopy.Web.Features.Memberships.Data.MembershipDbContext \ - --configuration Debug \ - --output create-membership-db.sql \ - --idempotent \ - --no-build diff --git a/backend/global.json b/backend/global.json deleted file mode 100644 index 039c683..0000000 --- a/backend/global.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "sdk": { - "version": "9.0.201", - "rollForward": "latestMinor", - "allowPrerelease": false - } -} diff --git a/backend/scripts/add-migration.sh b/backend/scripts/add-migration.sh new file mode 100755 index 0000000..b1760f7 --- /dev/null +++ b/backend/scripts/add-migration.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +# Usage: ./add-migration.sh Messaging Initial + +MODULE_NAME=$1 +MIGRATION_NAME=$2 + +if [ -z "$MODULE_NAME" ] || [ -z "$MIGRATION_NAME" ]; then + echo "Usage: $0 " + exit 1 +fi + +dotnet ef migrations add \ + --context "Hutopy.Modules.${MODULE_NAME}.Data.${MODULE_NAME}DbContext" \ + --configuration Debug \ + --output-dir "Modules/${MODULE_NAME}/Migrations" \ + "$MIGRATION_NAME" diff --git a/backend/scripts/start-infrastructure.sh b/backend/scripts/start-infrastructure.sh new file mode 100755 index 0000000..31e8039 --- /dev/null +++ b/backend/scripts/start-infrastructure.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +# Start the container (if not already running) +docker start postgres 2>/dev/null || docker run \ + --cap-add SYS_PTRACE \ + -e POSTGRES_USER=sa \ + -e POSTGRES_PASSWORD='P@ssword123!' \ + -p 5432:5432 \ + --name postgres \ + -d postgres:latest + +# Wait until Postgres is ready +echo "Waiting for Postgres to be ready..." +until docker exec postgres pg_isready -U sa > /dev/null 2>&1; do + sleep 1 +done + +# Create 'hutopy' database if it doesn't exist +echo "Ensuring 'hutopy' database exists..." +docker exec -e PGPASSWORD='P@ssword123!' postgres \ + psql -U sa -d postgres -tAc "SELECT 1 FROM pg_database WHERE datname='hutopy'" | grep -q 1 || \ + docker exec -e PGPASSWORD='P@ssword123!' postgres \ + createdb -U sa hutopy + +echo "✅ Done." diff --git a/backend/scripts/update-databases.sh b/backend/scripts/update-databases.sh new file mode 100755 index 0000000..5fd2ad0 --- /dev/null +++ b/backend/scripts/update-databases.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +# Usage: +# ./update-db.sh Messaging => applies latest migration +# ./update-db.sh Messaging Initial => updates to a specific migration + +MODULE_NAME=$1 +TARGET_MIGRATION=$2 # Optional + +if [ -z "$MODULE_NAME" ]; then + echo "Usage: $0 [MigrationName]" + exit 1 +fi + +UPDATE_COMMAND=( + dotnet ef database update + --context "Hutopy.Modules.${MODULE_NAME}.Data.${MODULE_NAME}DbContext" + --configuration Debug +) + +if [ -n "$TARGET_MIGRATION" ]; then + UPDATE_COMMAND+=("$TARGET_MIGRATION") +fi + +"${UPDATE_COMMAND[@]}" diff --git a/backend/src/Web/.config/dotnet-tools.json b/backend/src/Web/.config/dotnet-tools.json deleted file mode 100644 index 4633692..0000000 --- a/backend/src/Web/.config/dotnet-tools.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "version": 1, - "isRoot": true, - "tools": { - "dotnet-ef": { - "version": "9.0.3", - "commands": [ - "dotnet-ef" - ] - } - } -} \ No newline at end of file diff --git a/backend/src/Web/Common/BlobStorage/CommonFileNames.cs b/backend/src/Web/Common/BlobStorage/CommonFileNames.cs deleted file mode 100644 index 5d93f5b..0000000 --- a/backend/src/Web/Common/BlobStorage/CommonFileNames.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Hutopy.Web.Common.BlobStorage; - -public static class CommonFileNames -{ - public static string ProfilePicture = "profilePicture"; - public static string BannerPicture = "bannerPicture"; -} diff --git a/backend/src/Web/Common/BlobStorage/SubDirectoryNames.cs b/backend/src/Web/Common/BlobStorage/SubDirectoryNames.cs deleted file mode 100644 index 72c777e..0000000 --- a/backend/src/Web/Common/BlobStorage/SubDirectoryNames.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Hutopy.Web.Common.BlobStorage; - -public static class SubDirectoryNames -{ - public static string Profile = "profile"; - public static string Contents = "contents"; - public static string Albums = "albums"; -} diff --git a/backend/src/Web/Common/GuidExtensions.cs b/backend/src/Web/Common/GuidExtensions.cs deleted file mode 100644 index c91b23f..0000000 --- a/backend/src/Web/Common/GuidExtensions.cs +++ /dev/null @@ -1,94 +0,0 @@ -namespace Hutopy.Web.Common; - -/// -/// Adapted from https://raw.githubusercontent.com/uuidjs/uuid/main/src/v7.ts. -/// to match the uuid v7 generated on the client -/// -public static class GuidHelper -{ - private class V7State - { - public long Msecs { get; set; } = long.MinValue; - public int Seq { get; set; } - } - - private static readonly V7State State = new(); - private static readonly Random Random = new(); - - public static Guid GenerateUuidV7() - { - byte[] randomValues = new byte[16]; - Random.NextBytes(randomValues); - - UpdateV7State( - State, - DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(), - randomValues); - - var values = V7Bytes(randomValues, State.Msecs, State.Seq); - - return new Guid(values); - } - - private static void UpdateV7State(V7State state, long now, byte[] randomBytes) - { - if (now > state.Msecs) - { - state.Seq = (randomBytes[6] << 23) | (randomBytes[7] << 16) | (randomBytes[8] << 8) | randomBytes[9]; - state.Msecs = now; - } - else - { - state.Seq = (state.Seq + 1) | 0; - if (state.Seq == 0) - { - state.Msecs++; - } - } - } - - private static byte[] V7Bytes(byte[] randomBytes, long? msecs = null, int? seq = null, byte[]? buf = null, int offset = 0) - { - if (buf == null) - { - buf = new byte[16]; - offset = 0; - } - - // Defaults - msecs ??= DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); - seq ??= ((randomBytes[6] & 0x7f) << 24) | (randomBytes[7] << 16) | (randomBytes[8] << 8) | randomBytes[9]; - - // byte 0-5: timestamp (48 bits) - buf[offset++] = (byte)((msecs.Value / 0x10000000000) & 0xff); - buf[offset++] = (byte)((msecs.Value / 0x100000000) & 0xff); - buf[offset++] = (byte)((msecs.Value / 0x1000000) & 0xff); - buf[offset++] = (byte)((msecs.Value / 0x10000) & 0xff); - buf[offset++] = (byte)((msecs.Value / 0x100) & 0xff); - buf[offset++] = (byte)(msecs.Value & 0xff); - - // byte 6: `version` (4 bits) | sequence bits 28-31 (4 bits) - buf[offset++] = (byte)(0x70 | ((seq.Value >> 28) & 0x0f)); - - // byte 7: sequence bits 20-27 (8 bits) - buf[offset++] = (byte)((seq.Value >> 20) & 0xff); - - // byte 8: `variant` (2 bits) | sequence bits 14-19 (6 bits) - buf[offset++] = (byte)(0x80 | ((seq.Value >> 14) & 0x3f)); - - // byte 9: sequence bits 6-13 (8 bits) - buf[offset++] = (byte)((seq.Value >> 6) & 0xff); - - // byte 10: sequence bits 0-5 (6 bits) | random (2 bits) - buf[offset++] = (byte)(((seq.Value << 2) & 0xff) | (randomBytes[10] & 0x03)); - - // bytes 11-15: random (40 bits) - buf[offset++] = randomBytes[11]; - buf[offset++] = randomBytes[12]; - buf[offset++] = randomBytes[13]; - buf[offset++] = randomBytes[14]; - buf[offset] = randomBytes[15]; - - return buf; - } -} diff --git a/backend/src/Web/Common/Security/MissingClaimException.cs b/backend/src/Web/Common/Security/MissingClaimException.cs deleted file mode 100644 index f2b6126..0000000 --- a/backend/src/Web/Common/Security/MissingClaimException.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace Hutopy.Web.Common.Security; - -public class MissingClaimException( - string claimName) - : Exception; diff --git a/backend/src/Web/Features/Contents/Data/Album.cs b/backend/src/Web/Features/Contents/Data/Album.cs deleted file mode 100644 index d538001..0000000 --- a/backend/src/Web/Features/Contents/Data/Album.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System.ComponentModel.DataAnnotations; - -namespace Hutopy.Web.Features.Contents.Data; - -public class Album -{ - public Guid Id { get; init; } - public Guid CreatedBy { get; init; } - public DateTimeOffset CreatedAt { get; init; } - public Guid? DeletedBy { get; set; } - public DateTimeOffset? DeletedAt { get; set; } - public bool IsDeleted { get; private set; } // private set → EF updates it - [MaxLength(255)] public required string Title { get; set; } - public IList Photos { get; set; } = new List(); -} - -public class AlbumPhoto -{ - public Guid Id { get; init; } - public Guid CreatedBy { get; init; } - public DateTimeOffset CreatedAt { get; init; } - public Guid? DeletedBy { get; set; } - public DateTimeOffset? DeletedAt { get; set; } - public bool IsDeleted { get; private set; } // private set → EF updates it - public Guid AlbumId { get; set; } - public Album Album { get; init; } = null!; - [MaxLength(2048)] public required string OriginalUrl { get; set; } - [MaxLength(2048)] public required string ThumbnailUrl { get; set; } - [MaxLength(255)] public string? Caption { get; set; } - public int Order { get; set; } -} diff --git a/backend/src/Web/Features/Contents/Data/Content.cs b/backend/src/Web/Features/Contents/Data/Content.cs deleted file mode 100644 index c86fe58..0000000 --- a/backend/src/Web/Features/Contents/Data/Content.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.ComponentModel.DataAnnotations; - -namespace Hutopy.Web.Features.Contents.Data; - -public class Content -{ - public Guid Id { get; init; } - public Guid CreatedBy { get; init; } - public DateTimeOffset CreatedAt { get; init; } - public Guid? DeletedBy { get; set; } - public DateTimeOffset? DeletedAt { get; set; } - public Guid CreatorId { get; set; } - public Creator Creator { get; init; } = null!; - [MaxLength(128)] public required string Title { get; set; } - - [MaxLength(2048)] public string? ThumbnailUrl { get; set; } = ""; - [MaxLength(2048)] public string Description { get; set; } = ""; - [MaxLength(2048)] public string? HtmlFileUrl { get; set; } = ""; - public IList Reactions { get; set; } = new List(); - public string[]? Urls { get; init; } - -} diff --git a/backend/src/Web/Features/Contents/Data/ContentDbContext.cs b/backend/src/Web/Features/Contents/Data/ContentDbContext.cs deleted file mode 100644 index d65b4ce..0000000 --- a/backend/src/Web/Features/Contents/Data/ContentDbContext.cs +++ /dev/null @@ -1,113 +0,0 @@ -namespace Hutopy.Web.Features.Contents.Data; - -public class ContentDbContext( - DbContextOptions options) - : DbContext(options) -{ - public const string SchemaName = "Content"; - - public DbSet Contents => Set(); - public DbSet Creators => Set(); - public DbSet Slugs => Set(); - public DbSet Albums => Set(); - public DbSet AlbumPhotos => Set(); - - protected override void OnModelCreating( - ModelBuilder modelBuilder) - { - modelBuilder.HasDefaultSchema(SchemaName); - - - modelBuilder - .Entity() - .Property(c => c.CreatedAt) - .ValueGeneratedOnAdd() - .HasDefaultValueSql("CURRENT_TIMESTAMP"); - - modelBuilder - .Entity() - .HasOne(c => c.Creator) - .WithMany() - .HasForeignKey(c => c.CreatorId) - .IsRequired(false); - - modelBuilder - .Entity() - .OwnsMany(c => c.Reactions) - .ToTable("Reactions"); - - modelBuilder - .Entity() - .Property(c => c.ThumbnailUrl); - - - modelBuilder - .Entity() - .Property(x => x.NormalizedName) - .HasComputedColumnSql("LOWER( \"Content\".\"Slugs\".\"Name\")", stored: true); - - modelBuilder - .Entity() - .HasIndex(x => x.NormalizedName) - .IsUnique(); - - - modelBuilder - .Entity() - .Property(c => c.IsDeleted) - .HasComputedColumnSql("\"DeletedAt\" IS NOT NULL", stored: true); // bool - - modelBuilder - .Entity() - .OwnsOne(x => x.Socials) - .ToTable(nameof(Socials)); - - modelBuilder - .Entity() - .OwnsOne(x => x.Presentation) - .ToTable(nameof(Presentation)); - - modelBuilder - .Entity() - .HasQueryFilter(c => !c.IsDeleted); - - // Album configuration - modelBuilder - .Entity() - .Property(c => c.CreatedAt) - .ValueGeneratedOnAdd() - .HasDefaultValueSql("CURRENT_TIMESTAMP"); - - modelBuilder - .Entity() - .Property(c => c.IsDeleted) - .HasComputedColumnSql("\"DeletedAt\" IS NOT NULL", stored: true); - - modelBuilder - .Entity() - .HasQueryFilter(a => !a.IsDeleted); - - // AlbumPhoto configuration - modelBuilder - .Entity() - .Property(c => c.CreatedAt) - .ValueGeneratedOnAdd() - .HasDefaultValueSql("CURRENT_TIMESTAMP"); - - modelBuilder - .Entity() - .Property(c => c.IsDeleted) - .HasComputedColumnSql("\"DeletedAt\" IS NOT NULL", stored: true); - - modelBuilder - .Entity() - .HasOne(ap => ap.Album) - .WithMany(a => a.Photos) - .HasForeignKey(ap => ap.AlbumId) - .IsRequired(); - - modelBuilder - .Entity() - .HasQueryFilter(ap => !ap.IsDeleted); - } -} diff --git a/backend/src/Web/Features/Contents/Data/ContentDbContextInitializer.cs b/backend/src/Web/Features/Contents/Data/ContentDbContextInitializer.cs deleted file mode 100644 index 571e8f5..0000000 --- a/backend/src/Web/Features/Contents/Data/ContentDbContextInitializer.cs +++ /dev/null @@ -1,32 +0,0 @@ -namespace Hutopy.Web.Features.Contents.Data; - -public static class InitializerExtensions -{ - public static async Task InitialiseContentDbContextAsync(this WebApplication app) - { - using var scope = app.Services.CreateScope(); - - var initializer = scope.ServiceProvider.GetRequiredService(); - - await initializer.InitialiseAsync(); - } -} - -public class ContentDbContextInitializer( - ILogger logger, - ContentDbContext context - ) -{ - public async Task InitialiseAsync() - { - try - { - await context.Database.MigrateAsync(); - } - catch (Exception ex) - { - logger.LogError(ex, "An error occurred while initialising the content database."); - throw; - } - } -} diff --git a/backend/src/Web/Features/Contents/Data/ContentReaction.cs b/backend/src/Web/Features/Contents/Data/ContentReaction.cs deleted file mode 100644 index eec8fe4..0000000 --- a/backend/src/Web/Features/Contents/Data/ContentReaction.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using Hutopy.Web.Features.Contents.Data.Enums; - -namespace Hutopy.Web.Features.Contents.Data; - -public class ContentReaction -{ - public required Reaction Reaction { get; set; } - public required Guid UserId { get; set; } - [MaxLength(128)] public required string UserName { get; set; } -} diff --git a/backend/src/Web/Features/Contents/Data/Creator.cs b/backend/src/Web/Features/Contents/Data/Creator.cs deleted file mode 100644 index 19a5a6e..0000000 --- a/backend/src/Web/Features/Contents/Data/Creator.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System.ComponentModel.DataAnnotations; - -namespace Hutopy.Web.Features.Contents.Data; - -public class Creator -{ - public Guid Id { get; set; } - - public Guid CreatedBy { get; set; } - public DateTimeOffset CreatedAt { get; init; } - public Guid? DeletedBy { get; set; } - public DateTimeOffset? DeletedAt { get; set; } - - /// - /// Soft‑delete flag (false by default, true once DeletedAt is set) - /// - public bool IsDeleted { get; private set; } // private set → EF updates it - - [MaxLength(2048)] public string? BannerUrl { get; set; } - [MaxLength(2048)] public string? PortraitUrl { get; set; } - public bool Verified { get; set; } - [MaxLength(255)] public string Name { get; set; } - [MaxLength(128)] public string Slug { get; set; } - [MaxLength(255)] public string? Title { get; set; } - - public bool AcceptDonation { get; set; } - public Socials Socials { get; set; } = new(); - public Presentation Presentation { get; set; } = new() { Description = "Welcome to my profile!" }; -} - -public class Socials -{ - [MaxLength(2048)] public string? FacebookUrl { get; set; } - [MaxLength(2048)] public string? InstagramUrl { get; set; } - [MaxLength(2048)] public string? XUrl { get; set; } - [MaxLength(2048)] public string? LinkedInUrl { get; set; } - [MaxLength(2048)] public string? TikTokUrl { get; set; } - [MaxLength(2048)] public string? YoutubeUrl { get; set; } - [MaxLength(2048)] public string? RedditUrl { get; set; } - [MaxLength(2048)] public string? WebsiteUrl { get; set; } -} - -public class Presentation -{ - public string Description { get; set; } = null!; - [MaxLength(2048)] public string? VideoUrl { get; set; } - [MaxLength(255)] public string? PhoneNumber { get; set; } - [MaxLength(255)] public string? Email { get; set; } -} diff --git a/backend/src/Web/Features/Contents/Data/Enums/Reaction.cs b/backend/src/Web/Features/Contents/Data/Enums/Reaction.cs deleted file mode 100644 index 4617ffa..0000000 --- a/backend/src/Web/Features/Contents/Data/Enums/Reaction.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Hutopy.Web.Features.Contents.Data.Enums; - -public enum Reaction -{ - None = 0, - Like = 1, - Dislike = 2, - Love = 3, - Haha = 4, - Wow = 5, - Sad = 6, - Angry = 7 -} diff --git a/backend/src/Web/Features/Contents/Data/Migrations/20241020202641_Initial.Designer.cs b/backend/src/Web/Features/Contents/Data/Migrations/20241020202641_Initial.Designer.cs deleted file mode 100644 index 95015b2..0000000 --- a/backend/src/Web/Features/Contents/Data/Migrations/20241020202641_Initial.Designer.cs +++ /dev/null @@ -1,285 +0,0 @@ -// -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("20241020202641_Initial")] - partial class Initial - { - /// - 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("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("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedBy") - .HasColumnType("uuid"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.Property("Title") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.HasKey("Id"); - - 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.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("Socials") - .IsRequired(); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/backend/src/Web/Features/Contents/Data/Migrations/20241020202641_Initial.cs b/backend/src/Web/Features/Contents/Data/Migrations/20241020202641_Initial.cs deleted file mode 100644 index 37b0eb8..0000000 --- a/backend/src/Web/Features/Contents/Data/Migrations/20241020202641_Initial.cs +++ /dev/null @@ -1,197 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -#nullable disable - -namespace Hutopy.Web.Features.Contents.Data.Migrations -{ - /// - public partial class Initial : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.EnsureSchema( - name: "Content"); - - migrationBuilder.CreateTable( - name: "Creators", - schema: "Content", - columns: table => new - { - Id = table.Column(type: "uuid", nullable: false), - CreatedBy = table.Column(type: "uuid", nullable: false), - CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), - Name = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), - Title = table.Column(type: "character varying(255)", maxLength: 255, nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Creators", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "Colors", - schema: "Content", - columns: table => new - { - CreatorId = table.Column(type: "uuid", nullable: false), - Primary = table.Column(type: "character varying(9)", maxLength: 9, nullable: false), - Secondary = table.Column(type: "character varying(9)", maxLength: 9, nullable: false), - Background = table.Column(type: "character varying(9)", maxLength: 9, nullable: false), - Surface = table.Column(type: "character varying(9)", maxLength: 9, nullable: false), - Error = table.Column(type: "character varying(9)", maxLength: 9, nullable: false), - OnPrimary = table.Column(type: "character varying(9)", maxLength: 9, nullable: false), - OnSecondary = table.Column(type: "character varying(9)", maxLength: 9, nullable: false), - OnBackground = table.Column(type: "character varying(9)", maxLength: 9, nullable: false), - OnSurface = table.Column(type: "character varying(9)", maxLength: 9, nullable: false), - OnError = table.Column(type: "character varying(9)", maxLength: 9, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Colors", x => x.CreatorId); - table.ForeignKey( - name: "FK_Colors_Creators_CreatorId", - column: x => x.CreatorId, - principalSchema: "Content", - principalTable: "Creators", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "Contents", - schema: "Content", - columns: table => new - { - Id = table.Column(type: "uuid", nullable: false), - CreatedBy = table.Column(type: "uuid", nullable: false), - CreatedAt = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "CURRENT_TIMESTAMP"), - DeletedBy = table.Column(type: "uuid", nullable: true), - DeletedAt = table.Column(type: "timestamp with time zone", nullable: true), - Title = table.Column(type: "character varying(128)", maxLength: 128, nullable: false), - Description = table.Column(type: "character varying(2048)", maxLength: 2048, nullable: false), - HtmlFileUrl = table.Column(type: "character varying(2048)", maxLength: 2048, nullable: true), - Urls = table.Column(type: "text[]", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Contents", x => x.Id); - table.ForeignKey( - name: "FK_Contents_Creators_CreatedBy", - column: x => x.CreatedBy, - principalSchema: "Content", - principalTable: "Creators", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "Images", - schema: "Content", - columns: table => new - { - CreatorId = table.Column(type: "uuid", nullable: false), - Banner = table.Column(type: "character varying(255)", maxLength: 255, nullable: true), - Logo = table.Column(type: "character varying(255)", maxLength: 255, nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Images", x => x.CreatorId); - table.ForeignKey( - name: "FK_Images_Creators_CreatorId", - column: x => x.CreatorId, - principalSchema: "Content", - principalTable: "Creators", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "Socials", - schema: "Content", - columns: table => new - { - CreatorId = table.Column(type: "uuid", nullable: false), - FacebookUrl = table.Column(type: "character varying(255)", maxLength: 255, nullable: true), - InstagramUrl = table.Column(type: "character varying(255)", maxLength: 255, nullable: true), - XUrl = table.Column(type: "character varying(255)", maxLength: 255, nullable: true), - LinkedInUrl = table.Column(type: "character varying(255)", maxLength: 255, nullable: true), - TikTokUrl = table.Column(type: "character varying(255)", maxLength: 255, nullable: true), - YoutubeUrl = table.Column(type: "character varying(255)", maxLength: 255, nullable: true), - RedditUrl = table.Column(type: "character varying(255)", maxLength: 255, nullable: true), - WebsiteUrl = table.Column(type: "character varying(255)", maxLength: 255, nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Socials", x => x.CreatorId); - table.ForeignKey( - name: "FK_Socials_Creators_CreatorId", - column: x => x.CreatorId, - principalSchema: "Content", - principalTable: "Creators", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "Reactions", - schema: "Content", - columns: table => new - { - ContentId = table.Column(type: "uuid", nullable: false), - Id = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - Reaction = table.Column(type: "integer", nullable: false), - UserId = table.Column(type: "uuid", nullable: false), - UserName = table.Column(type: "character varying(128)", maxLength: 128, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Reactions", x => new { x.ContentId, x.Id }); - table.ForeignKey( - name: "FK_Reactions_Contents_ContentId", - column: x => x.ContentId, - principalSchema: "Content", - principalTable: "Contents", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateIndex( - name: "IX_Contents_CreatedBy", - schema: "Content", - table: "Contents", - column: "CreatedBy"); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "Colors", - schema: "Content"); - - migrationBuilder.DropTable( - name: "Images", - schema: "Content"); - - migrationBuilder.DropTable( - name: "Reactions", - schema: "Content"); - - migrationBuilder.DropTable( - name: "Socials", - schema: "Content"); - - migrationBuilder.DropTable( - name: "Contents", - schema: "Content"); - - migrationBuilder.DropTable( - name: "Creators", - schema: "Content"); - } - } -} diff --git a/backend/src/Web/Features/Contents/Data/Migrations/20241201173048_AddThumbnailUrl.Designer.cs b/backend/src/Web/Features/Contents/Data/Migrations/20241201173048_AddThumbnailUrl.Designer.cs deleted file mode 100644 index 02bab5f..0000000 --- a/backend/src/Web/Features/Contents/Data/Migrations/20241201173048_AddThumbnailUrl.Designer.cs +++ /dev/null @@ -1,289 +0,0 @@ -// -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("20241201173048_AddThumbnailUrl")] - partial class AddThumbnailUrl - { - /// - 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("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedBy") - .HasColumnType("uuid"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.Property("Title") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.HasKey("Id"); - - 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.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("Socials") - .IsRequired(); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/backend/src/Web/Features/Contents/Data/Migrations/20241201173048_AddThumbnailUrl.cs b/backend/src/Web/Features/Contents/Data/Migrations/20241201173048_AddThumbnailUrl.cs deleted file mode 100644 index 0bef1fe..0000000 --- a/backend/src/Web/Features/Contents/Data/Migrations/20241201173048_AddThumbnailUrl.cs +++ /dev/null @@ -1,31 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Hutopy.Web.Features.Contents.Data.Migrations -{ - /// - public partial class AddThumbnailUrl : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "ThumbnailUrl", - schema: "Content", - table: "Contents", - type: "character varying(512)", - maxLength: 512, - nullable: true); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "ThumbnailUrl", - schema: "Content", - table: "Contents"); - } - } -} diff --git a/backend/src/Web/Features/Contents/Data/Migrations/20241201182352_AddPresentationInfos.Designer.cs b/backend/src/Web/Features/Contents/Data/Migrations/20241201182352_AddPresentationInfos.Designer.cs deleted file mode 100644 index 6899120..0000000 --- a/backend/src/Web/Features/Contents/Data/Migrations/20241201182352_AddPresentationInfos.Designer.cs +++ /dev/null @@ -1,390 +0,0 @@ -// -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("20241201182352_AddPresentationInfos")] - partial class AddPresentationInfos - { - /// - 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("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedBy") - .HasColumnType("uuid"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.Property("Title") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.HasKey("Id"); - - 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(255) - .HasColumnType("character varying(255)"); - - b1.Property("Image2Url") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b1.Property("Image3Url") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b1.Property("Image4Url") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b1.Property("ImagesSubtitle") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b1.Property("ImagesText") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b1.Property("MainImageText") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b1.Property("MainImageUrl") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b1.Property("MainVideoText") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b1.Property("PhoneNumber") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b1.Property("Title") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b1.Property("VideoSubtitle") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b1.Property("VideoSubtitleMain") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b1.Property("VideoText") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b1.Property("VideoUrl") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b1.Property("VideoUrlMain") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - 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/backend/src/Web/Features/Contents/Data/Migrations/20241201182352_AddPresentationInfos.cs b/backend/src/Web/Features/Contents/Data/Migrations/20241201182352_AddPresentationInfos.cs deleted file mode 100644 index be125e8..0000000 --- a/backend/src/Web/Features/Contents/Data/Migrations/20241201182352_AddPresentationInfos.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Hutopy.Web.Features.Contents.Data.Migrations -{ - /// - public partial class AddPresentationInfos : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "PresentationInfos", - schema: "Content", - columns: table => new - { - CreatorId = table.Column(type: "uuid", nullable: false), - PhoneNumber = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), - Email = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), - Title = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), - MainImageUrl = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), - MainImageText = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), - MainVideoText = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), - ImagesSubtitle = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), - Image1Url = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), - Image2Url = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), - Image3Url = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), - Image4Url = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), - ImagesText = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), - VideoSubtitle = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), - VideoSubtitleMain = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), - VideoUrlMain = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), - VideoUrl = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), - VideoText = table.Column(type: "character varying(255)", maxLength: 255, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_PresentationInfos", x => x.CreatorId); - table.ForeignKey( - name: "FK_PresentationInfos_Creators_CreatorId", - column: x => x.CreatorId, - principalSchema: "Content", - principalTable: "Creators", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "PresentationInfos", - schema: "Content"); - } - } -} diff --git a/backend/src/Web/Features/Contents/Data/Migrations/20241202131957_LongerStringPresentationInfos.Designer.cs b/backend/src/Web/Features/Contents/Data/Migrations/20241202131957_LongerStringPresentationInfos.Designer.cs deleted file mode 100644 index 59ca1d8..0000000 --- a/backend/src/Web/Features/Contents/Data/Migrations/20241202131957_LongerStringPresentationInfos.Designer.cs +++ /dev/null @@ -1,390 +0,0 @@ -// -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("20241202131957_LongerStringPresentationInfos")] - partial class LongerStringPresentationInfos - { - /// - 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("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedBy") - .HasColumnType("uuid"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.Property("Title") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.HasKey("Id"); - - 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/backend/src/Web/Features/Contents/Data/Migrations/20241202131957_LongerStringPresentationInfos.cs b/backend/src/Web/Features/Contents/Data/Migrations/20241202131957_LongerStringPresentationInfos.cs deleted file mode 100644 index 1d25967..0000000 --- a/backend/src/Web/Features/Contents/Data/Migrations/20241202131957_LongerStringPresentationInfos.cs +++ /dev/null @@ -1,348 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Hutopy.Web.Features.Contents.Data.Migrations -{ - /// - public partial class LongerStringPresentationInfos : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AlterColumn( - name: "VideoUrlMain", - schema: "Content", - table: "PresentationInfos", - type: "character varying(2000)", - maxLength: 2000, - nullable: false, - oldClrType: typeof(string), - oldType: "character varying(255)", - oldMaxLength: 255); - - migrationBuilder.AlterColumn( - name: "VideoUrl", - schema: "Content", - table: "PresentationInfos", - type: "character varying(2000)", - maxLength: 2000, - nullable: false, - oldClrType: typeof(string), - oldType: "character varying(255)", - oldMaxLength: 255); - - migrationBuilder.AlterColumn( - name: "VideoText", - schema: "Content", - table: "PresentationInfos", - type: "character varying(10000)", - maxLength: 10000, - nullable: false, - oldClrType: typeof(string), - oldType: "character varying(255)", - oldMaxLength: 255); - - migrationBuilder.AlterColumn( - name: "VideoSubtitleMain", - schema: "Content", - table: "PresentationInfos", - type: "character varying(2000)", - maxLength: 2000, - nullable: false, - oldClrType: typeof(string), - oldType: "character varying(255)", - oldMaxLength: 255); - - migrationBuilder.AlterColumn( - name: "VideoSubtitle", - schema: "Content", - table: "PresentationInfos", - type: "character varying(2000)", - maxLength: 2000, - nullable: false, - oldClrType: typeof(string), - oldType: "character varying(255)", - oldMaxLength: 255); - - migrationBuilder.AlterColumn( - name: "Title", - schema: "Content", - table: "PresentationInfos", - type: "character varying(2000)", - maxLength: 2000, - nullable: false, - oldClrType: typeof(string), - oldType: "character varying(255)", - oldMaxLength: 255); - - migrationBuilder.AlterColumn( - name: "MainVideoText", - schema: "Content", - table: "PresentationInfos", - type: "character varying(10000)", - maxLength: 10000, - nullable: false, - oldClrType: typeof(string), - oldType: "character varying(255)", - oldMaxLength: 255); - - migrationBuilder.AlterColumn( - name: "MainImageUrl", - schema: "Content", - table: "PresentationInfos", - type: "character varying(2000)", - maxLength: 2000, - nullable: false, - oldClrType: typeof(string), - oldType: "character varying(255)", - oldMaxLength: 255); - - migrationBuilder.AlterColumn( - name: "MainImageText", - schema: "Content", - table: "PresentationInfos", - type: "character varying(10000)", - maxLength: 10000, - nullable: false, - oldClrType: typeof(string), - oldType: "character varying(255)", - oldMaxLength: 255); - - migrationBuilder.AlterColumn( - name: "ImagesText", - schema: "Content", - table: "PresentationInfos", - type: "character varying(10000)", - maxLength: 10000, - nullable: false, - oldClrType: typeof(string), - oldType: "character varying(255)", - oldMaxLength: 255); - - migrationBuilder.AlterColumn( - name: "ImagesSubtitle", - schema: "Content", - table: "PresentationInfos", - type: "character varying(2000)", - maxLength: 2000, - nullable: false, - oldClrType: typeof(string), - oldType: "character varying(255)", - oldMaxLength: 255); - - migrationBuilder.AlterColumn( - name: "Image4Url", - schema: "Content", - table: "PresentationInfos", - type: "character varying(2000)", - maxLength: 2000, - nullable: false, - oldClrType: typeof(string), - oldType: "character varying(255)", - oldMaxLength: 255); - - migrationBuilder.AlterColumn( - name: "Image3Url", - schema: "Content", - table: "PresentationInfos", - type: "character varying(2000)", - maxLength: 2000, - nullable: false, - oldClrType: typeof(string), - oldType: "character varying(255)", - oldMaxLength: 255); - - migrationBuilder.AlterColumn( - name: "Image2Url", - schema: "Content", - table: "PresentationInfos", - type: "character varying(2000)", - maxLength: 2000, - nullable: false, - oldClrType: typeof(string), - oldType: "character varying(255)", - oldMaxLength: 255); - - migrationBuilder.AlterColumn( - name: "Image1Url", - schema: "Content", - table: "PresentationInfos", - type: "character varying(2000)", - maxLength: 2000, - nullable: false, - oldClrType: typeof(string), - oldType: "character varying(255)", - oldMaxLength: 255); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.AlterColumn( - name: "VideoUrlMain", - schema: "Content", - table: "PresentationInfos", - type: "character varying(255)", - maxLength: 255, - nullable: false, - oldClrType: typeof(string), - oldType: "character varying(2000)", - oldMaxLength: 2000); - - migrationBuilder.AlterColumn( - name: "VideoUrl", - schema: "Content", - table: "PresentationInfos", - type: "character varying(255)", - maxLength: 255, - nullable: false, - oldClrType: typeof(string), - oldType: "character varying(2000)", - oldMaxLength: 2000); - - migrationBuilder.AlterColumn( - name: "VideoText", - schema: "Content", - table: "PresentationInfos", - type: "character varying(255)", - maxLength: 255, - nullable: false, - oldClrType: typeof(string), - oldType: "character varying(10000)", - oldMaxLength: 10000); - - migrationBuilder.AlterColumn( - name: "VideoSubtitleMain", - schema: "Content", - table: "PresentationInfos", - type: "character varying(255)", - maxLength: 255, - nullable: false, - oldClrType: typeof(string), - oldType: "character varying(2000)", - oldMaxLength: 2000); - - migrationBuilder.AlterColumn( - name: "VideoSubtitle", - schema: "Content", - table: "PresentationInfos", - type: "character varying(255)", - maxLength: 255, - nullable: false, - oldClrType: typeof(string), - oldType: "character varying(2000)", - oldMaxLength: 2000); - - migrationBuilder.AlterColumn( - name: "Title", - schema: "Content", - table: "PresentationInfos", - type: "character varying(255)", - maxLength: 255, - nullable: false, - oldClrType: typeof(string), - oldType: "character varying(2000)", - oldMaxLength: 2000); - - migrationBuilder.AlterColumn( - name: "MainVideoText", - schema: "Content", - table: "PresentationInfos", - type: "character varying(255)", - maxLength: 255, - nullable: false, - oldClrType: typeof(string), - oldType: "character varying(10000)", - oldMaxLength: 10000); - - migrationBuilder.AlterColumn( - name: "MainImageUrl", - schema: "Content", - table: "PresentationInfos", - type: "character varying(255)", - maxLength: 255, - nullable: false, - oldClrType: typeof(string), - oldType: "character varying(2000)", - oldMaxLength: 2000); - - migrationBuilder.AlterColumn( - name: "MainImageText", - schema: "Content", - table: "PresentationInfos", - type: "character varying(255)", - maxLength: 255, - nullable: false, - oldClrType: typeof(string), - oldType: "character varying(10000)", - oldMaxLength: 10000); - - migrationBuilder.AlterColumn( - name: "ImagesText", - schema: "Content", - table: "PresentationInfos", - type: "character varying(255)", - maxLength: 255, - nullable: false, - oldClrType: typeof(string), - oldType: "character varying(10000)", - oldMaxLength: 10000); - - migrationBuilder.AlterColumn( - name: "ImagesSubtitle", - schema: "Content", - table: "PresentationInfos", - type: "character varying(255)", - maxLength: 255, - nullable: false, - oldClrType: typeof(string), - oldType: "character varying(2000)", - oldMaxLength: 2000); - - migrationBuilder.AlterColumn( - name: "Image4Url", - schema: "Content", - table: "PresentationInfos", - type: "character varying(255)", - maxLength: 255, - nullable: false, - oldClrType: typeof(string), - oldType: "character varying(2000)", - oldMaxLength: 2000); - - migrationBuilder.AlterColumn( - name: "Image3Url", - schema: "Content", - table: "PresentationInfos", - type: "character varying(255)", - maxLength: 255, - nullable: false, - oldClrType: typeof(string), - oldType: "character varying(2000)", - oldMaxLength: 2000); - - migrationBuilder.AlterColumn( - name: "Image2Url", - schema: "Content", - table: "PresentationInfos", - type: "character varying(255)", - maxLength: 255, - nullable: false, - oldClrType: typeof(string), - oldType: "character varying(2000)", - oldMaxLength: 2000); - - migrationBuilder.AlterColumn( - name: "Image1Url", - schema: "Content", - table: "PresentationInfos", - type: "character varying(255)", - maxLength: 255, - nullable: false, - oldClrType: typeof(string), - oldType: "character varying(2000)", - oldMaxLength: 2000); - } - } -} diff --git a/backend/src/Web/Features/Contents/Data/Migrations/20250108022601_AddComputedColumnAndIndex_CreatorName.Designer.cs b/backend/src/Web/Features/Contents/Data/Migrations/20250108022601_AddComputedColumnAndIndex_CreatorName.Designer.cs deleted file mode 100644 index dcd72ef..0000000 --- a/backend/src/Web/Features/Contents/Data/Migrations/20250108022601_AddComputedColumnAndIndex_CreatorName.Designer.cs +++ /dev/null @@ -1,400 +0,0 @@ -// -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("20250108022601_AddComputedColumnAndIndex_CreatorName")] - partial class AddComputedColumnAndIndex_CreatorName - { - /// - 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("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.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/backend/src/Web/Features/Contents/Data/Migrations/20250108022601_AddComputedColumnAndIndex_CreatorName.cs b/backend/src/Web/Features/Contents/Data/Migrations/20250108022601_AddComputedColumnAndIndex_CreatorName.cs deleted file mode 100644 index ab8bd48..0000000 --- a/backend/src/Web/Features/Contents/Data/Migrations/20250108022601_AddComputedColumnAndIndex_CreatorName.cs +++ /dev/null @@ -1,45 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Hutopy.Web.Features.Contents.Data.Migrations -{ - /// - public partial class AddComputedColumnAndIndex_CreatorName : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "NormalizedName", - schema: "Content", - table: "Creators", - type: "character varying(255)", - maxLength: 255, - nullable: false, - computedColumnSql: "LOWER( \"Content\".\"Creators\".\"Name\")", - stored: true); - - migrationBuilder.CreateIndex( - name: "IX_Creators_NormalizedName", - schema: "Content", - table: "Creators", - column: "NormalizedName", - unique: true); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropIndex( - name: "IX_Creators_NormalizedName", - schema: "Content", - table: "Creators"); - - migrationBuilder.DropColumn( - name: "NormalizedName", - schema: "Content", - table: "Creators"); - } - } -} diff --git a/backend/src/Web/Features/Contents/Data/Migrations/20250108210552_Add_Verified_Creator.Designer.cs b/backend/src/Web/Features/Contents/Data/Migrations/20250108210552_Add_Verified_Creator.Designer.cs deleted file mode 100644 index fbc37b9..0000000 --- a/backend/src/Web/Features/Contents/Data/Migrations/20250108210552_Add_Verified_Creator.Designer.cs +++ /dev/null @@ -1,403 +0,0 @@ -// -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("20250108210552_Add_Verified_Creator")] - partial class Add_Verified_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("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/backend/src/Web/Features/Contents/Data/Migrations/20250108210552_Add_Verified_Creator.cs b/backend/src/Web/Features/Contents/Data/Migrations/20250108210552_Add_Verified_Creator.cs deleted file mode 100644 index 487b307..0000000 --- a/backend/src/Web/Features/Contents/Data/Migrations/20250108210552_Add_Verified_Creator.cs +++ /dev/null @@ -1,31 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Hutopy.Web.Features.Contents.Data.Migrations -{ - /// - public partial class Add_Verified_Creator : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "Verified", - schema: "Content", - table: "Creators", - type: "boolean", - nullable: false, - defaultValue: false); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "Verified", - schema: "Content", - table: "Creators"); - } - } -} diff --git a/backend/src/Web/Features/Contents/Data/Migrations/20250109015556_Adds_AcceptDonation_Creator.Designer.cs b/backend/src/Web/Features/Contents/Data/Migrations/20250109015556_Adds_AcceptDonation_Creator.Designer.cs deleted file mode 100644 index 1a12506..0000000 --- a/backend/src/Web/Features/Contents/Data/Migrations/20250109015556_Adds_AcceptDonation_Creator.Designer.cs +++ /dev/null @@ -1,406 +0,0 @@ -// -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/backend/src/Web/Features/Contents/Data/Migrations/20250131210849_AddSlug.Designer.cs b/backend/src/Web/Features/Contents/Data/Migrations/20250131210849_AddSlug.Designer.cs deleted file mode 100644 index 2577e29..0000000 --- a/backend/src/Web/Features/Contents/Data/Migrations/20250131210849_AddSlug.Designer.cs +++ /dev/null @@ -1,442 +0,0 @@ -// -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("20250131210849_AddSlug")] - partial class AddSlug - { - /// - 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("SlugsId") - .HasColumnType("uuid"); - - b.Property("Title") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.Property("Verified") - .HasColumnType("boolean"); - - b.HasKey("Id"); - - b.HasIndex("SlugsId"); - - b.ToTable("Creators", "Content"); - }); - - modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.Slugs", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("Active") - .HasColumnType("boolean"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedBy") - .HasColumnType("uuid"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)"); - - b.Property("NormalizedName") - .IsRequired() - .ValueGeneratedOnAddOrUpdate() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasComputedColumnSql("LOWER( \"Content\".\"Slugs\".\"Name\")", true); - - b.Property("ReservedUntil") - .HasColumnType("timestamp with time zone"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedName") - .IsUnique(); - - b.ToTable("Slugs", "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.HasOne("Hutopy.Web.Features.Contents.Data.Slugs", "Slugs") - .WithMany() - .HasForeignKey("SlugsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - 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("Slugs"); - - b.Navigation("Socials") - .IsRequired(); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/backend/src/Web/Features/Contents/Data/Migrations/20250131210849_AddSlug.cs b/backend/src/Web/Features/Contents/Data/Migrations/20250131210849_AddSlug.cs deleted file mode 100644 index ec5e2b5..0000000 --- a/backend/src/Web/Features/Contents/Data/Migrations/20250131210849_AddSlug.cs +++ /dev/null @@ -1,159 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Hutopy.Web.Features.Contents.Data.Migrations -{ - /// - public partial class AddSlug : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropIndex( - name: "IX_Creators_NormalizedName", - schema: "Content", - table: "Creators"); - - migrationBuilder.DropColumn( - name: "NormalizedName", - schema: "Content", - table: "Creators"); - - // Add SlugsId column to Creators (temporary nullable to avoid issues while updating) - migrationBuilder.AddColumn( - name: "SlugsId", - schema: "Content", - table: "Creators", - type: "uuid", - nullable: true); - - // Create the Slugs table - migrationBuilder.CreateTable( - name: "Slugs", - schema: "Content", - columns: table => new - { - Id = table.Column(type: "uuid", nullable: false), - CreatedBy = table.Column(type: "uuid", nullable: false), - CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), - Name = table.Column(type: "character varying(128)", maxLength: 128, nullable: false), - NormalizedName = table.Column(type: "character varying(128)", maxLength: 128, nullable: false, computedColumnSql: "LOWER( \"Content\".\"Slugs\".\"Name\")", stored: true), - ReservedUntil = table.Column(type: "timestamp with time zone", nullable: false), - Active = table.Column(type: "boolean", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Slugs", x => x.Id); - }); - - // Create Slugs for existing Creators - migrationBuilder.Sql(@" - INSERT INTO ""Content"".""Slugs"" (""Id"", ""Name"", ""CreatedBy"", ""CreatedAt"", ""ReservedUntil"", ""Active"") - SELECT ""Id"", ""Name"", ""CreatedBy"", ""CreatedAt"", ""CreatedAt"", TRUE - FROM ""Content"".""Creators"" - WHERE ""Name"" IS NOT NULL - "); - - // Update Creators to reference Slugs - migrationBuilder.Sql(@" - UPDATE ""Content"".""Creators"" - SET ""SlugsId"" = (SELECT ""Id"" FROM ""Content"".""Slugs"" WHERE ""Content"".""Slugs"".""Name"" = ""Content"".""Creators"".""Name"") - WHERE ""Name"" IS NOT NULL - "); - - // Make SlugsId non-nullable - migrationBuilder.AlterColumn( - name: "SlugsId", - schema: "Content", - table: "Creators", - type: "uuid", - nullable: false, - oldClrType: typeof(Guid), - oldType: "uuid", - oldNullable: true); - - // Create index for SlugsId - migrationBuilder.CreateIndex( - name: "IX_Creators_SlugsId", - schema: "Content", - table: "Creators", - column: "SlugsId"); - - // Create index for NormalizedName in Slugs - migrationBuilder.CreateIndex( - name: "IX_Slugs_NormalizedName", - schema: "Content", - table: "Slugs", - column: "NormalizedName", - unique: true); - - // Add foreign key constraint - migrationBuilder.AddForeignKey( - name: "FK_Creators_Slugs_SlugsId", - schema: "Content", - table: "Creators", - column: "SlugsId", - principalSchema: "Content", - principalTable: "Slugs", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - - // Drop the Name column - migrationBuilder.DropColumn( - name: "Name", - schema: "Content", - table: "Creators"); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropForeignKey( - name: "FK_Creators_Slugs_SlugsId", - schema: "Content", - table: "Creators"); - - migrationBuilder.DropTable( - name: "Slugs", - schema: "Content"); - - migrationBuilder.DropIndex( - name: "IX_Creators_SlugsId", - schema: "Content", - table: "Creators"); - - migrationBuilder.DropColumn( - name: "SlugsId", - schema: "Content", - table: "Creators"); - - migrationBuilder.AddColumn( - name: "Name", - schema: "Content", - table: "Creators", - type: "character varying(255)", - maxLength: 255, - nullable: false, - defaultValue: ""); - - migrationBuilder.AddColumn( - name: "NormalizedName", - schema: "Content", - table: "Creators", - type: "character varying(255)", - maxLength: 255, - nullable: false, - computedColumnSql: "LOWER( \"Content\".\"Creators\".\"Name\")", - stored: true); - - migrationBuilder.CreateIndex( - name: "IX_Creators_NormalizedName", - schema: "Content", - table: "Creators", - column: "NormalizedName", - unique: true); - } - } -} diff --git a/backend/src/Web/Features/Contents/Data/Migrations/20250208043324_RemoveColors.Designer.cs b/backend/src/Web/Features/Contents/Data/Migrations/20250208043324_RemoveColors.Designer.cs deleted file mode 100644 index 05560fe..0000000 --- a/backend/src/Web/Features/Contents/Data/Migrations/20250208043324_RemoveColors.Designer.cs +++ /dev/null @@ -1,376 +0,0 @@ -// -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("20250208043324_RemoveColors")] - partial class RemoveColors - { - /// - 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("SlugsId") - .HasColumnType("uuid"); - - b.Property("Title") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.Property("Verified") - .HasColumnType("boolean"); - - b.HasKey("Id"); - - b.HasIndex("SlugsId"); - - b.ToTable("Creators", "Content"); - }); - - modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.Slugs", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("Active") - .HasColumnType("boolean"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedBy") - .HasColumnType("uuid"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)"); - - b.Property("NormalizedName") - .IsRequired() - .ValueGeneratedOnAddOrUpdate() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasComputedColumnSql("LOWER( \"Content\".\"Slugs\".\"Name\")", true); - - b.Property("ReservedUntil") - .HasColumnType("timestamp with time zone"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedName") - .IsUnique(); - - b.ToTable("Slugs", "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.HasOne("Hutopy.Web.Features.Contents.Data.Slugs", "Slugs") - .WithMany() - .HasForeignKey("SlugsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - 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("Images") - .IsRequired(); - - b.Navigation("PresentationInfos") - .IsRequired(); - - b.Navigation("Slugs"); - - b.Navigation("Socials") - .IsRequired(); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/backend/src/Web/Features/Contents/Data/Migrations/20250208043324_RemoveColors.cs b/backend/src/Web/Features/Contents/Data/Migrations/20250208043324_RemoveColors.cs deleted file mode 100644 index da05c8c..0000000 --- a/backend/src/Web/Features/Contents/Data/Migrations/20250208043324_RemoveColors.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Hutopy.Web.Features.Contents.Data.Migrations -{ - /// - public partial class RemoveColors : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "Colors", - schema: "Content"); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "Colors", - schema: "Content", - columns: table => new - { - CreatorId = table.Column(type: "uuid", nullable: false), - Background = table.Column(type: "character varying(9)", maxLength: 9, nullable: false), - Error = table.Column(type: "character varying(9)", maxLength: 9, nullable: false), - OnBackground = table.Column(type: "character varying(9)", maxLength: 9, nullable: false), - OnError = table.Column(type: "character varying(9)", maxLength: 9, nullable: false), - OnPrimary = table.Column(type: "character varying(9)", maxLength: 9, nullable: false), - OnSecondary = table.Column(type: "character varying(9)", maxLength: 9, nullable: false), - OnSurface = table.Column(type: "character varying(9)", maxLength: 9, nullable: false), - Primary = table.Column(type: "character varying(9)", maxLength: 9, nullable: false), - Secondary = table.Column(type: "character varying(9)", maxLength: 9, nullable: false), - Surface = table.Column(type: "character varying(9)", maxLength: 9, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Colors", x => x.CreatorId); - table.ForeignKey( - name: "FK_Colors_Creators_CreatorId", - column: x => x.CreatorId, - principalSchema: "Content", - principalTable: "Creators", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - } - } -} diff --git a/backend/src/Web/Features/Contents/Data/Migrations/20250402024746_EnsureUrlsAre1024Chars.Designer.cs b/backend/src/Web/Features/Contents/Data/Migrations/20250402024746_EnsureUrlsAre1024Chars.Designer.cs deleted file mode 100644 index 0c0855d..0000000 --- a/backend/src/Web/Features/Contents/Data/Migrations/20250402024746_EnsureUrlsAre1024Chars.Designer.cs +++ /dev/null @@ -1,376 +0,0 @@ -// -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("20250402024746_EnsureUrlsAre1024Chars")] - partial class EnsureUrlsAre1024Chars - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasDefaultSchema("Content") - .HasAnnotation("ProductVersion", "8.0.14") - .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(1024) - .HasColumnType("character varying(1024)"); - - b.Property("ThumbnailUrl") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)"); - - b.PrimitiveCollection("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("SlugsId") - .HasColumnType("uuid"); - - b.Property("Title") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.Property("Verified") - .HasColumnType("boolean"); - - b.HasKey("Id"); - - b.HasIndex("SlugsId"); - - b.ToTable("Creators", "Content"); - }); - - modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.Slugs", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("Active") - .HasColumnType("boolean"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedBy") - .HasColumnType("uuid"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)"); - - b.Property("NormalizedName") - .IsRequired() - .ValueGeneratedOnAddOrUpdate() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasComputedColumnSql("LOWER( \"Content\".\"Slugs\".\"Name\")", true); - - b.Property("ReservedUntil") - .HasColumnType("timestamp with time zone"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedName") - .IsUnique(); - - b.ToTable("Slugs", "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.HasOne("Hutopy.Web.Features.Contents.Data.Slugs", "Slugs") - .WithMany() - .HasForeignKey("SlugsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - 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(1024) - .HasColumnType("character varying(1024)"); - - b1.Property("Image2Url") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)"); - - b1.Property("Image3Url") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)"); - - b1.Property("Image4Url") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)"); - - 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(1024) - .HasColumnType("character varying(1024)"); - - 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(1024) - .HasColumnType("character varying(1024)"); - - b1.Property("VideoUrlMain") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)"); - - 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(1024) - .HasColumnType("character varying(1024)"); - - b1.Property("InstagramUrl") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)"); - - b1.Property("LinkedInUrl") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)"); - - b1.Property("RedditUrl") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)"); - - b1.Property("TikTokUrl") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)"); - - b1.Property("WebsiteUrl") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)"); - - b1.Property("XUrl") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)"); - - b1.Property("YoutubeUrl") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)"); - - b1.HasKey("CreatorId"); - - b1.ToTable("Socials", "Content"); - - b1.WithOwner() - .HasForeignKey("CreatorId"); - }); - - b.Navigation("Images") - .IsRequired(); - - b.Navigation("PresentationInfos") - .IsRequired(); - - b.Navigation("Slugs"); - - b.Navigation("Socials") - .IsRequired(); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/backend/src/Web/Features/Contents/Data/Migrations/20250402024746_EnsureUrlsAre1024Chars.cs b/backend/src/Web/Features/Contents/Data/Migrations/20250402024746_EnsureUrlsAre1024Chars.cs deleted file mode 100644 index 95fe901..0000000 --- a/backend/src/Web/Features/Contents/Data/Migrations/20250402024746_EnsureUrlsAre1024Chars.cs +++ /dev/null @@ -1,412 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Hutopy.Web.Features.Contents.Data.Migrations -{ - /// - public partial class EnsureUrlsAre1024Chars : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AlterColumn( - name: "YoutubeUrl", - schema: "Content", - table: "Socials", - type: "character varying(1024)", - maxLength: 1024, - nullable: true, - oldClrType: typeof(string), - oldType: "character varying(255)", - oldMaxLength: 255, - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "XUrl", - schema: "Content", - table: "Socials", - type: "character varying(1024)", - maxLength: 1024, - nullable: true, - oldClrType: typeof(string), - oldType: "character varying(255)", - oldMaxLength: 255, - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "WebsiteUrl", - schema: "Content", - table: "Socials", - type: "character varying(1024)", - maxLength: 1024, - nullable: true, - oldClrType: typeof(string), - oldType: "character varying(255)", - oldMaxLength: 255, - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "TikTokUrl", - schema: "Content", - table: "Socials", - type: "character varying(1024)", - maxLength: 1024, - nullable: true, - oldClrType: typeof(string), - oldType: "character varying(255)", - oldMaxLength: 255, - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "RedditUrl", - schema: "Content", - table: "Socials", - type: "character varying(1024)", - maxLength: 1024, - nullable: true, - oldClrType: typeof(string), - oldType: "character varying(255)", - oldMaxLength: 255, - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "LinkedInUrl", - schema: "Content", - table: "Socials", - type: "character varying(1024)", - maxLength: 1024, - nullable: true, - oldClrType: typeof(string), - oldType: "character varying(255)", - oldMaxLength: 255, - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "InstagramUrl", - schema: "Content", - table: "Socials", - type: "character varying(1024)", - maxLength: 1024, - nullable: true, - oldClrType: typeof(string), - oldType: "character varying(255)", - oldMaxLength: 255, - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "FacebookUrl", - schema: "Content", - table: "Socials", - type: "character varying(1024)", - maxLength: 1024, - nullable: true, - oldClrType: typeof(string), - oldType: "character varying(255)", - oldMaxLength: 255, - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "VideoUrl", - schema: "Content", - table: "PresentationInfos", - type: "character varying(1024)", - maxLength: 1024, - nullable: false, - oldClrType: typeof(string), - oldType: "character varying(2000)", - oldMaxLength: 2000); - - migrationBuilder.AlterColumn( - name: "VideoUrlMain", - schema: "Content", - table: "PresentationInfos", - type: "character varying(1024)", - maxLength: 1024, - nullable: false, - oldClrType: typeof(string), - oldType: "character varying(2000)", - oldMaxLength: 2000); - - migrationBuilder.AlterColumn( - name: "MainImageUrl", - schema: "Content", - table: "PresentationInfos", - type: "character varying(1024)", - maxLength: 1024, - nullable: false, - oldClrType: typeof(string), - oldType: "character varying(2000)", - oldMaxLength: 2000); - - migrationBuilder.AlterColumn( - name: "Image4Url", - schema: "Content", - table: "PresentationInfos", - type: "character varying(1024)", - maxLength: 1024, - nullable: false, - oldClrType: typeof(string), - oldType: "character varying(2000)", - oldMaxLength: 2000); - - migrationBuilder.AlterColumn( - name: "Image3Url", - schema: "Content", - table: "PresentationInfos", - type: "character varying(1024)", - maxLength: 1024, - nullable: false, - oldClrType: typeof(string), - oldType: "character varying(2000)", - oldMaxLength: 2000); - - migrationBuilder.AlterColumn( - name: "Image2Url", - schema: "Content", - table: "PresentationInfos", - type: "character varying(1024)", - maxLength: 1024, - nullable: false, - oldClrType: typeof(string), - oldType: "character varying(2000)", - oldMaxLength: 2000); - - migrationBuilder.AlterColumn( - name: "Image1Url", - schema: "Content", - table: "PresentationInfos", - type: "character varying(1024)", - maxLength: 1024, - nullable: false, - oldClrType: typeof(string), - oldType: "character varying(2000)", - oldMaxLength: 2000); - - migrationBuilder.AlterColumn( - name: "ThumbnailUrl", - schema: "Content", - table: "Contents", - type: "character varying(1024)", - maxLength: 1024, - nullable: true, - oldClrType: typeof(string), - oldType: "character varying(512)", - oldMaxLength: 512, - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "HtmlFileUrl", - schema: "Content", - table: "Contents", - type: "character varying(1024)", - maxLength: 1024, - nullable: true, - oldClrType: typeof(string), - oldType: "character varying(2048)", - oldMaxLength: 2048, - oldNullable: true); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.AlterColumn( - name: "YoutubeUrl", - schema: "Content", - table: "Socials", - type: "character varying(255)", - maxLength: 255, - nullable: true, - oldClrType: typeof(string), - oldType: "character varying(1024)", - oldMaxLength: 1024, - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "XUrl", - schema: "Content", - table: "Socials", - type: "character varying(255)", - maxLength: 255, - nullable: true, - oldClrType: typeof(string), - oldType: "character varying(1024)", - oldMaxLength: 1024, - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "WebsiteUrl", - schema: "Content", - table: "Socials", - type: "character varying(255)", - maxLength: 255, - nullable: true, - oldClrType: typeof(string), - oldType: "character varying(1024)", - oldMaxLength: 1024, - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "TikTokUrl", - schema: "Content", - table: "Socials", - type: "character varying(255)", - maxLength: 255, - nullable: true, - oldClrType: typeof(string), - oldType: "character varying(1024)", - oldMaxLength: 1024, - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "RedditUrl", - schema: "Content", - table: "Socials", - type: "character varying(255)", - maxLength: 255, - nullable: true, - oldClrType: typeof(string), - oldType: "character varying(1024)", - oldMaxLength: 1024, - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "LinkedInUrl", - schema: "Content", - table: "Socials", - type: "character varying(255)", - maxLength: 255, - nullable: true, - oldClrType: typeof(string), - oldType: "character varying(1024)", - oldMaxLength: 1024, - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "InstagramUrl", - schema: "Content", - table: "Socials", - type: "character varying(255)", - maxLength: 255, - nullable: true, - oldClrType: typeof(string), - oldType: "character varying(1024)", - oldMaxLength: 1024, - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "FacebookUrl", - schema: "Content", - table: "Socials", - type: "character varying(255)", - maxLength: 255, - nullable: true, - oldClrType: typeof(string), - oldType: "character varying(1024)", - oldMaxLength: 1024, - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "VideoUrl", - schema: "Content", - table: "PresentationInfos", - type: "character varying(2000)", - maxLength: 2000, - nullable: false, - oldClrType: typeof(string), - oldType: "character varying(1024)", - oldMaxLength: 1024); - - migrationBuilder.AlterColumn( - name: "VideoUrlMain", - schema: "Content", - table: "PresentationInfos", - type: "character varying(2000)", - maxLength: 2000, - nullable: false, - oldClrType: typeof(string), - oldType: "character varying(1024)", - oldMaxLength: 1024); - - migrationBuilder.AlterColumn( - name: "MainImageUrl", - schema: "Content", - table: "PresentationInfos", - type: "character varying(2000)", - maxLength: 2000, - nullable: false, - oldClrType: typeof(string), - oldType: "character varying(1024)", - oldMaxLength: 1024); - - migrationBuilder.AlterColumn( - name: "Image4Url", - schema: "Content", - table: "PresentationInfos", - type: "character varying(2000)", - maxLength: 2000, - nullable: false, - oldClrType: typeof(string), - oldType: "character varying(1024)", - oldMaxLength: 1024); - - migrationBuilder.AlterColumn( - name: "Image3Url", - schema: "Content", - table: "PresentationInfos", - type: "character varying(2000)", - maxLength: 2000, - nullable: false, - oldClrType: typeof(string), - oldType: "character varying(1024)", - oldMaxLength: 1024); - - migrationBuilder.AlterColumn( - name: "Image2Url", - schema: "Content", - table: "PresentationInfos", - type: "character varying(2000)", - maxLength: 2000, - nullable: false, - oldClrType: typeof(string), - oldType: "character varying(1024)", - oldMaxLength: 1024); - - migrationBuilder.AlterColumn( - name: "Image1Url", - schema: "Content", - table: "PresentationInfos", - type: "character varying(2000)", - maxLength: 2000, - nullable: false, - oldClrType: typeof(string), - oldType: "character varying(1024)", - oldMaxLength: 1024); - - migrationBuilder.AlterColumn( - name: "ThumbnailUrl", - schema: "Content", - table: "Contents", - type: "character varying(512)", - maxLength: 512, - nullable: true, - oldClrType: typeof(string), - oldType: "character varying(1024)", - oldMaxLength: 1024, - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "HtmlFileUrl", - schema: "Content", - table: "Contents", - type: "character varying(2048)", - maxLength: 2048, - nullable: true, - oldClrType: typeof(string), - oldType: "character varying(1024)", - oldMaxLength: 1024, - oldNullable: true); - } - } -} diff --git a/backend/src/Web/Features/Contents/Data/Migrations/20250403035555_UrlsTo2048Chars.Designer.cs b/backend/src/Web/Features/Contents/Data/Migrations/20250403035555_UrlsTo2048Chars.Designer.cs deleted file mode 100644 index 3293357..0000000 --- a/backend/src/Web/Features/Contents/Data/Migrations/20250403035555_UrlsTo2048Chars.Designer.cs +++ /dev/null @@ -1,376 +0,0 @@ -// -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("20250403035555_UrlsTo2048Chars")] - partial class UrlsTo2048Chars - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasDefaultSchema("Content") - .HasAnnotation("ProductVersion", "9.0.3") - .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(2048) - .HasColumnType("character varying(2048)"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)"); - - b.PrimitiveCollection("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("SlugsId") - .HasColumnType("uuid"); - - b.Property("Title") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.Property("Verified") - .HasColumnType("boolean"); - - b.HasKey("Id"); - - b.HasIndex("SlugsId"); - - b.ToTable("Creators", "Content"); - }); - - modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.Slugs", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("Active") - .HasColumnType("boolean"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedBy") - .HasColumnType("uuid"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)"); - - b.Property("NormalizedName") - .IsRequired() - .ValueGeneratedOnAddOrUpdate() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasComputedColumnSql("LOWER( \"Content\".\"Slugs\".\"Name\")", true); - - b.Property("ReservedUntil") - .HasColumnType("timestamp with time zone"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedName") - .IsUnique(); - - b.ToTable("Slugs", "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.HasOne("Hutopy.Web.Features.Contents.Data.Slugs", "Slugs") - .WithMany() - .HasForeignKey("SlugsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.OwnsOne("Hutopy.Web.Features.Contents.Data.Images", "Images", b1 => - { - b1.Property("CreatorId") - .HasColumnType("uuid"); - - b1.Property("Banner") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("Logo") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - 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(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("Image2Url") - .IsRequired() - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("Image3Url") - .IsRequired() - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("Image4Url") - .IsRequired() - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - 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(2048) - .HasColumnType("character varying(2048)"); - - 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(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("VideoUrlMain") - .IsRequired() - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - 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(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("InstagramUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("LinkedInUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("RedditUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("TikTokUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("WebsiteUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("XUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("YoutubeUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.HasKey("CreatorId"); - - b1.ToTable("Socials", "Content"); - - b1.WithOwner() - .HasForeignKey("CreatorId"); - }); - - b.Navigation("Images") - .IsRequired(); - - b.Navigation("PresentationInfos") - .IsRequired(); - - b.Navigation("Slugs"); - - b.Navigation("Socials") - .IsRequired(); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/backend/src/Web/Features/Contents/Data/Migrations/20250403035555_UrlsTo2048Chars.cs b/backend/src/Web/Features/Contents/Data/Migrations/20250403035555_UrlsTo2048Chars.cs deleted file mode 100644 index 09e2785..0000000 --- a/backend/src/Web/Features/Contents/Data/Migrations/20250403035555_UrlsTo2048Chars.cs +++ /dev/null @@ -1,460 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Hutopy.Web.Features.Contents.Data.Migrations -{ - /// - public partial class UrlsTo2048Chars : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AlterColumn( - name: "YoutubeUrl", - schema: "Content", - table: "Socials", - type: "character varying(2048)", - maxLength: 2048, - nullable: true, - oldClrType: typeof(string), - oldType: "character varying(1024)", - oldMaxLength: 1024, - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "XUrl", - schema: "Content", - table: "Socials", - type: "character varying(2048)", - maxLength: 2048, - nullable: true, - oldClrType: typeof(string), - oldType: "character varying(1024)", - oldMaxLength: 1024, - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "WebsiteUrl", - schema: "Content", - table: "Socials", - type: "character varying(2048)", - maxLength: 2048, - nullable: true, - oldClrType: typeof(string), - oldType: "character varying(1024)", - oldMaxLength: 1024, - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "TikTokUrl", - schema: "Content", - table: "Socials", - type: "character varying(2048)", - maxLength: 2048, - nullable: true, - oldClrType: typeof(string), - oldType: "character varying(1024)", - oldMaxLength: 1024, - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "RedditUrl", - schema: "Content", - table: "Socials", - type: "character varying(2048)", - maxLength: 2048, - nullable: true, - oldClrType: typeof(string), - oldType: "character varying(1024)", - oldMaxLength: 1024, - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "LinkedInUrl", - schema: "Content", - table: "Socials", - type: "character varying(2048)", - maxLength: 2048, - nullable: true, - oldClrType: typeof(string), - oldType: "character varying(1024)", - oldMaxLength: 1024, - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "InstagramUrl", - schema: "Content", - table: "Socials", - type: "character varying(2048)", - maxLength: 2048, - nullable: true, - oldClrType: typeof(string), - oldType: "character varying(1024)", - oldMaxLength: 1024, - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "FacebookUrl", - schema: "Content", - table: "Socials", - type: "character varying(2048)", - maxLength: 2048, - nullable: true, - oldClrType: typeof(string), - oldType: "character varying(1024)", - oldMaxLength: 1024, - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "VideoUrlMain", - schema: "Content", - table: "PresentationInfos", - type: "character varying(2048)", - maxLength: 2048, - nullable: false, - oldClrType: typeof(string), - oldType: "character varying(1024)", - oldMaxLength: 1024); - - migrationBuilder.AlterColumn( - name: "VideoUrl", - schema: "Content", - table: "PresentationInfos", - type: "character varying(2048)", - maxLength: 2048, - nullable: false, - oldClrType: typeof(string), - oldType: "character varying(1024)", - oldMaxLength: 1024); - - migrationBuilder.AlterColumn( - name: "MainImageUrl", - schema: "Content", - table: "PresentationInfos", - type: "character varying(2048)", - maxLength: 2048, - nullable: false, - oldClrType: typeof(string), - oldType: "character varying(1024)", - oldMaxLength: 1024); - - migrationBuilder.AlterColumn( - name: "Image4Url", - schema: "Content", - table: "PresentationInfos", - type: "character varying(2048)", - maxLength: 2048, - nullable: false, - oldClrType: typeof(string), - oldType: "character varying(1024)", - oldMaxLength: 1024); - - migrationBuilder.AlterColumn( - name: "Image3Url", - schema: "Content", - table: "PresentationInfos", - type: "character varying(2048)", - maxLength: 2048, - nullable: false, - oldClrType: typeof(string), - oldType: "character varying(1024)", - oldMaxLength: 1024); - - migrationBuilder.AlterColumn( - name: "Image2Url", - schema: "Content", - table: "PresentationInfos", - type: "character varying(2048)", - maxLength: 2048, - nullable: false, - oldClrType: typeof(string), - oldType: "character varying(1024)", - oldMaxLength: 1024); - - migrationBuilder.AlterColumn( - name: "Image1Url", - schema: "Content", - table: "PresentationInfos", - type: "character varying(2048)", - maxLength: 2048, - nullable: false, - oldClrType: typeof(string), - oldType: "character varying(1024)", - oldMaxLength: 1024); - - migrationBuilder.AlterColumn( - name: "Logo", - schema: "Content", - table: "Images", - type: "character varying(2048)", - maxLength: 2048, - nullable: true, - oldClrType: typeof(string), - oldType: "character varying(255)", - oldMaxLength: 255, - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "Banner", - schema: "Content", - table: "Images", - type: "character varying(2048)", - maxLength: 2048, - nullable: true, - oldClrType: typeof(string), - oldType: "character varying(255)", - oldMaxLength: 255, - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "ThumbnailUrl", - schema: "Content", - table: "Contents", - type: "character varying(2048)", - maxLength: 2048, - nullable: true, - oldClrType: typeof(string), - oldType: "character varying(1024)", - oldMaxLength: 1024, - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "HtmlFileUrl", - schema: "Content", - table: "Contents", - type: "character varying(2048)", - maxLength: 2048, - nullable: true, - oldClrType: typeof(string), - oldType: "character varying(1024)", - oldMaxLength: 1024, - oldNullable: true); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.AlterColumn( - name: "YoutubeUrl", - schema: "Content", - table: "Socials", - type: "character varying(1024)", - maxLength: 1024, - nullable: true, - oldClrType: typeof(string), - oldType: "character varying(2048)", - oldMaxLength: 2048, - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "XUrl", - schema: "Content", - table: "Socials", - type: "character varying(1024)", - maxLength: 1024, - nullable: true, - oldClrType: typeof(string), - oldType: "character varying(2048)", - oldMaxLength: 2048, - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "WebsiteUrl", - schema: "Content", - table: "Socials", - type: "character varying(1024)", - maxLength: 1024, - nullable: true, - oldClrType: typeof(string), - oldType: "character varying(2048)", - oldMaxLength: 2048, - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "TikTokUrl", - schema: "Content", - table: "Socials", - type: "character varying(1024)", - maxLength: 1024, - nullable: true, - oldClrType: typeof(string), - oldType: "character varying(2048)", - oldMaxLength: 2048, - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "RedditUrl", - schema: "Content", - table: "Socials", - type: "character varying(1024)", - maxLength: 1024, - nullable: true, - oldClrType: typeof(string), - oldType: "character varying(2048)", - oldMaxLength: 2048, - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "LinkedInUrl", - schema: "Content", - table: "Socials", - type: "character varying(1024)", - maxLength: 1024, - nullable: true, - oldClrType: typeof(string), - oldType: "character varying(2048)", - oldMaxLength: 2048, - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "InstagramUrl", - schema: "Content", - table: "Socials", - type: "character varying(1024)", - maxLength: 1024, - nullable: true, - oldClrType: typeof(string), - oldType: "character varying(2048)", - oldMaxLength: 2048, - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "FacebookUrl", - schema: "Content", - table: "Socials", - type: "character varying(1024)", - maxLength: 1024, - nullable: true, - oldClrType: typeof(string), - oldType: "character varying(2048)", - oldMaxLength: 2048, - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "VideoUrlMain", - schema: "Content", - table: "PresentationInfos", - type: "character varying(1024)", - maxLength: 1024, - nullable: false, - oldClrType: typeof(string), - oldType: "character varying(2048)", - oldMaxLength: 2048); - - migrationBuilder.AlterColumn( - name: "VideoUrl", - schema: "Content", - table: "PresentationInfos", - type: "character varying(1024)", - maxLength: 1024, - nullable: false, - oldClrType: typeof(string), - oldType: "character varying(2048)", - oldMaxLength: 2048); - - migrationBuilder.AlterColumn( - name: "MainImageUrl", - schema: "Content", - table: "PresentationInfos", - type: "character varying(1024)", - maxLength: 1024, - nullable: false, - oldClrType: typeof(string), - oldType: "character varying(2048)", - oldMaxLength: 2048); - - migrationBuilder.AlterColumn( - name: "Image4Url", - schema: "Content", - table: "PresentationInfos", - type: "character varying(1024)", - maxLength: 1024, - nullable: false, - oldClrType: typeof(string), - oldType: "character varying(2048)", - oldMaxLength: 2048); - - migrationBuilder.AlterColumn( - name: "Image3Url", - schema: "Content", - table: "PresentationInfos", - type: "character varying(1024)", - maxLength: 1024, - nullable: false, - oldClrType: typeof(string), - oldType: "character varying(2048)", - oldMaxLength: 2048); - - migrationBuilder.AlterColumn( - name: "Image2Url", - schema: "Content", - table: "PresentationInfos", - type: "character varying(1024)", - maxLength: 1024, - nullable: false, - oldClrType: typeof(string), - oldType: "character varying(2048)", - oldMaxLength: 2048); - - migrationBuilder.AlterColumn( - name: "Image1Url", - schema: "Content", - table: "PresentationInfos", - type: "character varying(1024)", - maxLength: 1024, - nullable: false, - oldClrType: typeof(string), - oldType: "character varying(2048)", - oldMaxLength: 2048); - - migrationBuilder.AlterColumn( - name: "Logo", - schema: "Content", - table: "Images", - type: "character varying(255)", - maxLength: 255, - nullable: true, - oldClrType: typeof(string), - oldType: "character varying(2048)", - oldMaxLength: 2048, - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "Banner", - schema: "Content", - table: "Images", - type: "character varying(255)", - maxLength: 255, - nullable: true, - oldClrType: typeof(string), - oldType: "character varying(2048)", - oldMaxLength: 2048, - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "ThumbnailUrl", - schema: "Content", - table: "Contents", - type: "character varying(1024)", - maxLength: 1024, - nullable: true, - oldClrType: typeof(string), - oldType: "character varying(2048)", - oldMaxLength: 2048, - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "HtmlFileUrl", - schema: "Content", - table: "Contents", - type: "character varying(1024)", - maxLength: 1024, - nullable: true, - oldClrType: typeof(string), - oldType: "character varying(2048)", - oldMaxLength: 2048, - oldNullable: true); - } - } -} diff --git a/backend/src/Web/Features/Contents/Data/Migrations/20250415064846_AddsCreatorDeletionAudits.Designer.cs b/backend/src/Web/Features/Contents/Data/Migrations/20250415064846_AddsCreatorDeletionAudits.Designer.cs deleted file mode 100644 index 8d674cb..0000000 --- a/backend/src/Web/Features/Contents/Data/Migrations/20250415064846_AddsCreatorDeletionAudits.Designer.cs +++ /dev/null @@ -1,387 +0,0 @@ -// -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("20250415064846_AddsCreatorDeletionAudits")] - partial class AddsCreatorDeletionAudits - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasDefaultSchema("Content") - .HasAnnotation("ProductVersion", "9.0.3") - .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(2048) - .HasColumnType("character varying(2048)"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)"); - - b.PrimitiveCollection("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("DeletedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("DeletedBy") - .HasColumnType("uuid"); - - b.Property("IsDeleted") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("boolean") - .HasComputedColumnSql("\"DeletedAt\" IS NOT NULL", true); - - b.Property("SlugsId") - .HasColumnType("uuid"); - - b.Property("Title") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.Property("Verified") - .HasColumnType("boolean"); - - b.HasKey("Id"); - - b.HasIndex("SlugsId"); - - b.ToTable("Creators", "Content"); - }); - - modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.Slugs", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("Active") - .HasColumnType("boolean"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedBy") - .HasColumnType("uuid"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)"); - - b.Property("NormalizedName") - .IsRequired() - .ValueGeneratedOnAddOrUpdate() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasComputedColumnSql("LOWER( \"Content\".\"Slugs\".\"Name\")", true); - - b.Property("ReservedUntil") - .HasColumnType("timestamp with time zone"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedName") - .IsUnique(); - - b.ToTable("Slugs", "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.HasOne("Hutopy.Web.Features.Contents.Data.Slugs", "Slugs") - .WithMany() - .HasForeignKey("SlugsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.OwnsOne("Hutopy.Web.Features.Contents.Data.Images", "Images", b1 => - { - b1.Property("CreatorId") - .HasColumnType("uuid"); - - b1.Property("Banner") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("Logo") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - 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(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("Image2Url") - .IsRequired() - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("Image3Url") - .IsRequired() - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("Image4Url") - .IsRequired() - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - 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(2048) - .HasColumnType("character varying(2048)"); - - 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(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("VideoUrlMain") - .IsRequired() - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - 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(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("InstagramUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("LinkedInUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("RedditUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("TikTokUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("WebsiteUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("XUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("YoutubeUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.HasKey("CreatorId"); - - b1.ToTable("Socials", "Content"); - - b1.WithOwner() - .HasForeignKey("CreatorId"); - }); - - b.Navigation("Images") - .IsRequired(); - - b.Navigation("PresentationInfos") - .IsRequired(); - - b.Navigation("Slugs"); - - b.Navigation("Socials") - .IsRequired(); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/backend/src/Web/Features/Contents/Data/Migrations/20250415064846_AddsCreatorDeletionAudits.cs b/backend/src/Web/Features/Contents/Data/Migrations/20250415064846_AddsCreatorDeletionAudits.cs deleted file mode 100644 index da22f29..0000000 --- a/backend/src/Web/Features/Contents/Data/Migrations/20250415064846_AddsCreatorDeletionAudits.cs +++ /dev/null @@ -1,57 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Hutopy.Web.Features.Contents.Data.Migrations -{ - /// - public partial class AddsCreatorDeletionAudits : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "DeletedAt", - schema: "Content", - table: "Creators", - type: "timestamp with time zone", - nullable: true); - - migrationBuilder.AddColumn( - name: "DeletedBy", - schema: "Content", - table: "Creators", - type: "uuid", - nullable: true); - - migrationBuilder.AddColumn( - name: "IsDeleted", - schema: "Content", - table: "Creators", - type: "boolean", - nullable: false, - computedColumnSql: "\"DeletedAt\" IS NOT NULL", - stored: true); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "IsDeleted", - schema: "Content", - table: "Creators"); - - migrationBuilder.DropColumn( - name: "DeletedAt", - schema: "Content", - table: "Creators"); - - migrationBuilder.DropColumn( - name: "DeletedBy", - schema: "Content", - table: "Creators"); - } - } -} diff --git a/backend/src/Web/Features/Contents/Data/Migrations/20250415065154_AddsDeleteToContentQueryFilter.Designer.cs b/backend/src/Web/Features/Contents/Data/Migrations/20250415065154_AddsDeleteToContentQueryFilter.Designer.cs deleted file mode 100644 index 4cd27f4..0000000 --- a/backend/src/Web/Features/Contents/Data/Migrations/20250415065154_AddsDeleteToContentQueryFilter.Designer.cs +++ /dev/null @@ -1,385 +0,0 @@ -// -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("20250415065154_AddsDeleteToContentQueryFilter")] - partial class AddsDeleteToContentQueryFilter - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasDefaultSchema("Content") - .HasAnnotation("ProductVersion", "9.0.3") - .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(2048) - .HasColumnType("character varying(2048)"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)"); - - b.PrimitiveCollection("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("DeletedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("DeletedBy") - .HasColumnType("uuid"); - - b.Property("IsDeleted") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("boolean") - .HasComputedColumnSql("\"DeletedAt\" IS NOT NULL", true); - - b.Property("SlugsId") - .HasColumnType("uuid"); - - b.Property("Title") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.Property("Verified") - .HasColumnType("boolean"); - - b.HasKey("Id"); - - b.HasIndex("SlugsId"); - - b.ToTable("Creators", "Content"); - }); - - modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.Slugs", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("Active") - .HasColumnType("boolean"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedBy") - .HasColumnType("uuid"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)"); - - b.Property("NormalizedName") - .IsRequired() - .ValueGeneratedOnAddOrUpdate() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasComputedColumnSql("LOWER( \"Content\".\"Slugs\".\"Name\")", true); - - b.Property("ReservedUntil") - .HasColumnType("timestamp with time zone"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedName") - .IsUnique(); - - b.ToTable("Slugs", "Content"); - }); - - modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.Content", b => - { - b.HasOne("Hutopy.Web.Features.Contents.Data.Creator", "Creator") - .WithMany() - .HasForeignKey("CreatedBy"); - - 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.HasOne("Hutopy.Web.Features.Contents.Data.Slugs", "Slugs") - .WithMany() - .HasForeignKey("SlugsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.OwnsOne("Hutopy.Web.Features.Contents.Data.Images", "Images", b1 => - { - b1.Property("CreatorId") - .HasColumnType("uuid"); - - b1.Property("Banner") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("Logo") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - 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(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("Image2Url") - .IsRequired() - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("Image3Url") - .IsRequired() - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("Image4Url") - .IsRequired() - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - 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(2048) - .HasColumnType("character varying(2048)"); - - 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(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("VideoUrlMain") - .IsRequired() - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - 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(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("InstagramUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("LinkedInUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("RedditUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("TikTokUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("WebsiteUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("XUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("YoutubeUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.HasKey("CreatorId"); - - b1.ToTable("Socials", "Content"); - - b1.WithOwner() - .HasForeignKey("CreatorId"); - }); - - b.Navigation("Images") - .IsRequired(); - - b.Navigation("PresentationInfos") - .IsRequired(); - - b.Navigation("Slugs"); - - b.Navigation("Socials") - .IsRequired(); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/backend/src/Web/Features/Contents/Data/Migrations/20250415065154_AddsDeleteToContentQueryFilter.cs b/backend/src/Web/Features/Contents/Data/Migrations/20250415065154_AddsDeleteToContentQueryFilter.cs deleted file mode 100644 index 1ea43a6..0000000 --- a/backend/src/Web/Features/Contents/Data/Migrations/20250415065154_AddsDeleteToContentQueryFilter.cs +++ /dev/null @@ -1,47 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Hutopy.Web.Features.Contents.Data.Migrations -{ - /// - public partial class AddsDeleteToContentQueryFilter : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropForeignKey( - name: "FK_Contents_Creators_CreatedBy", - schema: "Content", - table: "Contents"); - - migrationBuilder.AddForeignKey( - name: "FK_Contents_Creators_CreatedBy", - schema: "Content", - table: "Contents", - column: "CreatedBy", - principalSchema: "Content", - principalTable: "Creators", - principalColumn: "Id"); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropForeignKey( - name: "FK_Contents_Creators_CreatedBy", - schema: "Content", - table: "Contents"); - - migrationBuilder.AddForeignKey( - name: "FK_Contents_Creators_CreatedBy", - schema: "Content", - table: "Contents", - column: "CreatedBy", - principalSchema: "Content", - principalTable: "Creators", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - } - } -} diff --git a/backend/src/Web/Features/Contents/Data/Migrations/20250415071053_SplitSlugFromCreator.Designer.cs b/backend/src/Web/Features/Contents/Data/Migrations/20250415071053_SplitSlugFromCreator.Designer.cs deleted file mode 100644 index a252da1..0000000 --- a/backend/src/Web/Features/Contents/Data/Migrations/20250415071053_SplitSlugFromCreator.Designer.cs +++ /dev/null @@ -1,382 +0,0 @@ -// -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("20250415071053_SplitSlugFromCreator")] - partial class SplitSlugFromCreator - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasDefaultSchema("Content") - .HasAnnotation("ProductVersion", "9.0.3") - .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(2048) - .HasColumnType("character varying(2048)"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)"); - - b.PrimitiveCollection("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("DeletedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("DeletedBy") - .HasColumnType("uuid"); - - b.Property("IsDeleted") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("boolean") - .HasComputedColumnSql("\"DeletedAt\" IS NOT NULL", true); - - b.Property("Name") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)"); - - b.Property("Title") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.Property("Verified") - .HasColumnType("boolean"); - - b.HasKey("Id"); - - b.ToTable("Creators", "Content"); - }); - - modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.Slugs", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedBy") - .HasColumnType("uuid"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)"); - - b.Property("NormalizedName") - .IsRequired() - .ValueGeneratedOnAddOrUpdate() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasComputedColumnSql("LOWER( \"Content\".\"Slugs\".\"Name\")", true); - - b.Property("ReservedUntil") - .HasColumnType("timestamp with time zone"); - - b.Property("UsedBy") - .HasColumnType("uuid"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedName") - .IsUnique(); - - b.ToTable("Slugs", "Content"); - }); - - modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.Content", b => - { - b.HasOne("Hutopy.Web.Features.Contents.Data.Creator", "Creator") - .WithMany() - .HasForeignKey("CreatedBy"); - - 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.Images", "Images", b1 => - { - b1.Property("CreatorId") - .HasColumnType("uuid"); - - b1.Property("Banner") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("Logo") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - 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(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("Image2Url") - .IsRequired() - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("Image3Url") - .IsRequired() - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("Image4Url") - .IsRequired() - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - 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(2048) - .HasColumnType("character varying(2048)"); - - 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(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("VideoUrlMain") - .IsRequired() - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - 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(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("InstagramUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("LinkedInUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("RedditUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("TikTokUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("WebsiteUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("XUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("YoutubeUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.HasKey("CreatorId"); - - b1.ToTable("Socials", "Content"); - - b1.WithOwner() - .HasForeignKey("CreatorId"); - }); - - b.Navigation("Images") - .IsRequired(); - - b.Navigation("PresentationInfos") - .IsRequired(); - - b.Navigation("Socials") - .IsRequired(); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/backend/src/Web/Features/Contents/Data/Migrations/20250415071053_SplitSlugFromCreator.cs b/backend/src/Web/Features/Contents/Data/Migrations/20250415071053_SplitSlugFromCreator.cs deleted file mode 100644 index 4ecee0a..0000000 --- a/backend/src/Web/Features/Contents/Data/Migrations/20250415071053_SplitSlugFromCreator.cs +++ /dev/null @@ -1,111 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Hutopy.Web.Features.Contents.Data.Migrations -{ - /// - public partial class SplitSlugFromCreator : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropForeignKey( - name: "FK_Creators_Slugs_SlugsId", - schema: "Content", - table: "Creators"); - - migrationBuilder.DropIndex( - name: "IX_Creators_SlugsId", - schema: "Content", - table: "Creators"); - - migrationBuilder.DropColumn( - name: "Active", - schema: "Content", - table: "Slugs"); - - migrationBuilder.DropColumn( - name: "SlugsId", - schema: "Content", - table: "Creators"); - - migrationBuilder.AddColumn( - name: "UsedBy", - schema: "Content", - table: "Slugs", - type: "uuid", - nullable: true); - - migrationBuilder.AddColumn( - name: "Name", - schema: "Content", - table: "Creators", - type: "character varying(255)", - maxLength: 255, - nullable: false, - defaultValue: ""); - - migrationBuilder.AddColumn( - name: "Slug", - schema: "Content", - table: "Creators", - type: "character varying(128)", - maxLength: 128, - nullable: false, - defaultValue: ""); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "UsedBy", - schema: "Content", - table: "Slugs"); - - migrationBuilder.DropColumn( - name: "Name", - schema: "Content", - table: "Creators"); - - migrationBuilder.DropColumn( - name: "Slug", - schema: "Content", - table: "Creators"); - - migrationBuilder.AddColumn( - name: "Active", - schema: "Content", - table: "Slugs", - type: "boolean", - nullable: false, - defaultValue: false); - - migrationBuilder.AddColumn( - name: "SlugsId", - schema: "Content", - table: "Creators", - type: "uuid", - nullable: false, - defaultValue: new Guid("00000000-0000-0000-0000-000000000000")); - - migrationBuilder.CreateIndex( - name: "IX_Creators_SlugsId", - schema: "Content", - table: "Creators", - column: "SlugsId"); - - migrationBuilder.AddForeignKey( - name: "FK_Creators_Slugs_SlugsId", - schema: "Content", - table: "Creators", - column: "SlugsId", - principalSchema: "Content", - principalTable: "Slugs", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - } - } -} diff --git a/backend/src/Web/Features/Contents/Data/Migrations/20250415080713_AddContentCreatorId.Designer.cs b/backend/src/Web/Features/Contents/Data/Migrations/20250415080713_AddContentCreatorId.Designer.cs deleted file mode 100644 index f9cb639..0000000 --- a/backend/src/Web/Features/Contents/Data/Migrations/20250415080713_AddContentCreatorId.Designer.cs +++ /dev/null @@ -1,385 +0,0 @@ -// -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("20250415080713_AddContentCreatorId")] - partial class AddContentCreatorId - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasDefaultSchema("Content") - .HasAnnotation("ProductVersion", "9.0.3") - .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("CreatorId") - .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(2048) - .HasColumnType("character varying(2048)"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)"); - - b.PrimitiveCollection("Urls") - .HasColumnType("text[]"); - - b.HasKey("Id"); - - b.HasIndex("CreatorId"); - - 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("DeletedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("DeletedBy") - .HasColumnType("uuid"); - - b.Property("IsDeleted") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("boolean") - .HasComputedColumnSql("\"DeletedAt\" IS NOT NULL", true); - - b.Property("Name") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)"); - - b.Property("Title") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.Property("Verified") - .HasColumnType("boolean"); - - b.HasKey("Id"); - - b.ToTable("Creators", "Content"); - }); - - modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.Slugs", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedBy") - .HasColumnType("uuid"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)"); - - b.Property("NormalizedName") - .IsRequired() - .ValueGeneratedOnAddOrUpdate() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasComputedColumnSql("LOWER( \"Content\".\"Slugs\".\"Name\")", true); - - b.Property("ReservedUntil") - .HasColumnType("timestamp with time zone"); - - b.Property("UsedBy") - .HasColumnType("uuid"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedName") - .IsUnique(); - - b.ToTable("Slugs", "Content"); - }); - - modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.Content", b => - { - b.HasOne("Hutopy.Web.Features.Contents.Data.Creator", "Creator") - .WithMany() - .HasForeignKey("CreatorId"); - - 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.Images", "Images", b1 => - { - b1.Property("CreatorId") - .HasColumnType("uuid"); - - b1.Property("Banner") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("Logo") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - 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(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("Image2Url") - .IsRequired() - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("Image3Url") - .IsRequired() - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("Image4Url") - .IsRequired() - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - 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(2048) - .HasColumnType("character varying(2048)"); - - 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(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("VideoUrlMain") - .IsRequired() - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - 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(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("InstagramUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("LinkedInUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("RedditUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("TikTokUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("WebsiteUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("XUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("YoutubeUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.HasKey("CreatorId"); - - b1.ToTable("Socials", "Content"); - - b1.WithOwner() - .HasForeignKey("CreatorId"); - }); - - b.Navigation("Images") - .IsRequired(); - - b.Navigation("PresentationInfos") - .IsRequired(); - - b.Navigation("Socials") - .IsRequired(); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/backend/src/Web/Features/Contents/Data/Migrations/20250415080713_AddContentCreatorId.cs b/backend/src/Web/Features/Contents/Data/Migrations/20250415080713_AddContentCreatorId.cs deleted file mode 100644 index 243aff5..0000000 --- a/backend/src/Web/Features/Contents/Data/Migrations/20250415080713_AddContentCreatorId.cs +++ /dev/null @@ -1,82 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Hutopy.Web.Features.Contents.Data.Migrations -{ - /// - public partial class AddContentCreatorId : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropForeignKey( - name: "FK_Contents_Creators_CreatedBy", - schema: "Content", - table: "Contents"); - - migrationBuilder.DropIndex( - name: "IX_Contents_CreatedBy", - schema: "Content", - table: "Contents"); - - migrationBuilder.AddColumn( - name: "CreatorId", - schema: "Content", - table: "Contents", - type: "uuid", - nullable: false, - defaultValue: new Guid("00000000-0000-0000-0000-000000000000")); - - migrationBuilder.CreateIndex( - name: "IX_Contents_CreatorId", - schema: "Content", - table: "Contents", - column: "CreatorId"); - - migrationBuilder.AddForeignKey( - name: "FK_Contents_Creators_CreatorId", - schema: "Content", - table: "Contents", - column: "CreatorId", - principalSchema: "Content", - principalTable: "Creators", - principalColumn: "Id"); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropForeignKey( - name: "FK_Contents_Creators_CreatorId", - schema: "Content", - table: "Contents"); - - migrationBuilder.DropIndex( - name: "IX_Contents_CreatorId", - schema: "Content", - table: "Contents"); - - migrationBuilder.DropColumn( - name: "CreatorId", - schema: "Content", - table: "Contents"); - - migrationBuilder.CreateIndex( - name: "IX_Contents_CreatedBy", - schema: "Content", - table: "Contents", - column: "CreatedBy"); - - migrationBuilder.AddForeignKey( - name: "FK_Contents_Creators_CreatedBy", - schema: "Content", - table: "Contents", - column: "CreatedBy", - principalSchema: "Content", - principalTable: "Creators", - principalColumn: "Id"); - } - } -} diff --git a/backend/src/Web/Features/Contents/Data/Migrations/20250423153323_AddPresentation.cs b/backend/src/Web/Features/Contents/Data/Migrations/20250423153323_AddPresentation.cs deleted file mode 100644 index cc8676a..0000000 --- a/backend/src/Web/Features/Contents/Data/Migrations/20250423153323_AddPresentation.cs +++ /dev/null @@ -1,137 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Hutopy.Web.Features.Contents.Data.Migrations -{ - /// - public partial class AddPresentation : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "Images", - schema: "Content"); - - migrationBuilder.DropTable( - name: "PresentationInfos", - schema: "Content"); - - migrationBuilder.AddColumn( - name: "BannerUrl", - schema: "Content", - table: "Creators", - type: "character varying(2048)", - maxLength: 2048, - nullable: true); - - migrationBuilder.AddColumn( - name: "PortraitUrl", - schema: "Content", - table: "Creators", - type: "character varying(2048)", - maxLength: 2048, - nullable: true); - - migrationBuilder.CreateTable( - name: "Presentation", - schema: "Content", - columns: table => new - { - CreatorId = table.Column(type: "uuid", nullable: false), - Description = table.Column(type: "character varying(2000)", maxLength: 2000, nullable: false), - VideoUrl = table.Column(type: "character varying(2048)", maxLength: 2048, nullable: true), - PhoneNumber = table.Column(type: "character varying(255)", maxLength: 255, nullable: true), - Email = table.Column(type: "character varying(255)", maxLength: 255, nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Presentation", x => x.CreatorId); - table.ForeignKey( - name: "FK_Presentation_Creators_CreatorId", - column: x => x.CreatorId, - principalSchema: "Content", - principalTable: "Creators", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "Presentation", - schema: "Content"); - - migrationBuilder.DropColumn( - name: "BannerUrl", - schema: "Content", - table: "Creators"); - - migrationBuilder.DropColumn( - name: "PortraitUrl", - schema: "Content", - table: "Creators"); - - migrationBuilder.CreateTable( - name: "Images", - schema: "Content", - columns: table => new - { - CreatorId = table.Column(type: "uuid", nullable: false), - Banner = table.Column(type: "character varying(2048)", maxLength: 2048, nullable: true), - Logo = table.Column(type: "character varying(2048)", maxLength: 2048, nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Images", x => x.CreatorId); - table.ForeignKey( - name: "FK_Images_Creators_CreatorId", - column: x => x.CreatorId, - principalSchema: "Content", - principalTable: "Creators", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "PresentationInfos", - schema: "Content", - columns: table => new - { - CreatorId = table.Column(type: "uuid", nullable: false), - Email = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), - Image1Url = table.Column(type: "character varying(2048)", maxLength: 2048, nullable: false), - Image2Url = table.Column(type: "character varying(2048)", maxLength: 2048, nullable: false), - Image3Url = table.Column(type: "character varying(2048)", maxLength: 2048, nullable: false), - Image4Url = table.Column(type: "character varying(2048)", maxLength: 2048, nullable: false), - ImagesSubtitle = table.Column(type: "character varying(2000)", maxLength: 2000, nullable: false), - ImagesText = table.Column(type: "character varying(10000)", maxLength: 10000, nullable: false), - MainImageText = table.Column(type: "character varying(10000)", maxLength: 10000, nullable: false), - MainImageUrl = table.Column(type: "character varying(2048)", maxLength: 2048, nullable: false), - MainVideoText = table.Column(type: "character varying(10000)", maxLength: 10000, nullable: false), - PhoneNumber = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), - Title = table.Column(type: "character varying(2000)", maxLength: 2000, nullable: false), - VideoSubtitle = table.Column(type: "character varying(2000)", maxLength: 2000, nullable: false), - VideoSubtitleMain = table.Column(type: "character varying(2000)", maxLength: 2000, nullable: false), - VideoText = table.Column(type: "character varying(10000)", maxLength: 10000, nullable: false), - VideoUrl = table.Column(type: "character varying(2048)", maxLength: 2048, nullable: false), - VideoUrlMain = table.Column(type: "character varying(2048)", maxLength: 2048, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_PresentationInfos", x => x.CreatorId); - table.ForeignKey( - name: "FK_PresentationInfos_Creators_CreatorId", - column: x => x.CreatorId, - principalSchema: "Content", - principalTable: "Creators", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - } - } -} diff --git a/backend/src/Web/Features/Contents/Data/Migrations/20250423173651_AddAlbumAndPhotos.Designer.cs b/backend/src/Web/Features/Contents/Data/Migrations/20250423173651_AddAlbumAndPhotos.Designer.cs deleted file mode 100644 index 88afe4d..0000000 --- a/backend/src/Web/Features/Contents/Data/Migrations/20250423173651_AddAlbumAndPhotos.Designer.cs +++ /dev/null @@ -1,407 +0,0 @@ -// -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("20250423173651_AddAlbumAndPhotos")] - partial class AddAlbumAndPhotos - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasDefaultSchema("Content") - .HasAnnotation("ProductVersion", "9.0.3") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.Album", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("CoverPhotoUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - 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") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)"); - - b.Property("IsDeleted") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("boolean") - .HasComputedColumnSql("\"DeletedAt\" IS NOT NULL", true); - - b.Property("Title") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.HasKey("Id"); - - b.ToTable("Albums", "Content"); - }); - - modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.AlbumPhoto", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("AlbumId") - .HasColumnType("uuid"); - - b.Property("Caption") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - 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("IsDeleted") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("boolean") - .HasComputedColumnSql("\"DeletedAt\" IS NOT NULL", true); - - b.Property("Order") - .HasColumnType("integer"); - - b.Property("PhotoUrl") - .IsRequired() - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b.HasKey("Id"); - - b.HasIndex("AlbumId"); - - b.ToTable("AlbumPhotos", "Content"); - }); - - 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("CreatorId") - .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(2048) - .HasColumnType("character varying(2048)"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)"); - - b.PrimitiveCollection("Urls") - .HasColumnType("text[]"); - - b.HasKey("Id"); - - b.HasIndex("CreatorId"); - - 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("BannerUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedBy") - .HasColumnType("uuid"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("DeletedBy") - .HasColumnType("uuid"); - - b.Property("IsDeleted") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("boolean") - .HasComputedColumnSql("\"DeletedAt\" IS NOT NULL", true); - - b.Property("Name") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.Property("PortraitUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)"); - - b.Property("Title") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.Property("Verified") - .HasColumnType("boolean"); - - b.HasKey("Id"); - - b.ToTable("Creators", "Content"); - }); - - modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.Slugs", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedBy") - .HasColumnType("uuid"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)"); - - b.Property("NormalizedName") - .IsRequired() - .ValueGeneratedOnAddOrUpdate() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasComputedColumnSql("LOWER( \"Content\".\"Slugs\".\"Name\")", true); - - b.Property("ReservedUntil") - .HasColumnType("timestamp with time zone"); - - b.Property("UsedBy") - .HasColumnType("uuid"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedName") - .IsUnique(); - - b.ToTable("Slugs", "Content"); - }); - - modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.AlbumPhoto", b => - { - b.HasOne("Hutopy.Web.Features.Contents.Data.Album", "Album") - .WithMany("Photos") - .HasForeignKey("AlbumId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Album"); - }); - - modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.Content", b => - { - b.HasOne("Hutopy.Web.Features.Contents.Data.Creator", "Creator") - .WithMany() - .HasForeignKey("CreatorId"); - - 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.Presentation", "Presentation", b1 => - { - b1.Property("CreatorId") - .HasColumnType("uuid"); - - b1.Property("Description") - .IsRequired() - .HasMaxLength(2000) - .HasColumnType("character varying(2000)"); - - b1.Property("Email") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b1.Property("PhoneNumber") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b1.Property("VideoUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.HasKey("CreatorId"); - - b1.ToTable("Presentation", "Content"); - - b1.WithOwner() - .HasForeignKey("CreatorId"); - }); - - b.OwnsOne("Hutopy.Web.Features.Contents.Data.Socials", "Socials", b1 => - { - b1.Property("CreatorId") - .HasColumnType("uuid"); - - b1.Property("FacebookUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("InstagramUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("LinkedInUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("RedditUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("TikTokUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("WebsiteUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("XUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("YoutubeUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.HasKey("CreatorId"); - - b1.ToTable("Socials", "Content"); - - b1.WithOwner() - .HasForeignKey("CreatorId"); - }); - - b.Navigation("Presentation") - .IsRequired(); - - b.Navigation("Socials") - .IsRequired(); - }); - - modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.Album", b => - { - b.Navigation("Photos"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/backend/src/Web/Features/Contents/Data/Migrations/20250423180519_SimplifyAlbums.Designer.cs b/backend/src/Web/Features/Contents/Data/Migrations/20250423180519_SimplifyAlbums.Designer.cs deleted file mode 100644 index 02647b8..0000000 --- a/backend/src/Web/Features/Contents/Data/Migrations/20250423180519_SimplifyAlbums.Designer.cs +++ /dev/null @@ -1,399 +0,0 @@ -// -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("20250423180519_SimplifyAlbums")] - partial class SimplifyAlbums - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasDefaultSchema("Content") - .HasAnnotation("ProductVersion", "9.0.3") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.Album", 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("IsDeleted") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("boolean") - .HasComputedColumnSql("\"DeletedAt\" IS NOT NULL", true); - - b.Property("Title") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.HasKey("Id"); - - b.ToTable("Albums", "Content"); - }); - - modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.AlbumPhoto", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("AlbumId") - .HasColumnType("uuid"); - - b.Property("Caption") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - 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("IsDeleted") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("boolean") - .HasComputedColumnSql("\"DeletedAt\" IS NOT NULL", true); - - b.Property("Order") - .HasColumnType("integer"); - - b.Property("PhotoUrl") - .IsRequired() - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b.HasKey("Id"); - - b.HasIndex("AlbumId"); - - b.ToTable("AlbumPhotos", "Content"); - }); - - 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("CreatorId") - .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(2048) - .HasColumnType("character varying(2048)"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)"); - - b.PrimitiveCollection("Urls") - .HasColumnType("text[]"); - - b.HasKey("Id"); - - b.HasIndex("CreatorId"); - - 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("BannerUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedBy") - .HasColumnType("uuid"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("DeletedBy") - .HasColumnType("uuid"); - - b.Property("IsDeleted") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("boolean") - .HasComputedColumnSql("\"DeletedAt\" IS NOT NULL", true); - - b.Property("Name") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.Property("PortraitUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)"); - - b.Property("Title") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.Property("Verified") - .HasColumnType("boolean"); - - b.HasKey("Id"); - - b.ToTable("Creators", "Content"); - }); - - modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.Slugs", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedBy") - .HasColumnType("uuid"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)"); - - b.Property("NormalizedName") - .IsRequired() - .ValueGeneratedOnAddOrUpdate() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasComputedColumnSql("LOWER( \"Content\".\"Slugs\".\"Name\")", true); - - b.Property("ReservedUntil") - .HasColumnType("timestamp with time zone"); - - b.Property("UsedBy") - .HasColumnType("uuid"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedName") - .IsUnique(); - - b.ToTable("Slugs", "Content"); - }); - - modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.AlbumPhoto", b => - { - b.HasOne("Hutopy.Web.Features.Contents.Data.Album", "Album") - .WithMany("Photos") - .HasForeignKey("AlbumId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Album"); - }); - - modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.Content", b => - { - b.HasOne("Hutopy.Web.Features.Contents.Data.Creator", "Creator") - .WithMany() - .HasForeignKey("CreatorId"); - - 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.Presentation", "Presentation", b1 => - { - b1.Property("CreatorId") - .HasColumnType("uuid"); - - b1.Property("Description") - .IsRequired() - .HasMaxLength(2000) - .HasColumnType("character varying(2000)"); - - b1.Property("Email") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b1.Property("PhoneNumber") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b1.Property("VideoUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.HasKey("CreatorId"); - - b1.ToTable("Presentation", "Content"); - - b1.WithOwner() - .HasForeignKey("CreatorId"); - }); - - b.OwnsOne("Hutopy.Web.Features.Contents.Data.Socials", "Socials", b1 => - { - b1.Property("CreatorId") - .HasColumnType("uuid"); - - b1.Property("FacebookUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("InstagramUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("LinkedInUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("RedditUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("TikTokUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("WebsiteUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("XUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("YoutubeUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.HasKey("CreatorId"); - - b1.ToTable("Socials", "Content"); - - b1.WithOwner() - .HasForeignKey("CreatorId"); - }); - - b.Navigation("Presentation") - .IsRequired(); - - b.Navigation("Socials") - .IsRequired(); - }); - - modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.Album", b => - { - b.Navigation("Photos"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/backend/src/Web/Features/Contents/Data/Migrations/20250423180519_SimplifyAlbums.cs b/backend/src/Web/Features/Contents/Data/Migrations/20250423180519_SimplifyAlbums.cs deleted file mode 100644 index 67633df..0000000 --- a/backend/src/Web/Features/Contents/Data/Migrations/20250423180519_SimplifyAlbums.cs +++ /dev/null @@ -1,44 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Hutopy.Web.Features.Contents.Data.Migrations -{ - /// - public partial class SimplifyAlbums : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "CoverPhotoUrl", - schema: "Content", - table: "Albums"); - - migrationBuilder.DropColumn( - name: "Description", - schema: "Content", - table: "Albums"); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "CoverPhotoUrl", - schema: "Content", - table: "Albums", - type: "character varying(2048)", - maxLength: 2048, - nullable: true); - - migrationBuilder.AddColumn( - name: "Description", - schema: "Content", - table: "Albums", - type: "character varying(1000)", - maxLength: 1000, - nullable: true); - } - } -} diff --git a/backend/src/Web/Features/Contents/Data/Migrations/20250425043506_CreatorDescriptionToText.Designer.cs b/backend/src/Web/Features/Contents/Data/Migrations/20250425043506_CreatorDescriptionToText.Designer.cs deleted file mode 100644 index 0e9632a..0000000 --- a/backend/src/Web/Features/Contents/Data/Migrations/20250425043506_CreatorDescriptionToText.Designer.cs +++ /dev/null @@ -1,398 +0,0 @@ -// -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("20250425043506_CreatorDescriptionToText")] - partial class CreatorDescriptionToText - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasDefaultSchema("Content") - .HasAnnotation("ProductVersion", "9.0.3") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.Album", 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("IsDeleted") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("boolean") - .HasComputedColumnSql("\"DeletedAt\" IS NOT NULL", true); - - b.Property("Title") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.HasKey("Id"); - - b.ToTable("Albums", "Content"); - }); - - modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.AlbumPhoto", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("AlbumId") - .HasColumnType("uuid"); - - b.Property("Caption") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - 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("IsDeleted") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("boolean") - .HasComputedColumnSql("\"DeletedAt\" IS NOT NULL", true); - - b.Property("Order") - .HasColumnType("integer"); - - b.Property("PhotoUrl") - .IsRequired() - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b.HasKey("Id"); - - b.HasIndex("AlbumId"); - - b.ToTable("AlbumPhotos", "Content"); - }); - - 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("CreatorId") - .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(2048) - .HasColumnType("character varying(2048)"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)"); - - b.PrimitiveCollection("Urls") - .HasColumnType("text[]"); - - b.HasKey("Id"); - - b.HasIndex("CreatorId"); - - 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("BannerUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedBy") - .HasColumnType("uuid"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("DeletedBy") - .HasColumnType("uuid"); - - b.Property("IsDeleted") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("boolean") - .HasComputedColumnSql("\"DeletedAt\" IS NOT NULL", true); - - b.Property("Name") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.Property("PortraitUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)"); - - b.Property("Title") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.Property("Verified") - .HasColumnType("boolean"); - - b.HasKey("Id"); - - b.ToTable("Creators", "Content"); - }); - - modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.Slugs", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedBy") - .HasColumnType("uuid"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)"); - - b.Property("NormalizedName") - .IsRequired() - .ValueGeneratedOnAddOrUpdate() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasComputedColumnSql("LOWER( \"Content\".\"Slugs\".\"Name\")", true); - - b.Property("ReservedUntil") - .HasColumnType("timestamp with time zone"); - - b.Property("UsedBy") - .HasColumnType("uuid"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedName") - .IsUnique(); - - b.ToTable("Slugs", "Content"); - }); - - modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.AlbumPhoto", b => - { - b.HasOne("Hutopy.Web.Features.Contents.Data.Album", "Album") - .WithMany("Photos") - .HasForeignKey("AlbumId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Album"); - }); - - modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.Content", b => - { - b.HasOne("Hutopy.Web.Features.Contents.Data.Creator", "Creator") - .WithMany() - .HasForeignKey("CreatorId"); - - 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.Presentation", "Presentation", b1 => - { - b1.Property("CreatorId") - .HasColumnType("uuid"); - - b1.Property("Description") - .IsRequired() - .HasColumnType("text"); - - b1.Property("Email") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b1.Property("PhoneNumber") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b1.Property("VideoUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.HasKey("CreatorId"); - - b1.ToTable("Presentation", "Content"); - - b1.WithOwner() - .HasForeignKey("CreatorId"); - }); - - b.OwnsOne("Hutopy.Web.Features.Contents.Data.Socials", "Socials", b1 => - { - b1.Property("CreatorId") - .HasColumnType("uuid"); - - b1.Property("FacebookUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("InstagramUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("LinkedInUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("RedditUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("TikTokUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("WebsiteUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("XUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("YoutubeUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.HasKey("CreatorId"); - - b1.ToTable("Socials", "Content"); - - b1.WithOwner() - .HasForeignKey("CreatorId"); - }); - - b.Navigation("Presentation") - .IsRequired(); - - b.Navigation("Socials") - .IsRequired(); - }); - - modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.Album", b => - { - b.Navigation("Photos"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/backend/src/Web/Features/Contents/Data/Migrations/20250425043506_CreatorDescriptionToText.cs b/backend/src/Web/Features/Contents/Data/Migrations/20250425043506_CreatorDescriptionToText.cs deleted file mode 100644 index 2ae16f6..0000000 --- a/backend/src/Web/Features/Contents/Data/Migrations/20250425043506_CreatorDescriptionToText.cs +++ /dev/null @@ -1,38 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Hutopy.Web.Features.Contents.Data.Migrations -{ - /// - public partial class CreatorDescriptionToText : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AlterColumn( - name: "Description", - schema: "Content", - table: "Presentation", - type: "text", - nullable: false, - oldClrType: typeof(string), - oldType: "character varying(2000)", - oldMaxLength: 2000); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.AlterColumn( - name: "Description", - schema: "Content", - table: "Presentation", - type: "character varying(2000)", - maxLength: 2000, - nullable: false, - oldClrType: typeof(string), - oldType: "text"); - } - } -} diff --git a/backend/src/Web/Features/Contents/Data/Migrations/20250526174825_AddThumbnailUrlToPhoto.Designer.cs b/backend/src/Web/Features/Contents/Data/Migrations/20250526174825_AddThumbnailUrlToPhoto.Designer.cs deleted file mode 100644 index a06f704..0000000 --- a/backend/src/Web/Features/Contents/Data/Migrations/20250526174825_AddThumbnailUrlToPhoto.Designer.cs +++ /dev/null @@ -1,403 +0,0 @@ -// -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("20250526174825_AddThumbnailUrlToPhoto")] - partial class AddThumbnailUrlToPhoto - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasDefaultSchema("Content") - .HasAnnotation("ProductVersion", "9.0.3") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.Album", 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("IsDeleted") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("boolean") - .HasComputedColumnSql("\"DeletedAt\" IS NOT NULL", true); - - b.Property("Title") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.HasKey("Id"); - - b.ToTable("Albums", "Content"); - }); - - modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.AlbumPhoto", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("AlbumId") - .HasColumnType("uuid"); - - b.Property("Caption") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - 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("IsDeleted") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("boolean") - .HasComputedColumnSql("\"DeletedAt\" IS NOT NULL", true); - - b.Property("Order") - .HasColumnType("integer"); - - b.Property("OriginalUrl") - .IsRequired() - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b.Property("ThumbnailUrl") - .IsRequired() - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b.HasKey("Id"); - - b.HasIndex("AlbumId"); - - b.ToTable("AlbumPhotos", "Content"); - }); - - 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("CreatorId") - .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(2048) - .HasColumnType("character varying(2048)"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)"); - - b.PrimitiveCollection("Urls") - .HasColumnType("text[]"); - - b.HasKey("Id"); - - b.HasIndex("CreatorId"); - - 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("BannerUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedBy") - .HasColumnType("uuid"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("DeletedBy") - .HasColumnType("uuid"); - - b.Property("IsDeleted") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("boolean") - .HasComputedColumnSql("\"DeletedAt\" IS NOT NULL", true); - - b.Property("Name") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.Property("PortraitUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)"); - - b.Property("Title") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.Property("Verified") - .HasColumnType("boolean"); - - b.HasKey("Id"); - - b.ToTable("Creators", "Content"); - }); - - modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.Slugs", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedBy") - .HasColumnType("uuid"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)"); - - b.Property("NormalizedName") - .IsRequired() - .ValueGeneratedOnAddOrUpdate() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasComputedColumnSql("LOWER( \"Content\".\"Slugs\".\"Name\")", true); - - b.Property("ReservedUntil") - .HasColumnType("timestamp with time zone"); - - b.Property("UsedBy") - .HasColumnType("uuid"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedName") - .IsUnique(); - - b.ToTable("Slugs", "Content"); - }); - - modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.AlbumPhoto", b => - { - b.HasOne("Hutopy.Web.Features.Contents.Data.Album", "Album") - .WithMany("Photos") - .HasForeignKey("AlbumId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Album"); - }); - - modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.Content", b => - { - b.HasOne("Hutopy.Web.Features.Contents.Data.Creator", "Creator") - .WithMany() - .HasForeignKey("CreatorId"); - - 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.Presentation", "Presentation", b1 => - { - b1.Property("CreatorId") - .HasColumnType("uuid"); - - b1.Property("Description") - .IsRequired() - .HasColumnType("text"); - - b1.Property("Email") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b1.Property("PhoneNumber") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b1.Property("VideoUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.HasKey("CreatorId"); - - b1.ToTable("Presentation", "Content"); - - b1.WithOwner() - .HasForeignKey("CreatorId"); - }); - - b.OwnsOne("Hutopy.Web.Features.Contents.Data.Socials", "Socials", b1 => - { - b1.Property("CreatorId") - .HasColumnType("uuid"); - - b1.Property("FacebookUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("InstagramUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("LinkedInUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("RedditUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("TikTokUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("WebsiteUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("XUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("YoutubeUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.HasKey("CreatorId"); - - b1.ToTable("Socials", "Content"); - - b1.WithOwner() - .HasForeignKey("CreatorId"); - }); - - b.Navigation("Presentation") - .IsRequired(); - - b.Navigation("Socials") - .IsRequired(); - }); - - modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.Album", b => - { - b.Navigation("Photos"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/backend/src/Web/Features/Contents/Data/Migrations/20250526174825_AddThumbnailUrlToPhoto.cs b/backend/src/Web/Features/Contents/Data/Migrations/20250526174825_AddThumbnailUrlToPhoto.cs deleted file mode 100644 index 32c0e4d..0000000 --- a/backend/src/Web/Features/Contents/Data/Migrations/20250526174825_AddThumbnailUrlToPhoto.cs +++ /dev/null @@ -1,44 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Hutopy.Web.Features.Contents.Data.Migrations -{ - /// - public partial class AddThumbnailUrlToPhoto : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.RenameColumn( - name: "PhotoUrl", - schema: "Content", - table: "AlbumPhotos", - newName: "OriginalUrl"); - - migrationBuilder.AddColumn( - name: "ThumbnailUrl", - schema: "Content", - table: "AlbumPhotos", - type: "character varying(2048)", - maxLength: 2048, - nullable: false, - defaultValue: ""); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "ThumbnailUrl", - schema: "Content", - table: "AlbumPhotos"); - - migrationBuilder.RenameColumn( - name: "OriginalUrl", - schema: "Content", - table: "AlbumPhotos", - newName: "PhotoUrl"); - } - } -} diff --git a/backend/src/Web/Features/Contents/Data/Migrations/ContentDbContextModelSnapshot.cs b/backend/src/Web/Features/Contents/Data/Migrations/ContentDbContextModelSnapshot.cs deleted file mode 100644 index e60b022..0000000 --- a/backend/src/Web/Features/Contents/Data/Migrations/ContentDbContextModelSnapshot.cs +++ /dev/null @@ -1,400 +0,0 @@ -// -using System; -using Hutopy.Web.Features.Contents.Data; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -#nullable disable - -namespace Hutopy.Web.Features.Contents.Data.Migrations -{ - [DbContext(typeof(ContentDbContext))] - partial class ContentDbContextModelSnapshot : ModelSnapshot - { - protected override void BuildModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasDefaultSchema("Content") - .HasAnnotation("ProductVersion", "9.0.3") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.Album", 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("IsDeleted") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("boolean") - .HasComputedColumnSql("\"DeletedAt\" IS NOT NULL", true); - - b.Property("Title") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.HasKey("Id"); - - b.ToTable("Albums", "Content"); - }); - - modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.AlbumPhoto", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("AlbumId") - .HasColumnType("uuid"); - - b.Property("Caption") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - 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("IsDeleted") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("boolean") - .HasComputedColumnSql("\"DeletedAt\" IS NOT NULL", true); - - b.Property("Order") - .HasColumnType("integer"); - - b.Property("OriginalUrl") - .IsRequired() - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b.Property("ThumbnailUrl") - .IsRequired() - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b.HasKey("Id"); - - b.HasIndex("AlbumId"); - - b.ToTable("AlbumPhotos", "Content"); - }); - - 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("CreatorId") - .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(2048) - .HasColumnType("character varying(2048)"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)"); - - b.PrimitiveCollection("Urls") - .HasColumnType("text[]"); - - b.HasKey("Id"); - - b.HasIndex("CreatorId"); - - 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("BannerUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedBy") - .HasColumnType("uuid"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("DeletedBy") - .HasColumnType("uuid"); - - b.Property("IsDeleted") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("boolean") - .HasComputedColumnSql("\"DeletedAt\" IS NOT NULL", true); - - b.Property("Name") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.Property("PortraitUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)"); - - b.Property("Title") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.Property("Verified") - .HasColumnType("boolean"); - - b.HasKey("Id"); - - b.ToTable("Creators", "Content"); - }); - - modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.Slugs", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedBy") - .HasColumnType("uuid"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)"); - - b.Property("NormalizedName") - .IsRequired() - .ValueGeneratedOnAddOrUpdate() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasComputedColumnSql("LOWER( \"Content\".\"Slugs\".\"Name\")", true); - - b.Property("ReservedUntil") - .HasColumnType("timestamp with time zone"); - - b.Property("UsedBy") - .HasColumnType("uuid"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedName") - .IsUnique(); - - b.ToTable("Slugs", "Content"); - }); - - modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.AlbumPhoto", b => - { - b.HasOne("Hutopy.Web.Features.Contents.Data.Album", "Album") - .WithMany("Photos") - .HasForeignKey("AlbumId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Album"); - }); - - modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.Content", b => - { - b.HasOne("Hutopy.Web.Features.Contents.Data.Creator", "Creator") - .WithMany() - .HasForeignKey("CreatorId"); - - 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.Presentation", "Presentation", b1 => - { - b1.Property("CreatorId") - .HasColumnType("uuid"); - - b1.Property("Description") - .IsRequired() - .HasColumnType("text"); - - b1.Property("Email") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b1.Property("PhoneNumber") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b1.Property("VideoUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.HasKey("CreatorId"); - - b1.ToTable("Presentation", "Content"); - - b1.WithOwner() - .HasForeignKey("CreatorId"); - }); - - b.OwnsOne("Hutopy.Web.Features.Contents.Data.Socials", "Socials", b1 => - { - b1.Property("CreatorId") - .HasColumnType("uuid"); - - b1.Property("FacebookUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("InstagramUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("LinkedInUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("RedditUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("TikTokUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("WebsiteUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("XUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.Property("YoutubeUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b1.HasKey("CreatorId"); - - b1.ToTable("Socials", "Content"); - - b1.WithOwner() - .HasForeignKey("CreatorId"); - }); - - b.Navigation("Presentation") - .IsRequired(); - - b.Navigation("Socials") - .IsRequired(); - }); - - modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.Album", b => - { - b.Navigation("Photos"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/backend/src/Web/Features/Contents/DependencyInjection.cs b/backend/src/Web/Features/Contents/DependencyInjection.cs deleted file mode 100644 index 70f19cc..0000000 --- a/backend/src/Web/Features/Contents/DependencyInjection.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Hutopy.Web.Features.Contents.Data; -using Hutopy.Web.Features.Contents.Handlers; - -namespace Hutopy.Web.Features.Contents; - -public static class DependencyInjection -{ - public static WebApplicationBuilder AddContentModule( - this WebApplicationBuilder builder, - Action? configureAction = null) - { - builder.Services.AddDbContext(configureAction); - builder.Services.AddScoped(); - builder.Services.Configure(builder.Configuration.GetSection(ContentOptions.ConfigurationSection)); - builder.Services.AddScoped(); - - return builder; - } -} diff --git a/backend/src/Web/Features/Contents/EventHandlers/StripeAccountConfiguredHandler.cs b/backend/src/Web/Features/Contents/EventHandlers/StripeAccountConfiguredHandler.cs deleted file mode 100644 index 43e604c..0000000 --- a/backend/src/Web/Features/Contents/EventHandlers/StripeAccountConfiguredHandler.cs +++ /dev/null @@ -1,43 +0,0 @@ -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 = !string.IsNullOrWhiteSpace(eventModel.StripeAccountId); - - 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/backend/src/Web/Features/Contents/Handlers/AddReaction.cs b/backend/src/Web/Features/Contents/Handlers/AddReaction.cs deleted file mode 100644 index 8002c8f..0000000 --- a/backend/src/Web/Features/Contents/Handlers/AddReaction.cs +++ /dev/null @@ -1,91 +0,0 @@ -using Hutopy.Web.Features.Contents.Data; -using Hutopy.Web.Features.Contents.Data.Enums; - -namespace Hutopy.Web.Features.Contents.Handlers; - -[PublicAPI] -public sealed class AddReactionRequest -{ - public required Guid ContentId { get; set; } - public required string Reaction { get; set; } - public required Guid UserId { get; set; } - public required string UserName { get; set; } -} - -[PublicAPI] -internal sealed class AddReactionRequestValidator - : Validator -{ - public AddReactionRequestValidator() - { - RuleFor(r => r.Reaction) - .NotNull() - .Must(BeAValidReaction) - .WithMessage("'{PropertyValue}' is not a valid reaction."); - } - - private bool BeAValidReaction(string reaction) - { - return Enum.TryParse(typeof(Reaction), reaction, true, out _); - } -} - -[PublicAPI] -public class AddReaction( - ContentDbContext context) - : Endpoint -{ - public override void Configure() - { - Post("/api/content/reaction"); - Options(o => o.WithTags("Contents")); - } - - public override async Task HandleAsync( - AddReactionRequest req, - CancellationToken ct) - { - var content = await context.Contents.SingleAsync(x => x.Id == req.ContentId, ct); - Reaction? reactionEnum; - if (Enum.TryParse(req.Reaction, true, out Reaction result)) - { - reactionEnum = result; - } - else - { - reactionEnum = null; - } - - var currentReaction = content.Reactions.SingleOrDefault(x => x.UserId == req.UserId); - - // Already reacted or reaction didn't change, do nothing - if (currentReaction != null && currentReaction.Reaction == reactionEnum) - { - return; - } - - // User has already reacted, remove the existing reaction - if (currentReaction != null) - { - content.Reactions.Remove(currentReaction); - } - - // If the new reaction is valid, add or update the reaction - if (reactionEnum.HasValue) - { - var reaction = new ContentReaction - { - Reaction = reactionEnum.Value, - UserId = req.UserId, - UserName = req.UserName - }; - - content.Reactions.Add(reaction); - } - - await context.SaveChangesAsync(ct); - } -} - - - diff --git a/backend/src/Web/Features/Contents/Handlers/ChangeBanner.cs b/backend/src/Web/Features/Contents/Handlers/ChangeBanner.cs deleted file mode 100644 index 22e6c9b..0000000 --- a/backend/src/Web/Features/Contents/Handlers/ChangeBanner.cs +++ /dev/null @@ -1,59 +0,0 @@ -using Hutopy.Web.Common.BlobStorage; -using Hutopy.Web.Features.Contents.Data; - -namespace Hutopy.Web.Features.Contents.Handlers; - -[PublicAPI] -public record ChangeBannerRequest( - Guid CreatorId, - IFormFile File); - -[PublicAPI] -public record ChangeBannerResponse( - string BlobUrl); - -[PublicAPI] -public class ChangeBannerHandler( - ContentDbContext context, - AzureBlobStorage blobStorage) - : Endpoint -{ - public override void Configure() - { - Post("/api/creators/{CreatorId}/banner"); - Options(o => o.WithTags("Creators")); - AllowFileUploads(); - } - - public override async Task HandleAsync( - ChangeBannerRequest request, - CancellationToken ct) - { - var creator = await context - .Creators - .SingleOrDefaultAsync( - c => c.Id == request.CreatorId, - cancellationToken: ct); - - if (creator is null) - { - await SendNotFoundAsync(ct); - return; - } - - var blobUrl = await blobStorage.UploadFileAsync( - ContainerNames.Creators, - $"{request.CreatorId}/{SubDirectoryNames.Profile}/{CommonFileNames.BannerPicture}", - request.File.OpenReadStream(), - request.File.ContentType, - ct); - - creator.BannerUrl = $"{blobUrl}?t={DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()}"; - - await context.SaveChangesAsync(ct); - - await SendOkAsync( - new ChangeBannerResponse(blobUrl), - ct); - } -} diff --git a/backend/src/Web/Features/Contents/Handlers/ContentOptions.cs b/backend/src/Web/Features/Contents/Handlers/ContentOptions.cs deleted file mode 100644 index bf43610..0000000 --- a/backend/src/Web/Features/Contents/Handlers/ContentOptions.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Hutopy.Web.Features.Contents.Handlers; - -public class ContentOptions -{ - public const string ConfigurationSection = "Contents"; - - public TimeSpan SlugReservationDuration { get; set; } -} diff --git a/backend/src/Web/Features/Contents/Handlers/CreateContent.cs b/backend/src/Web/Features/Contents/Handlers/CreateContent.cs deleted file mode 100644 index 4d1cad1..0000000 --- a/backend/src/Web/Features/Contents/Handlers/CreateContent.cs +++ /dev/null @@ -1,156 +0,0 @@ -using System.Collections.Concurrent; -using Hutopy.Web.Common.BlobStorage; -using Hutopy.Web.Common.Security; -using Hutopy.Web.Features.Contents.Data; - -namespace Hutopy.Web.Features.Contents.Handlers; - -[PublicAPI] -public record PostContentRequest( - Guid Id, - Guid CreatorId, - string Title, - string Description, - IFormFileCollection? Files, - IFormFile? Thumbnail, - string[]? ExternalUrls); - -[PublicAPI] -public sealed class PostContentRequestValidator : Validator -{ - public PostContentRequestValidator() - { - RuleFor(r => r.Id) - .NotNull().WithMessage("You should specify the Id") - .NotEmpty().WithMessage("You should specify a valid/not empty Id"); - - RuleFor(r => r.CreatorId) - .NotNull().WithMessage("You should specify the CreatorId") - .NotEmpty().WithMessage("You should specify a valid/not empty CreatorId"); - - RuleFor(r => r.Title) - .NotNull().WithMessage("You should specify the Title") - .NotEmpty().WithMessage("You should specify a valid/not empty Title"); - - RuleFor(r => r.Description) - .NotNull().WithMessage("You should specify the Description") - .NotEmpty().WithMessage("You should specify a valid/not empty Description"); - - RuleForEach(r => r.ExternalUrls) - .Must(url => Uri.IsWellFormedUriString(url, UriKind.Absolute) && - (url.StartsWith("http://") || url.StartsWith("https://"))) - .WithMessage("External URL must be a valid HTTP/HTTPS URL"); - - RuleFor(r => r.Thumbnail) - .Must(file => file == null || file.ContentType.StartsWith("image/")) - .WithMessage("Thumbnail must be an image"); - - - } -} - -public sealed class PostContent( - AzureBlobStorage blobStorage, - ContentDbContext context) - : Endpoint -{ - public override void Configure() - { - Post("/api/contents"); - Options(o => o.WithTags("Contents")); - AllowFileUploads(); - } - - public override async Task HandleAsync(PostContentRequest req, CancellationToken ct) - { - var urls = new ConcurrentBag(); - string? thumbnailUrl = null; - - await using var transaction = await context.Database.BeginTransactionAsync(ct); - - try - { - - if (req.Files is not null) - { - await Parallel.ForEachAsync(req.Files, ct, async (file, ict) => - { - try - { - var contentUrl = await SaveFileAsync(req.CreatorId, req.Id, file, ict); - urls.Add(contentUrl); - } - catch (Exception ex) - { - Logger.LogError("Failed to upload file {FileName}: {Message}", file.FileName, ex.Message); - } - }); - } - - - if (req.ExternalUrls is not null) - { - foreach (var externalUrl in req.ExternalUrls.Where(url => !string.IsNullOrWhiteSpace(url))) - { - urls.Add(externalUrl); - } - } - - - if (req.Thumbnail is not null) - { - try - { - thumbnailUrl = await SaveFileAsync(req.CreatorId, req.Id, req.Thumbnail, ct, isThumbnail: true); - } - catch (Exception ex) - { - Logger.LogError("Error uploading thumbnail: {Message}", ex.Message); - } - } - - - await context.Contents.AddAsync(new Content - { - Id = req.Id, - CreatedBy = User.GetUserId(), - Title = req.Title, - Description = req.Description, - Urls = urls.IsEmpty ? null : urls.ToArray(), - ThumbnailUrl = thumbnailUrl, - }, ct); - - await context.SaveChangesAsync(ct); - await transaction.CommitAsync(ct); - - await SendOkAsync(new { Message = "Content published successfully!" }, ct); - } - catch (Exception ex) - { - await transaction.RollbackAsync(ct); - Logger.LogError("Transaction failed: {Message}", ex.Message); - throw; - } - } - - private async Task SaveFileAsync( - Guid creatorId, - Guid contentId, - IFormFile file, - CancellationToken ct = default, - bool isThumbnail = false) - { - - var blobName = isThumbnail - ? $"{creatorId}/{SubDirectoryNames.Contents}/{contentId}/thumbnail-{file.FileName}" - : $"{creatorId}/{SubDirectoryNames.Contents}/{contentId}/{file.FileName}"; - - - return await blobStorage.UploadFileAsync( - ContainerNames.Creators, - blobName, - file.OpenReadStream(), - file.ContentType, - ct: ct); - } -} diff --git a/backend/src/Web/Features/Contents/Handlers/CreateContentFromHtml.cs b/backend/src/Web/Features/Contents/Handlers/CreateContentFromHtml.cs deleted file mode 100644 index 3ada19c..0000000 --- a/backend/src/Web/Features/Contents/Handlers/CreateContentFromHtml.cs +++ /dev/null @@ -1,110 +0,0 @@ -using System.Text; -using Hutopy.Web.Common.BlobStorage; -using Hutopy.Web.Common.Security; -using Hutopy.Web.Features.Contents.Data; -using Hutopy.Web.Features.Contents.Handlers.Models; - -namespace Hutopy.Web.Features.Contents.Handlers; - -[PublicAPI] -public record PostContentFromHtmlRequest( - Guid Id, - Guid CreatorId, - string Title, - string HtmlContent -); - -[PublicAPI] -public sealed class PostContentFromHtmlRequestValidator : Validator -{ - public PostContentFromHtmlRequestValidator() - { - RuleFor(r => r.Id) - .NotNull().WithMessage("You should specify the Id") - .NotEmpty().WithMessage("You should specify a valid/not empty Id"); - - RuleFor(r => r.CreatorId) - .NotNull().WithMessage("You should specify the CreatorId") - .NotEmpty().WithMessage("You should specify a valid/not empty CreatorId"); - - RuleFor(r => r.Title) - .NotNull().WithMessage("You should specify the Title") - .NotEmpty().WithMessage("You should specify a valid/not empty Title"); - } -} - -public sealed class PostContentHtml( - AzureBlobStorage blobStorage, - ContentDbContext context) - : Endpoint -{ - public override void Configure() - { - Post("/api/contents/html"); - Options(o => o.WithTags("Contents")); - AllowFileUploads(); - } - - public override async Task HandleAsync( - PostContentFromHtmlRequest req, - CancellationToken ct) - { - var htmlFileUrl = await SaveHtmlContentAsHtmlFileAsync( - req.CreatorId, - req.Id, - req.HtmlContent, - ct); - - await context.Contents.AddAsync( - new Content { Id = req.Id, CreatedBy = User.GetUserId(), Title = req.Title, HtmlFileUrl = htmlFileUrl }, - ct); - - await context.SaveChangesAsync(ct); - - var content = await context - .Contents - .Select(c => new ContentModel - { - Id = c.Id, - CreatedBy = c.CreatedBy, - CreatedByName = c.Creator.Name, - CreatedByPortraitUrl = c.Creator.PortraitUrl, - CreatedAt = c.CreatedAt, - DeletedBy = c.DeletedBy, - DeletedAt = c.DeletedAt, - Title = c.Title, - Description = c.Description, - Urls = c.Urls, - ThumbnailUrl = c.ThumbnailUrl, - HtmlFileUrl = htmlFileUrl - }) - .SingleOrDefaultAsync( - c => c.Id == req.Id, - cancellationToken: ct); - - await SendOkAsync(content, ct); - } - - private async Task SaveHtmlContentAsHtmlFileAsync( - Guid creatorId, - Guid contentId, - string htmlContent, - CancellationToken ct = default) - { - var fileName = $"{contentId}.html"; - var filePath = $"{creatorId}/{SubDirectoryNames.Contents}/{fileName}"; - - // Convert the HTML string into a stream - using var stream = new MemoryStream(Encoding.UTF8.GetBytes(htmlContent)); - - // Upload the stream as an HTML file - var url = await blobStorage.UploadFileAsync( - ContainerNames.Creators, - filePath, - stream, - "text/html", - ct: ct); - - return url; - } -} diff --git a/backend/src/Web/Features/Contents/Handlers/DeleteContent.cs b/backend/src/Web/Features/Contents/Handlers/DeleteContent.cs deleted file mode 100644 index 26e37e2..0000000 --- a/backend/src/Web/Features/Contents/Handlers/DeleteContent.cs +++ /dev/null @@ -1,60 +0,0 @@ -using Hutopy.Web.Common; -using Hutopy.Web.Common.Security; -using Hutopy.Web.Features.Contents.Data; - -namespace Hutopy.Web.Features.Contents.Handlers; - -[PublicAPI] -public record DeleteContentRequest( - Guid ContentId); - -[PublicAPI] -public sealed class DeleteContentRequestValidator : Validator -{ - public DeleteContentRequestValidator() - { - RuleFor(r => r.ContentId) - .NotNull().WithMessage("You should specify the ContentId") - .NotEmpty().WithMessage("You should specify a valid/not empty ContentId"); - } -} - -public sealed class DeleteContent( - ContentDbContext context) - : Endpoint -{ - public override void Configure() - { - Delete("/api/contents/{ContentId}"); - Options(o => o.WithTags("Contents")); - } - - public override async Task HandleAsync( - DeleteContentRequest req, - CancellationToken ct) - { - var content = await context.Contents.FindAsync( - [req.ContentId], - ct); - - if (content is null) - { - await SendNotFoundAsync(ct); - return; - } - - var userId = HttpContext.User.GetUserId(); - if (content.CreatedBy != userId) - { - await SendForbiddenAsync(ct); - return; - } - - content.DeletedAt = DateTimeOffset.UtcNow; - content.DeletedBy = userId; - - await context.SaveChangesAsync(ct); - - await SendOkAsync(ct); - } -} diff --git a/backend/src/Web/Features/Contents/Handlers/GetContent.cs b/backend/src/Web/Features/Contents/Handlers/GetContent.cs deleted file mode 100644 index 07ec5fb..0000000 --- a/backend/src/Web/Features/Contents/Handlers/GetContent.cs +++ /dev/null @@ -1,58 +0,0 @@ -using Hutopy.Web.Features.Contents.Data; -using Hutopy.Web.Features.Contents.Handlers.Models; - -namespace Hutopy.Web.Features.Contents.Handlers; - -[PublicAPI] -public sealed class GetContentRequest -{ - public Guid ContentId { get; set; } -} - -[PublicAPI] -public class GetContent( - ContentDbContext context) - : Endpoint -{ - public override void Configure() - { - Get("/api/contents/{ContentId:guid}"); - Options(o => o.WithTags("Contents")); - AllowAnonymous(); - } - - public override async Task HandleAsync( - GetContentRequest req, - CancellationToken ct) - { - var content = await context - .Contents - .Select(c => new ContentModel - { - Id = c.Id, - CreatedBy = c.CreatedBy, - CreatedByName = c.Creator.Name, - CreatedByPortraitUrl = c.Creator.PortraitUrl, - CreatedAt = c.CreatedAt, - DeletedBy = c.DeletedBy, - DeletedAt = c.DeletedAt, - Title = c.Title, - Description = c.Description, - Urls = c.Urls, - ThumbnailUrl = c.ThumbnailUrl, - HtmlFileUrl = c.HtmlFileUrl ?? "", - Reactions = c.Reactions.Select(x => new ReactionModel - { - Reaction = x.Reaction.ToString(), UserId = x.UserId, UserName = x.UserName - }).ToList() - }) - .SingleOrDefaultAsync( - c => c.Id == req.ContentId, - cancellationToken: ct); - - if (content is null) - await SendNotFoundAsync(cancellation: ct); - else - await SendAsync(content, cancellation: ct); - } -} diff --git a/backend/src/Web/Features/Contents/Handlers/GetContentsByCreator.cs b/backend/src/Web/Features/Contents/Handlers/GetContentsByCreator.cs deleted file mode 100644 index 1f3e3db..0000000 --- a/backend/src/Web/Features/Contents/Handlers/GetContentsByCreator.cs +++ /dev/null @@ -1,67 +0,0 @@ -using Hutopy.Web.Features.Contents.Data; -using Hutopy.Web.Features.Contents.Handlers.Models; - -namespace Hutopy.Web.Features.Contents.Handlers; - -[PublicAPI] -public sealed class GetContentsByCreatorRequest -{ - public Guid CreatorId { get; set; } - [BindFrom("page_size")] public int PageSize { get; set; } = 10; - [BindFrom("last_id")] public Guid? LastId { get; set; } -} - -[PublicAPI] -public class GetContentsByCreatorHandler( - ContentDbContext context) - : Endpoint> -{ - public override void Configure() - { - Get("/api/contents/creator/{CreatorId:guid}"); - Options(o => o.WithTags("Contents")); - AllowAnonymous(); - } - - public override async Task HandleAsync( - GetContentsByCreatorRequest req, - CancellationToken ct) - { - var query = context.Contents - .Where(c => c.CreatedBy == req.CreatorId && c.DeletedAt == null) - .OrderByDescending(c => c.CreatedAt); - - if (req.LastId.HasValue) - { - query = query.Where(c => c.Id > req.LastId.Value) - .OrderByDescending(c => c.CreatedAt); - } - - var content = await query - .Select(c => new ContentModel - { - Id = c.Id, - CreatedBy = c.CreatedBy, - CreatedByName = c.Creator.Name, - CreatedByPortraitUrl = c.Creator.PortraitUrl, - CreatedAt = c.CreatedAt, - DeletedBy = c.DeletedBy, - DeletedAt = c.DeletedAt, - Title = c.Title, - Description = c.Description, - Urls = c.Urls, - ThumbnailUrl = c.ThumbnailUrl, - HtmlFileUrl = c.HtmlFileUrl ?? "", - Reactions = c.Reactions.Select(x => new ReactionModel - { - Reaction = x.Reaction.ToString(), - UserId = x.UserId, - UserName = x.UserName - }).ToList() - }) - .Take(req.PageSize) - .ToListAsync(ct); - - await SendAsync(content, cancellation: ct); - } -} diff --git a/backend/src/Web/Features/Contents/Handlers/GetFeaturedContents.cs b/backend/src/Web/Features/Contents/Handlers/GetFeaturedContents.cs deleted file mode 100644 index 8d3ffb6..0000000 --- a/backend/src/Web/Features/Contents/Handlers/GetFeaturedContents.cs +++ /dev/null @@ -1,65 +0,0 @@ -using Hutopy.Web.Features.Contents.Data; -using Hutopy.Web.Features.Contents.Handlers.Models; - -namespace Hutopy.Web.Features.Contents.Handlers; - -[PublicAPI] -public sealed class GetFeaturedContentsRequest -{ - [BindFrom("page_size")] public int PageSize { get; set; } = 10; - [BindFrom("last_id")] public Guid? LastId { get; set; } -} - -[PublicAPI] -public class GetFeaturedContentsHandler( - ContentDbContext context) - : Endpoint> -{ - public override void Configure() - { - Get("/api/contents/featured"); - Options(o => o.WithTags("Contents")); - AllowAnonymous(); - } - - public override async Task HandleAsync( - GetFeaturedContentsRequest req, - CancellationToken ct) - { - var query = context.Contents - .Where(c => c.DeletedAt == null); - - if (req.LastId.HasValue) - { - query = query.Where(c => c.Id > req.LastId.Value); - } - - query = query.OrderByDescending(x => x.Reactions.Count); - - var content = await query - .Select(c => new ContentModel - { - Id = c.Id, - CreatedBy = c.CreatedBy, - CreatedByName = c.Creator.Name, - CreatedByPortraitUrl = c.Creator.PortraitUrl, - CreatedAt = c.CreatedAt, - DeletedBy = c.DeletedBy, - DeletedAt = c.DeletedAt, - Title = c.Title, - Description = c.Description, - Urls = c.Urls, - ThumbnailUrl = c.ThumbnailUrl, - Reactions = c.Reactions.Select(x => new ReactionModel - { - Reaction = x.Reaction.ToString(), - UserId = x.UserId, - UserName = x.UserName - }).ToList() - }) - .Take(req.PageSize) - .ToListAsync(ct); - - await SendAsync(content, cancellation: ct); - } -} diff --git a/backend/src/Web/Features/Contents/Handlers/InsertImage.cs b/backend/src/Web/Features/Contents/Handlers/InsertImage.cs deleted file mode 100644 index 49c31b0..0000000 --- a/backend/src/Web/Features/Contents/Handlers/InsertImage.cs +++ /dev/null @@ -1,86 +0,0 @@ -using Hutopy.Web.Common.BlobStorage; - -namespace Hutopy.Web.Features.Contents.Handlers; - -[PublicAPI] -public record InsertImagesRequest( - Guid Id, - Guid CreatorId, - IFormFileCollection? Files -); - -[PublicAPI] -public sealed class InsertImagesRequestValidator : Validator -{ - public InsertImagesRequestValidator() - { - RuleFor(r => r.Id) - .NotNull().WithMessage("You should specify the Id") - .NotEmpty().WithMessage("You should specify a valid/not empty Id"); - - RuleFor(r => r.CreatorId) - .NotNull().WithMessage("You should specify the CreatorId") - .NotEmpty().WithMessage("You should specify a valid/not empty CreatorId"); - } -} - -public sealed class InsertImages( - AzureBlobStorage blobStorage) - : Endpoint -{ - public override void Configure() - { - Post("/api/content/insert-image/"); - Options(o => o.WithTags("Contents")); - AllowFileUploads(); - } - - public override async Task HandleAsync( - InsertImagesRequest req, - CancellationToken ct) - { - var urls = new List(); - if (req.Files is not null) - { - await Parallel.ForEachAsync( - req.Files, - ct, - async ( - file, - ict) => - { - try - { - var contentUrl = await SaveFileAsync( - req.CreatorId, - req.Id, - file, - ict); - urls.Add(contentUrl); - } - catch (Exception ex) - { - Logger.LogError("{ErrorMessage}", ex.Message); - } - }); - } - - await SendOkAsync(urls, ct); - } - - private async Task SaveFileAsync( - Guid creatorId, - Guid contentId, - IFormFile file, - CancellationToken ct = default) - { - var url = await blobStorage.UploadFileAsync( - ContainerNames.Creators, - $"{creatorId}/{SubDirectoryNames.Contents}/{contentId}/{file.FileName}", - file.OpenReadStream(), - file.ContentType, - ct: ct); - - return url; - } -} diff --git a/backend/src/Web/Features/Contents/Handlers/Models/ReactionModel.cs b/backend/src/Web/Features/Contents/Handlers/Models/ReactionModel.cs deleted file mode 100644 index 514efd3..0000000 --- a/backend/src/Web/Features/Contents/Handlers/Models/ReactionModel.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Hutopy.Web.Features.Contents.Handlers.Models; - -public class ReactionModel -{ - public required string Reaction { get; set; } - public required Guid UserId { get; set; } - public required string UserName { get; set; } -} diff --git a/backend/src/Web/Features/Contents/Handlers/RemoveReaction.cs b/backend/src/Web/Features/Contents/Handlers/RemoveReaction.cs deleted file mode 100644 index 04d2b43..0000000 --- a/backend/src/Web/Features/Contents/Handlers/RemoveReaction.cs +++ /dev/null @@ -1,36 +0,0 @@ -using Hutopy.Web.Features.Contents.Data; - -namespace Hutopy.Web.Features.Contents.Handlers; - -[PublicAPI] -public sealed class RemoveReactionRequest -{ - public required Guid ContentId { get; set; } - public required Guid UserId { get; set; } -} - -[PublicAPI] -public class RemoveReaction( - ContentDbContext context) - : Endpoint -{ - public override void Configure() - { - Post("/api/content/reaction/remove"); - Options(o => o.WithTags("Contents")); - } - - public override async Task HandleAsync( - RemoveReactionRequest req, - CancellationToken ct) - { - var content = await context.Contents - .SingleAsync(x => x.Id == req.ContentId, ct); - - var reaction = content.Reactions.Single(x => x.UserId == req.UserId); - - content.Reactions.Remove(reaction); - - await context.SaveChangesAsync(ct); - } -} diff --git a/backend/src/Web/Features/Memberships/Data/Creator.cs b/backend/src/Web/Features/Memberships/Data/Creator.cs deleted file mode 100644 index b536f28..0000000 --- a/backend/src/Web/Features/Memberships/Data/Creator.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Hutopy.Web.Features.Memberships.Data; - -public class Creator -{ - public Guid Id { get; set; } - public string Name { get; set; } - public string? StripeAccountId { get; set; } - public string PortraitUrl { get; set; } -} diff --git a/backend/src/Web/Features/Memberships/Data/MembershipDbContext.cs b/backend/src/Web/Features/Memberships/Data/MembershipDbContext.cs deleted file mode 100644 index f0dcd47..0000000 --- a/backend/src/Web/Features/Memberships/Data/MembershipDbContext.cs +++ /dev/null @@ -1,58 +0,0 @@ -namespace Hutopy.Web.Features.Memberships.Data; - -public sealed class MembershipDbContext( - DbContextOptions options) - : DbContext(options) -{ - public const string SchemaName = "Membership"; - - public DbSet Creators => Set(); - public DbSet Subscriptions => Set(); - public DbSet Tiers => Set(); - public DbSet Tips => Set(); - public DbSet Transactions => Set(); - - - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - modelBuilder.HasDefaultSchema(SchemaName); - - modelBuilder.Entity(); - - modelBuilder - .Entity() - .Property(c => c.CreatedAt) - .ValueGeneratedOnAdd() - .HasDefaultValueSql("CURRENT_TIMESTAMP"); - - modelBuilder - .Entity() - .HasOne(c => c.Creator) - .WithMany() - .HasForeignKey(c => c.CreatorId); - - modelBuilder - .Entity() - .HasOne(c => c.Creator) - .WithMany() - .HasForeignKey(c => c.CreatorId); - - modelBuilder - .Entity() - .Property(c => c.CreatedAt) - .ValueGeneratedOnAdd() - .HasDefaultValueSql("CURRENT_TIMESTAMP"); - - modelBuilder - .Entity() - .Property(c => c.CreatedAt) - .ValueGeneratedOnAdd() - .HasDefaultValueSql("CURRENT_TIMESTAMP"); - - modelBuilder - .Entity() - .Property(c => c.CreatedAt) - .ValueGeneratedOnAdd() - .HasDefaultValueSql("CURRENT_TIMESTAMP"); - } -} diff --git a/backend/src/Web/Features/Memberships/Data/MembershipDbContextInitializer.cs b/backend/src/Web/Features/Memberships/Data/MembershipDbContextInitializer.cs deleted file mode 100644 index 3ddaeba..0000000 --- a/backend/src/Web/Features/Memberships/Data/MembershipDbContextInitializer.cs +++ /dev/null @@ -1,32 +0,0 @@ -namespace Hutopy.Web.Features.Memberships.Data; - -public static class InitializerExtensions -{ - public static async Task InitialiseMembershipDbContextAsync(this WebApplication app) - { - using var scope = app.Services.CreateScope(); - - var initializer = scope.ServiceProvider.GetRequiredService(); - - await initializer.InitialiseAsync(); - } -} - -public class MembershipDbContextInitializer( - ILogger logger, - MembershipDbContext context -) -{ - public async Task InitialiseAsync() - { - try - { - await context.Database.MigrateAsync(); - } - catch (Exception ex) - { - logger.LogError(ex, "An error occurred while initialising the membership database."); - throw; - } - } -} diff --git a/backend/src/Web/Features/Memberships/Data/Migrations/20241022191000_Initial.Designer.cs b/backend/src/Web/Features/Memberships/Data/Migrations/20241022191000_Initial.Designer.cs deleted file mode 100644 index a392276..0000000 --- a/backend/src/Web/Features/Memberships/Data/Migrations/20241022191000_Initial.Designer.cs +++ /dev/null @@ -1,288 +0,0 @@ -// -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("20241022191000_Initial")] - partial class Initial - { - /// - 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("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/backend/src/Web/Features/Memberships/Data/Migrations/20241022191000_Initial.cs b/backend/src/Web/Features/Memberships/Data/Migrations/20241022191000_Initial.cs deleted file mode 100644 index 60ee58d..0000000 --- a/backend/src/Web/Features/Memberships/Data/Migrations/20241022191000_Initial.cs +++ /dev/null @@ -1,201 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Hutopy.Web.Features.Memberships.Data.Migrations -{ - /// - public partial class Initial : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.EnsureSchema( - name: "Membership"); - - migrationBuilder.CreateTable( - name: "Creators", - schema: "Membership", - columns: table => new - { - Id = table.Column(type: "uuid", nullable: false), - Name = table.Column(type: "text", nullable: false), - StripeAccountId = table.Column(type: "text", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Creators", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "Tiers", - schema: "Membership", - columns: table => new - { - Id = table.Column(type: "uuid", nullable: false), - CreatedAt = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "CURRENT_TIMESTAMP"), - CreatorId = table.Column(type: "uuid", nullable: false), - Name = table.Column(type: "character varying(128)", maxLength: 128, nullable: false), - Description = table.Column(type: "character varying(4096)", maxLength: 4096, nullable: false), - Price = table.Column(type: "numeric", nullable: false), - CurrencyCode = table.Column(type: "character varying(128)", maxLength: 128, nullable: false), - StripeProductId = table.Column(type: "character varying(128)", maxLength: 128, nullable: false), - StripePriceId = table.Column(type: "character varying(128)", maxLength: 128, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Tiers", x => x.Id); - table.ForeignKey( - name: "FK_Tiers_Creators_CreatorId", - column: x => x.CreatorId, - principalSchema: "Membership", - principalTable: "Creators", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "Subscriptions", - schema: "Membership", - columns: table => new - { - Id = table.Column(type: "uuid", nullable: false), - CreatedAt = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "CURRENT_TIMESTAMP"), - UserId = table.Column(type: "uuid", nullable: false), - CreatorId = table.Column(type: "uuid", nullable: false), - TierId = table.Column(type: "uuid", nullable: false), - StartDate = table.Column(type: "timestamp with time zone", nullable: false), - EndDate = table.Column(type: "timestamp with time zone", nullable: true), - StripeSessionId = table.Column(type: "character varying(255)", maxLength: 255, nullable: true), - StripeSubscriptionId = table.Column(type: "character varying(255)", maxLength: 255, nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Subscriptions", x => x.Id); - table.ForeignKey( - name: "FK_Subscriptions_Creators_CreatorId", - column: x => x.CreatorId, - principalSchema: "Membership", - principalTable: "Creators", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_Subscriptions_Tiers_TierId", - column: x => x.TierId, - principalSchema: "Membership", - principalTable: "Tiers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "Transactions", - schema: "Membership", - columns: table => new - { - Id = table.Column(type: "uuid", nullable: false), - CreatedAt = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "CURRENT_TIMESTAMP"), - Amount = table.Column(type: "numeric", nullable: false), - Currency = table.Column(type: "text", nullable: false), - Type = table.Column(type: "text", nullable: false), - Timestamp = table.Column(type: "timestamp with time zone", nullable: false), - StripeInvoiceUrl = table.Column(type: "text", nullable: true), - SubscriptionId = table.Column(type: "uuid", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Transactions", x => x.Id); - table.ForeignKey( - name: "FK_Transactions_Subscriptions_SubscriptionId", - column: x => x.SubscriptionId, - principalSchema: "Membership", - principalTable: "Subscriptions", - principalColumn: "Id"); - }); - - migrationBuilder.CreateTable( - name: "Tips", - schema: "Membership", - columns: table => new - { - Id = table.Column(type: "uuid", nullable: false), - CreatedAt = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "CURRENT_TIMESTAMP"), - StripeSessionId = table.Column(type: "text", nullable: false), - TipperId = table.Column(type: "uuid", nullable: false), - TipperName = table.Column(type: "text", nullable: false), - CreatorId = table.Column(type: "uuid", nullable: false), - CreatorName = table.Column(type: "text", nullable: false), - Amount = table.Column(type: "numeric", nullable: false), - Currency = table.Column(type: "text", nullable: false), - Message = table.Column(type: "text", nullable: false), - TransactionId = table.Column(type: "uuid", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Tips", x => x.Id); - table.ForeignKey( - name: "FK_Tips_Transactions_TransactionId", - column: x => x.TransactionId, - principalSchema: "Membership", - principalTable: "Transactions", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateIndex( - name: "IX_Subscriptions_CreatorId", - schema: "Membership", - table: "Subscriptions", - column: "CreatorId"); - - migrationBuilder.CreateIndex( - name: "IX_Subscriptions_TierId", - schema: "Membership", - table: "Subscriptions", - column: "TierId"); - - migrationBuilder.CreateIndex( - name: "IX_Tiers_CreatorId", - schema: "Membership", - table: "Tiers", - column: "CreatorId"); - - migrationBuilder.CreateIndex( - name: "IX_Tips_TransactionId", - schema: "Membership", - table: "Tips", - column: "TransactionId"); - - migrationBuilder.CreateIndex( - name: "IX_Transactions_SubscriptionId", - schema: "Membership", - table: "Transactions", - column: "SubscriptionId"); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "Tips", - schema: "Membership"); - - migrationBuilder.DropTable( - name: "Transactions", - schema: "Membership"); - - migrationBuilder.DropTable( - name: "Subscriptions", - schema: "Membership"); - - migrationBuilder.DropTable( - name: "Tiers", - schema: "Membership"); - - migrationBuilder.DropTable( - name: "Creators", - schema: "Membership"); - } - } -} diff --git a/backend/src/Web/Features/Memberships/Data/Migrations/20241022203207_PortraitUrlToCreator.Designer.cs b/backend/src/Web/Features/Memberships/Data/Migrations/20241022203207_PortraitUrlToCreator.Designer.cs deleted file mode 100644 index 74c8285..0000000 --- a/backend/src/Web/Features/Memberships/Data/Migrations/20241022203207_PortraitUrlToCreator.Designer.cs +++ /dev/null @@ -1,292 +0,0 @@ -// -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("20241022203207_PortraitUrlToCreator")] - partial class PortraitUrlToCreator - { - /// - 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/backend/src/Web/Features/Memberships/Data/Migrations/20241022203207_PortraitUrlToCreator.cs b/backend/src/Web/Features/Memberships/Data/Migrations/20241022203207_PortraitUrlToCreator.cs deleted file mode 100644 index 47dc9c9..0000000 --- a/backend/src/Web/Features/Memberships/Data/Migrations/20241022203207_PortraitUrlToCreator.cs +++ /dev/null @@ -1,31 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Hutopy.Web.Features.Memberships.Data.Migrations -{ - /// - public partial class PortraitUrlToCreator : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "PortraitUrl", - schema: "Membership", - table: "Creators", - type: "text", - nullable: false, - defaultValue: ""); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "PortraitUrl", - schema: "Membership", - table: "Creators"); - } - } -} diff --git a/backend/src/Web/Features/Memberships/Data/Migrations/20241216215210_UpdateSeedData.Designer.cs b/backend/src/Web/Features/Memberships/Data/Migrations/20241216215210_UpdateSeedData.Designer.cs deleted file mode 100644 index dc927cf..0000000 --- a/backend/src/Web/Features/Memberships/Data/Migrations/20241216215210_UpdateSeedData.Designer.cs +++ /dev/null @@ -1,292 +0,0 @@ -// -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/backend/src/Web/Features/Memberships/Data/Migrations/20241216215210_UpdateSeedData.cs b/backend/src/Web/Features/Memberships/Data/Migrations/20241216215210_UpdateSeedData.cs deleted file mode 100644 index cb5b667..0000000 --- a/backend/src/Web/Features/Memberships/Data/Migrations/20241216215210_UpdateSeedData.cs +++ /dev/null @@ -1,22 +0,0 @@ -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/backend/src/Web/Features/Memberships/Data/Migrations/20250401042836_StripeIdNullable.Designer.cs b/backend/src/Web/Features/Memberships/Data/Migrations/20250401042836_StripeIdNullable.Designer.cs deleted file mode 100644 index ab06aa1..0000000 --- a/backend/src/Web/Features/Memberships/Data/Migrations/20250401042836_StripeIdNullable.Designer.cs +++ /dev/null @@ -1,291 +0,0 @@ -// -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("20250401042836_StripeIdNullable")] - partial class StripeIdNullable - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasDefaultSchema("Membership") - .HasAnnotation("ProductVersion", "9.0.3") - .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") - .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/backend/src/Web/Features/Memberships/Data/Migrations/20250401042836_StripeIdNullable.cs b/backend/src/Web/Features/Memberships/Data/Migrations/20250401042836_StripeIdNullable.cs deleted file mode 100644 index c517414..0000000 --- a/backend/src/Web/Features/Memberships/Data/Migrations/20250401042836_StripeIdNullable.cs +++ /dev/null @@ -1,38 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Hutopy.Web.Features.Memberships.Data.Migrations -{ - /// - public partial class StripeIdNullable : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AlterColumn( - name: "StripeAccountId", - schema: "Membership", - table: "Creators", - type: "text", - nullable: true, - oldClrType: typeof(string), - oldType: "text"); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.AlterColumn( - name: "StripeAccountId", - schema: "Membership", - table: "Creators", - type: "text", - nullable: false, - defaultValue: "", - oldClrType: typeof(string), - oldType: "text", - oldNullable: true); - } - } -} diff --git a/backend/src/Web/Features/Memberships/Data/Migrations/MembershipDbContextModelSnapshot.cs b/backend/src/Web/Features/Memberships/Data/Migrations/MembershipDbContextModelSnapshot.cs deleted file mode 100644 index 2b708ef..0000000 --- a/backend/src/Web/Features/Memberships/Data/Migrations/MembershipDbContextModelSnapshot.cs +++ /dev/null @@ -1,288 +0,0 @@ -// -using System; -using Hutopy.Web.Features.Memberships.Data; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -#nullable disable - -namespace Hutopy.Web.Features.Memberships.Data.Migrations -{ - [DbContext(typeof(MembershipDbContext))] - partial class MembershipDbContextModelSnapshot : ModelSnapshot - { - protected override void BuildModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasDefaultSchema("Membership") - .HasAnnotation("ProductVersion", "9.0.3") - .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") - .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/backend/src/Web/Features/Memberships/Data/Subscription.cs b/backend/src/Web/Features/Memberships/Data/Subscription.cs deleted file mode 100644 index 67b3ad1..0000000 --- a/backend/src/Web/Features/Memberships/Data/Subscription.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.ComponentModel.DataAnnotations; - -namespace Hutopy.Web.Features.Memberships.Data; - -public class Subscription -{ - public Guid Id { get; set; } - public DateTimeOffset CreatedAt { get; set; } - public Guid UserId { get; set; } - public Guid CreatorId { get; set; } - public Creator Creator { get; set; } - public Guid TierId { get; set; } - public Tier Tier { get; set; } - public DateTimeOffset StartDate { get; set; } - public DateTimeOffset? EndDate { get; set; } - public bool IsActive => EndDate == null || EndDate > DateTimeOffset.UtcNow; - [MaxLength(255)]public string? StripeSessionId { get; set; } - [MaxLength(255)]public string? StripeSubscriptionId { get; set; } - - public ICollection Transactions { get; set; } = []; -} diff --git a/backend/src/Web/Features/Memberships/Data/Tip.cs b/backend/src/Web/Features/Memberships/Data/Tip.cs deleted file mode 100644 index 0f4bd09..0000000 --- a/backend/src/Web/Features/Memberships/Data/Tip.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace Hutopy.Web.Features.Memberships.Data; - -public class Tip -{ - public Guid Id { get; set; } - public DateTimeOffset CreatedAt { get; set; } - public string StripeSessionId { get; set; } - public Guid TipperId { get; set; } - public string TipperName { get; set; } - public Guid CreatorId { get; set; } - public string CreatorName { get; set; } - public decimal Amount { get; set; } - public string Currency { get; set; } - public string Message { get; set; } - - public Guid TransactionId { get; set; } - public Transaction Transaction { get; set; } -} diff --git a/backend/src/Web/Features/Memberships/Data/Transaction.cs b/backend/src/Web/Features/Memberships/Data/Transaction.cs deleted file mode 100644 index 5b62f41..0000000 --- a/backend/src/Web/Features/Memberships/Data/Transaction.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Hutopy.Web.Features.Memberships.Data; - -public class Transaction -{ - public Guid Id { get; set; } - public DateTimeOffset CreatedAt { get; set; } - public decimal Amount { get; set; } - public string Currency { get; set; } - public string Type { get; set; } // Subscription, Tip - public DateTime Timestamp { get; set; } - public string? StripeInvoiceUrl { get; set; } -} diff --git a/backend/src/Web/Features/Memberships/DependencyInjection.cs b/backend/src/Web/Features/Memberships/DependencyInjection.cs deleted file mode 100644 index eb14bd1..0000000 --- a/backend/src/Web/Features/Memberships/DependencyInjection.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Hutopy.Web.Features.Memberships.Data; -using Hutopy.Web.Features.Memberships.Infrastructure; - -namespace Hutopy.Web.Features.Memberships; - -public static class DependencyInjection -{ - public static WebApplicationBuilder AddMembershipModule( - this WebApplicationBuilder builder, - Action? configureAction = null) - { - builder.Services.AddSingleton(); - - builder.Services.AddDbContext(configureAction); - builder.Services.AddScoped(); - - builder.Services.AddScoped(); - - builder.Services - .AddOptions() - .Bind(builder.Configuration.GetSection("Stripe")) - .ValidateDataAnnotations() - .ValidateOnStart(); - - return builder; - } -} diff --git a/backend/src/Web/Features/Memberships/Events/StripeAccountConfigured.cs b/backend/src/Web/Features/Memberships/Events/StripeAccountConfigured.cs deleted file mode 100644 index cb91976..0000000 --- a/backend/src/Web/Features/Memberships/Events/StripeAccountConfigured.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace Hutopy.Web.Features.Memberships.Events; - -public record StripeAccountConfigured( - Guid CreatorId, - string StripeAccountId); diff --git a/backend/src/Web/Features/Memberships/Events/SubscriptionPaid.cs b/backend/src/Web/Features/Memberships/Events/SubscriptionPaid.cs deleted file mode 100644 index b661b35..0000000 --- a/backend/src/Web/Features/Memberships/Events/SubscriptionPaid.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Hutopy.Web.Features.Memberships.Events; - -public record struct SubscriptionPaid( - Guid CreatorId, - string CreatorName, - string Tier, - DateTimeOffset Since); diff --git a/backend/src/Web/Features/Memberships/Events/TipPaid.cs b/backend/src/Web/Features/Memberships/Events/TipPaid.cs deleted file mode 100644 index 6331757..0000000 --- a/backend/src/Web/Features/Memberships/Events/TipPaid.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Hutopy.Web.Features.Memberships.Events; - -public record struct TipPaid( - Guid CreatorId, - string CreatorName, - decimal Amount, - string Currency, - string Message); diff --git a/backend/src/Web/Features/Memberships/Handlers/ChangeStripeId.cs b/backend/src/Web/Features/Memberships/Handlers/ChangeStripeId.cs deleted file mode 100644 index 9a3dae0..0000000 --- a/backend/src/Web/Features/Memberships/Handlers/ChangeStripeId.cs +++ /dev/null @@ -1,55 +0,0 @@ -using Hutopy.Web.Common.Security; -using Hutopy.Web.Features.Memberships.Data; -using Hutopy.Web.Features.Memberships.Events; - -namespace Hutopy.Web.Features.Memberships.Handlers; - -[PublicAPI] -public record struct ChangeStripeIdRequest( - string StripeAccountId); - -public class ChangeStripeIdHandler( - MembershipDbContext dbContext) - : Endpoint -{ - public override void Configure() - { - Post("/api/membership/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(), - PortraitUrl = HttpContext.User.GetPortraitUrl() ?? string.Empty - }; - - await dbContext.AddAsync(creator, ct); - } - - creator.StripeAccountId = req.StripeAccountId; - - await dbContext.SaveChangesAsync(ct); - - await PublishAsync( - new StripeAccountConfigured(creator.Id, creator.StripeAccountId), - cancellation: ct); - - await SendOkAsync(creator.Id, ct); - } -} diff --git a/backend/src/Web/Features/Memberships/Handlers/GetActiveSubscriptions.cs b/backend/src/Web/Features/Memberships/Handlers/GetActiveSubscriptions.cs deleted file mode 100644 index a42601f..0000000 --- a/backend/src/Web/Features/Memberships/Handlers/GetActiveSubscriptions.cs +++ /dev/null @@ -1,44 +0,0 @@ -using Hutopy.Web.Common.Security; -using Hutopy.Web.Features.Memberships.Data; - -namespace Hutopy.Web.Features.Memberships.Handlers; - -[PublicAPI] -public record struct GetActiveSubscriptionsResponse( - Guid Id, - Guid CreatorId, - string CreatorName, - string CreatorPortraitUrl, - DateTimeOffset StartDate, - DateTimeOffset? EndDate); - -[PublicAPI] -public class GetActiveSubscriptionsHandler( - MembershipDbContext dbContext) - : EndpointWithoutRequest> -{ - public override void Configure() - { - Get("/api/membership/active"); - Options(o => o.WithTags("Memberships")); - } - - public override async Task HandleAsync( - CancellationToken ct) - { - var subscriptions = await dbContext - .Subscriptions - .Where(subscription => subscription.UserId == User.GetUserId()) - .Where(subscription => subscription.EndDate == null || subscription.EndDate > DateTimeOffset.UtcNow) - .Select(subscription => new GetActiveSubscriptionsResponse( - subscription.Id, - subscription.Creator.Id, - subscription.Creator.Name, - subscription.Creator.PortraitUrl, - subscription.StartDate, - subscription.EndDate)) - .ToListAsync(ct); - - await SendOkAsync(subscriptions, ct); - } -} diff --git a/backend/src/Web/Features/Memberships/Handlers/GetReceivedTips.cs b/backend/src/Web/Features/Memberships/Handlers/GetReceivedTips.cs deleted file mode 100644 index 85e1d7f..0000000 --- a/backend/src/Web/Features/Memberships/Handlers/GetReceivedTips.cs +++ /dev/null @@ -1,46 +0,0 @@ -using Hutopy.Web.Common; -using Hutopy.Web.Common.Security; -using Hutopy.Web.Features.Memberships.Data; - -namespace Hutopy.Web.Features.Memberships.Handlers; - -[PublicAPI] -public record struct TipReceivedModel( - Guid Id, - DateTimeOffset CreatedAt, - Guid TipperId, - string TipperName, - decimal Amount, - string Currency, - string Message); - -[PublicAPI] -public class GetReceivedTipsHandler( - MembershipDbContext dbContext) - : EndpointWithoutRequest> -{ - public override void Configure() - { - Get("/api/tips/received"); - Options(o => o.WithTags("Memberships")); - } - - public override async Task HandleAsync( - CancellationToken ct) - { - var tipsReceived = await dbContext - .Tips - .Where(tip => tip.CreatorId == User.GetUserId()) - .Select(tip => new TipReceivedModel( - tip.Id, - tip.CreatedAt, - tip.TipperId, - tip.TipperName, - tip.Amount, - tip.Currency, - tip.Message)) - .ToListAsync(ct); - - await SendOkAsync(tipsReceived, ct); - } -} diff --git a/backend/src/Web/Features/Memberships/Handlers/GetSentTips.cs b/backend/src/Web/Features/Memberships/Handlers/GetSentTips.cs deleted file mode 100644 index 70c1b52..0000000 --- a/backend/src/Web/Features/Memberships/Handlers/GetSentTips.cs +++ /dev/null @@ -1,46 +0,0 @@ -using Hutopy.Web.Common; -using Hutopy.Web.Common.Security; -using Hutopy.Web.Features.Memberships.Data; - -namespace Hutopy.Web.Features.Memberships.Handlers; - -[PublicAPI] -public record struct TipSentModel( - Guid Id, - DateTimeOffset CreatedAt, - Guid CreatorId, - string CreatorName, - decimal Amount, - string Currency, - string Message); - -[PublicAPI] -public class GetSentTipsHandler( - MembershipDbContext dbContext) - : EndpointWithoutRequest> -{ - public override void Configure() - { - Get("/api/tips/sent"); - Options(o => o.WithTags("Memberships")); - } - - public override async Task HandleAsync( - CancellationToken ct) - { - var tips = await dbContext - .Tips - .Where(t => t.TipperId == User.GetUserId()) - .Select(tip => new TipSentModel( - tip.Id, - tip.CreatedAt, - tip.CreatorId, - tip.CreatorName, - tip.Amount, - tip.Currency, - tip.Message)) - .ToListAsync(ct); - - await SendOkAsync(tips, ct); - } -} diff --git a/backend/src/Web/Features/Memberships/Handlers/HandleStripe.cs b/backend/src/Web/Features/Memberships/Handlers/HandleStripe.cs deleted file mode 100644 index 5578604..0000000 --- a/backend/src/Web/Features/Memberships/Handlers/HandleStripe.cs +++ /dev/null @@ -1,51 +0,0 @@ -using Hutopy.Web.Features.Memberships.Infrastructure; -using Microsoft.Extensions.Options; -using Stripe; - -namespace Hutopy.Web.Features.Memberships.Handlers; - -public class StripeWebhookEndpoint( - StripeService stripeService, - IOptions options) - : EndpointWithoutRequest -{ - public override void Configure() - { - Post("/api/stripe"); - AllowAnonymous(); - Options(o => o.WithTags("Memberships")); - } - - public override async Task HandleAsync(CancellationToken ct) - { - using var streamReader = new StreamReader(HttpContext.Request.Body); - var json = await streamReader.ReadToEndAsync(ct); - - var signatureHeader = HttpContext.Request.Headers["Stripe-Signature"]; - var stripeEvent = EventUtility.ConstructEvent(json, signatureHeader, options.Value.WebhookSecret); - - switch (stripeEvent.Type) - { - case "checkout.session.completed": - await stripeService.HandleCheckoutSessionCompleted(stripeEvent, ct); - break; - case "invoice.payment_succeeded": - await stripeService.HandleInvoicePaymentSucceeded(stripeEvent, ct); - break; - case "invoice.payment_failed": - await stripeService.HandleInvoicePaymentFailed(stripeEvent, ct); - break; - case "customer.subscription.created": - await stripeService.HandleCustomerSubscriptionCreated(stripeEvent, ct); - break; - case "customer.subscription.updated": - await stripeService.HandleCustomerSubscriptionUpdated(stripeEvent, ct); - break; - case "customer.subscription.deleted": - await stripeService.HandleCustomerSubscriptionDeleted(stripeEvent, ct); - break; - } - - await SendOkAsync(ct); - } -} diff --git a/backend/src/Web/Features/Memberships/Handlers/SendTip.cs b/backend/src/Web/Features/Memberships/Handlers/SendTip.cs deleted file mode 100644 index 7311311..0000000 --- a/backend/src/Web/Features/Memberships/Handlers/SendTip.cs +++ /dev/null @@ -1,95 +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 SendTipRequest( - Guid CreatorId, - decimal Amount, - string Currency, - string Message, - string CheckoutSuccessUrl, - string CheckoutCancelledUrl); - -[PublicAPI] -public record SendTipResponse( - string Status, - string StripeCheckoutUrl); - -[PublicAPI] -public class SendTipRequestValidator : Validator -{ - public SendTipRequestValidator() - { - RuleFor(x => x.Amount) - .GreaterThan(0) - .WithMessage("Tip amount must be greater than 0"); - - RuleFor(x => x.CreatorId) - .NotEmpty() - .WithMessage("Creator ID is required"); - - RuleFor(x => x.CheckoutSuccessUrl) - .NotEmpty() - .WithMessage("CheckoutSuccessUrl is required"); - - RuleFor(x => x.CheckoutCancelledUrl) - .NotEmpty() - .WithMessage("CheckoutCancelledUrl is required"); - } -} - -[PublicAPI] -public class SendTipHandler( - MembershipDbContext dbContext, - StripeService stripeService, - ILogger logger) - : Endpoint -{ - public override void Configure() - { - Post("/api/tips"); - Options(o => o.WithTags("Memberships")); - - AllowAnonymous(); - } - - public override async Task HandleAsync( - SendTipRequest req, - CancellationToken ct) - { - try - { - var creator = await dbContext.Creators.FindAsync( - [req.CreatorId], - cancellationToken: ct); - - if (creator == null) - { - await SendNotFoundAsync(ct); - return; - } - - var checkoutSession = await stripeService.CreateTipCheckoutSessionAsync( - creator.Id, - creator.Name, - req.Amount, - req.Currency, - req.Message, - creator.StripeAccountId!, - req.CheckoutSuccessUrl, - req.CheckoutCancelledUrl, - ct); - - await SendAsync( - new SendTipResponse("Pending", checkoutSession.Url), - cancellation: ct); - } - catch (Exception e) - { - logger.LogError(e.Message); - } - } -} diff --git a/backend/src/Web/Features/Memberships/Infrastructure/PushNotificationService.cs b/backend/src/Web/Features/Memberships/Infrastructure/PushNotificationService.cs deleted file mode 100644 index 425b323..0000000 --- a/backend/src/Web/Features/Memberships/Infrastructure/PushNotificationService.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Hutopy.Web.Features.Memberships.Infrastructure; - -public sealed class PushNotificationService( - ILogger logger) -{ - public void NotifyCreator( - Guid tipCreatorId, - TEvent notification) - where TEvent : struct - { - logger.LogInformation("Notifying creator {CreatorId}, {Notification}", tipCreatorId, notification); - } -} diff --git a/backend/src/Web/Features/Memberships/Infrastructure/StripeService.cs b/backend/src/Web/Features/Memberships/Infrastructure/StripeService.cs deleted file mode 100644 index c1db0ec..0000000 --- a/backend/src/Web/Features/Memberships/Infrastructure/StripeService.cs +++ /dev/null @@ -1,428 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using Hutopy.Web.Features.Memberships.Data; -using Hutopy.Web.Features.Memberships.Events; -using Microsoft.Extensions.Options; -using Stripe; -using Stripe.Checkout; -using Subscription = Stripe.Subscription; - -namespace Hutopy.Web.Features.Memberships.Infrastructure; - -public class StripeOptions -{ - [Required] public required string SecretKey { get; init; } - - [Required] public required string WebhookSecret { get; init; } - - [Required] [Range(0, 1)] public required decimal HutopyRate { get; init; } -} - -public sealed class StripeService( - IOptions paymentOptions, - MembershipDbContext dbContext, - PushNotificationService notificationService) -{ - public async Task CreateProductAsync( - Guid creatorId, - Guid tierId, - string productName, - string currencyCode, - decimal amount) - { - StripeConfiguration.ApiKey = paymentOptions.Value.SecretKey; - - // Create the product - var productService = new ProductService(); - var product = await productService.CreateAsync( - new ProductCreateOptions - { - Name = productName, - Metadata = { { "creatorId", creatorId.ToString() }, { "tierId", tierId.ToString() } } - }); - - // Create the price for the product - var priceService = new PriceService(); - await priceService.CreateAsync( - new PriceCreateOptions - { - Product = product.Id, - UnitAmountDecimal = amount * 100, // Convert amount to cents - Currency = currencyCode, - Recurring = new PriceRecurringOptions { Interval = "month" } - }); - - return product.Id; - } - - public async Task CreateTipCheckoutSessionAsync( - Guid creatorId, - string creatorName, - decimal amount, - string currencyCode, - string message, - string creatorAccountId, - string successUrl, - string cancelUrl, - CancellationToken ct = default) - { - StripeConfiguration.ApiKey = paymentOptions.Value.SecretKey; - - // Create Stripe customer for the user if not already created - var customerService = new CustomerService(); - var customer = await customerService.CreateAsync( - new CustomerCreateOptions{}, - cancellationToken: ct); - - // Create paymentIntent for the user - var sessionService = new SessionService(); - return await sessionService.CreateAsync( - new SessionCreateOptions - { - Customer = customer.Id, - PaymentMethodTypes = ["card"], - LineItems = - [ - new SessionLineItemOptions - { - PriceData = new SessionLineItemPriceDataOptions - { - Currency = currencyCode, - UnitAmountDecimal = amount, // Amount in cents - ProductData = new SessionLineItemPriceDataProductDataOptions - { - Name = $"Tip for {creatorName}", // or any descriptive name for the tip - Metadata = new Dictionary { { "creatorId", creatorId.ToString() } } - } - }, - Quantity = 1 - } - ], - Mode = "payment", - PaymentIntentData = new SessionPaymentIntentDataOptions - { - ApplicationFeeAmount = - Convert.ToInt64(amount * 100 * paymentOptions.Value.HutopyRate), // Platform fee - TransferData = new SessionPaymentIntentDataTransferDataOptions - { - Destination = creatorAccountId // Creator's Stripe account ID - } - }, - SuccessUrl = successUrl, // Redirect after successful payment - CancelUrl = cancelUrl, // Redirect after canceled payment - Metadata = new Dictionary - { - { "creatorId", creatorId.ToString() }, - { "creatorName", creatorName }, - { "message", message }, - } - }, - cancellationToken: ct); - } - - public async Task CreateSubscriptionCheckoutSession( - Guid userId, - Guid creatorId, - string creatorName, - string creatorAccountId, - Guid tierId, - string priceId, - string successUrl, - string cancelUrl) - { - StripeConfiguration.ApiKey = paymentOptions.Value.SecretKey; - - // 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", userId.ToString() } } - }); - - // Create Checkout Session for the subscription - var sessionService = new SessionService(); - return await sessionService.CreateAsync( - new SessionCreateOptions - { - Customer = customer.Id, - PaymentMethodTypes = ["card"], - LineItems = - [ - new SessionLineItemOptions { Price = priceId, Quantity = 1 } - ], - Mode = "subscription", - SubscriptionData = new SessionSubscriptionDataOptions - { - ApplicationFeePercent = paymentOptions.Value.HutopyRate, - TransferData = new SessionSubscriptionDataTransferDataOptions { Destination = creatorAccountId } - }, - SuccessUrl = successUrl, // Redirect after successful payment - CancelUrl = cancelUrl, // Redirect after canceled payment - Metadata = new Dictionary - { - { "userId", userId.ToString() }, - { "creatorId", creatorId.ToString() }, - { "creatorName", creatorName }, - { "tierId", tierId.ToString() } - } - }); - } - - public async Task CancelSubscription( - Guid subscriptionId) - { - var subscriptionService = new SubscriptionService(); - await subscriptionService.CancelAsync(subscriptionId.ToString()); - } - - public async Task HandleInvoicePaymentSucceeded( - Event stripeEvent, - CancellationToken ct = default) - { - // Ensure we have an invoice related to a Subscription - if (stripeEvent.Data.Object is not Invoice { Subscription: not null } invoice) - { - return; - } - - var subscription = await dbContext - .Subscriptions - .FirstOrDefaultAsync( - subscription => subscription.StripeSubscriptionId == invoice.Subscription.Id, - cancellationToken: ct); - - if (subscription == null) - { - return; - } - - // Record the Transaction - var transaction = new Transaction - { - Id = Guid.NewGuid(), - CreatedAt = DateTimeOffset.UtcNow, - Amount = invoice.AmountPaid / 100m, // Convert amount from cents to dollars - Currency = invoice.Currency, - Type = "Subscription", - Timestamp = DateTime.UtcNow, - StripeInvoiceUrl = invoice.HostedInvoiceUrl - }; - - dbContext.Transactions.Add(transaction); - - // Link the Transaction to the Subscription - subscription.Transactions.Add(transaction); - - await dbContext.SaveChangesAsync(ct); - } - - public async Task HandleInvoicePaymentFailed( - Event stripeEvent, - CancellationToken ct = default) - { - if (stripeEvent.Data.Object is not Invoice { Subscription: not null } invoice) - { - return; - } - - var subscription = await dbContext - .Subscriptions - .SingleOrDefaultAsync( - subscription => subscription.StripeSubscriptionId == invoice.SubscriptionId, - cancellationToken: ct); - - if (subscription != null) - { - subscription.EndDate = DateTimeOffset.UtcNow; // Mark as expired or failed - await dbContext.SaveChangesAsync(ct); - } - } - - private async Task HandleTipPayment( - Session session, - CancellationToken ct) - { - // Record the Tip - var tip = new Tip - { - Id = Guid.NewGuid(), - CreatedAt = DateTimeOffset.UtcNow, - StripeSessionId = session.Id, - TipperId = Guid.Parse(session.Metadata["tipperId"]), - TipperName = session.Metadata["tipperName"], - CreatorId = Guid.Parse(session.Metadata["creatorId"]), - CreatorName = session.Metadata["creatorName"], - Amount = session.AmountTotal!.Value / 100m, - Currency = session.Currency, - Message = session.Metadata["message"] - }; - - dbContext.Tips.Add(tip); - - // Record the Transaction - var transaction = new Transaction - { - Id = Guid.NewGuid(), - CreatedAt = DateTimeOffset.UtcNow, - Amount = tip.Amount, - Currency = tip.Currency, - Type = "Tip", - Timestamp = DateTime.UtcNow, - // TODO: __StripeInvoiceUrl = session.Invoice.HostedInvoiceUrl__ How come nor Invoice or InvoiceId are set. - }; - - dbContext.Transactions.Add(transaction); - - // Link the Transaction to the Tip - tip.TransactionId = transaction.Id; - - // Save the changes - await dbContext.SaveChangesAsync(ct); - - // Notify the Creator - notificationService.NotifyCreator( - tip.CreatorId, - new TipPaid( - tip.CreatorId, - tip.CreatorName, - tip.Amount, - tip.Currency, - tip.Message) - ); - } - - private async Task HandleSubscriptionPayment( - Session session, - CancellationToken ct) - { - // Record the Subscription - var subscription = new Data.Subscription - { - Id = Guid.NewGuid(), - CreatedAt = DateTimeOffset.UtcNow, - UserId = Guid.Parse(session.Metadata["userId"]), - CreatorId = Guid.Parse(session.Metadata["creatorId"]), - TierId = Guid.Parse(session.Metadata["tierId"]), - StartDate = DateTimeOffset.UtcNow, - StripeSessionId = session.Id, - StripeSubscriptionId = session.SubscriptionId - }; - - dbContext.Subscriptions.Add(subscription); - - // Record the Transaction - var transaction = new Transaction - { - Id = Guid.NewGuid(), - CreatedAt = DateTimeOffset.UtcNow, - Amount = session.AmountTotal!.Value / 100m, // Convert amount from cents to dollars - Currency = session.Currency, - Type = "Subscription", - Timestamp = DateTime.UtcNow, - // TODO: __StripeInvoiceUrl = session.Invoice.HostedInvoiceUrl__ How come nor Invoice or InvoiceId are set. - }; - - dbContext.Transactions.Add(transaction); - - // Link the Transaction to the Subscription - subscription.Transactions.Add(transaction); - - // Save the changes - await dbContext.SaveChangesAsync(ct); - - // Notify the Creator - notificationService.NotifyCreator( - subscription.CreatorId, - new SubscriptionPaid( - subscription.CreatorId, - session.Metadata["creatorName"], - subscription.TierId.ToString(), - subscription.StartDate) - ); - } - - public async Task HandleCheckoutSessionCompleted( - Event stripeEvent, - CancellationToken ct = default) - { - if (stripeEvent.Data.Object is not Session session) - { - return; - } - - switch (session.Mode) - { - // Check if this is a one-time tip - case "payment" when session.PaymentIntentId != null: - await HandleTipPayment(session, ct); - break; - // Check if this is a subscription - case "subscription" when session.SubscriptionId != null: - await HandleSubscriptionPayment(session, ct); - break; - } - } - - public async Task HandleCustomerSubscriptionCreated( - Event stripeEvent, - CancellationToken ct) - { - if (stripeEvent.Data.Object is not Subscription stripeSubscription) - return; - - var subscription = await dbContext - .Subscriptions - .SingleOrDefaultAsync( - subscription => subscription.StripeSubscriptionId == stripeSubscription.Id, - cancellationToken: ct); - - if (subscription != null) - { - subscription.StartDate = stripeSubscription.CurrentPeriodStart; - subscription.EndDate = null; // Active subscription - - await dbContext.SaveChangesAsync(ct); - } - } - - public async Task HandleCustomerSubscriptionUpdated( - Event stripeEvent, - CancellationToken ct) - { - if (stripeEvent.Data.Object is Subscription stripeSubscription) - { - var subscription = await dbContext - .Subscriptions - .SingleOrDefaultAsync( - s => s.StripeSubscriptionId == stripeSubscription.Id, - cancellationToken: ct); - - if (subscription != null) - { - subscription.StartDate = stripeSubscription.CurrentPeriodStart; - subscription.EndDate = null; // Active subscription - - await dbContext.SaveChangesAsync(ct); - } - } - } - - public async Task HandleCustomerSubscriptionDeleted( - Event stripeEvent, - CancellationToken ct) - { - var subscription = stripeEvent.Data.Object as Subscription; - var existingSubscription = await dbContext - .Subscriptions - .FirstOrDefaultAsync(x => x.StripeSubscriptionId == subscription!.Id, ct); - - if (existingSubscription != null) - { - var today = DateTime.Today; - int lastDay = DateTime.DaysInMonth(today.Year, today.Month); - var lastDayOfMonth = new DateTime(today.Year, today.Month, lastDay); - existingSubscription.EndDate = new DateTimeOffset(lastDayOfMonth); - await dbContext.SaveChangesAsync(ct); - } - } -} diff --git a/backend/src/Web/Features/Messages/Data/Message.cs b/backend/src/Web/Features/Messages/Data/Message.cs deleted file mode 100644 index fff397b..0000000 --- a/backend/src/Web/Features/Messages/Data/Message.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.ComponentModel.DataAnnotations; - -namespace Hutopy.Web.Features.Messages.Data; - -public class Message -{ - public Guid Id { get; set; } - public Guid SubjectId { get; set; } - public Guid CreatedBy { get; set; } - [MaxLength(255)] public required string CreatedByName { get; set; } - [MaxLength(255)] public string? CreatedByPortraitUrl { get; set; } - public DateTimeOffset CreatedAt { get; set; } - public Guid? ParentId { get; set; } - [MaxLength(2048)] public required string Value { get; set; } -} diff --git a/backend/src/Web/Features/Messages/Data/MessagingDbContextInitializer.cs b/backend/src/Web/Features/Messages/Data/MessagingDbContextInitializer.cs deleted file mode 100644 index cd40485..0000000 --- a/backend/src/Web/Features/Messages/Data/MessagingDbContextInitializer.cs +++ /dev/null @@ -1,32 +0,0 @@ -namespace Hutopy.Web.Features.Messages.Data; - -public static class InitializerExtensions -{ - public static async Task InitialiseMessagingDbContextAsync(this WebApplication app) - { - using var scope = app.Services.CreateScope(); - - var initializer = scope.ServiceProvider.GetRequiredService(); - - await initializer.InitialiseAsync(); - } -} - -public class MessagingDbContextInitializer( - ILogger logger, - MessagingDbContext context - ) -{ - public async Task InitialiseAsync() - { - try - { - await context.Database.MigrateAsync(); - } - catch (Exception ex) - { - logger.LogError(ex, "An error occurred while initialising the messaging database."); - throw; - } - } -} diff --git a/backend/src/Web/Features/Messages/Data/Migrations/20241217225954_ChangeStripeId.cs b/backend/src/Web/Features/Messages/Data/Migrations/20241217225954_ChangeStripeId.cs deleted file mode 100644 index 44a9df9..0000000 --- a/backend/src/Web/Features/Messages/Data/Migrations/20241217225954_ChangeStripeId.cs +++ /dev/null @@ -1,38 +0,0 @@ -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/backend/src/Web/Features/Messages/DependencyInjection.cs b/backend/src/Web/Features/Messages/DependencyInjection.cs deleted file mode 100644 index f75c425..0000000 --- a/backend/src/Web/Features/Messages/DependencyInjection.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Hutopy.Web.Features.Messages.Data; - -namespace Hutopy.Web.Features.Messages; - -public static class DependencyInjection -{ - public static WebApplicationBuilder AddMessagingModule( - this WebApplicationBuilder builder, - Action? configureAction = null) - { - builder.Services.AddDbContext(configureAction); - builder.Services.AddScoped(); - - return builder; - } -} diff --git a/backend/src/Web/Features/Messages/Handlers/GetMessagesByUser.cs b/backend/src/Web/Features/Messages/Handlers/GetMessagesByUser.cs deleted file mode 100644 index bdd5105..0000000 --- a/backend/src/Web/Features/Messages/Handlers/GetMessagesByUser.cs +++ /dev/null @@ -1,46 +0,0 @@ -using Hutopy.Web.Features.Messages.Data; -using Hutopy.Web.Features.Messages.Handlers.Models; - -namespace Hutopy.Web.Features.Messages.Handlers; - -public class GetMessagesByUserRequest -{ - public Guid UserId { get; set; } -} - -public record struct GetMessagesByUserResponse -{ - public required List Messages { get; init; } -} - -public class GetMessagesByUser( - MessagingDbContext context) - : Endpoint -{ - public override void Configure() - { - Get("/api/messages/user/{UserId:guid}"); - Options(o => o.WithTags("Messages")); - AllowAnonymous(); - } - - public override async Task HandleAsync( - GetMessagesByUserRequest req, - CancellationToken ct) - { - var messages = await context - .Messages - .Where(c => c.CreatedBy == req.UserId) - .Where(c => c.ParentId == null) - .ToListAsync(cancellationToken: ct); - - await SendAsync( - new() - { - Messages = messages - .Select(message => message.ToDto()) - .ToList() - }, - cancellation: ct); - } -} diff --git a/backend/src/Web/Features/Messages/Handlers/Models/MessageDto.cs b/backend/src/Web/Features/Messages/Handlers/Models/MessageDto.cs deleted file mode 100644 index 16b9e78..0000000 --- a/backend/src/Web/Features/Messages/Handlers/Models/MessageDto.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Hutopy.Web.Features.Messages.Data; - -namespace Hutopy.Web.Features.Messages.Handlers.Models; - -public record struct MessageDto( - Guid Id, - Guid SubjectId, - Guid CreatedBy, - string CreatedByName, - string? CreatedByPortraitUrl, - DateTimeOffset CreatedAt, - Guid? ParentId, - string Value -); - -public static class MessageExtensions -{ - public static MessageDto ToDto(this Message message) => - new() - { - Id = message.Id, - ParentId = message.ParentId, - CreatedAt = message.CreatedAt, - CreatedBy = message.CreatedBy, - CreatedByName = message.CreatedByName, - CreatedByPortraitUrl = message.CreatedByPortraitUrl, - SubjectId = message.SubjectId, - Value = message.Value - }; -} diff --git a/backend/src/Web/Features/Users/Data/ApplicationDbContextInitializer.cs b/backend/src/Web/Features/Users/Data/ApplicationDbContextInitializer.cs deleted file mode 100644 index a93926b..0000000 --- a/backend/src/Web/Features/Users/Data/ApplicationDbContextInitializer.cs +++ /dev/null @@ -1,64 +0,0 @@ -using Microsoft.AspNetCore.Identity; - -namespace Hutopy.Web.Features.Users.Data; - -public static class InitializerExtensions -{ - public static async Task InitialiseIdentityDatabaseAsync(this WebApplication app) - { - using var scope = app.Services.CreateScope(); - - var initializer = scope.ServiceProvider.GetRequiredService(); - - await initializer.InitialiseAsync(); - - await initializer.SeedAsync(); - } -} - -public class ApplicationDbContextInitializer( - ILogger logger, - ApplicationDbContext context, - RoleManager roleManager) -{ - public async Task InitialiseAsync() - { - try - { - await context.Database.MigrateAsync(); - } - catch (Exception ex) - { - logger.LogError(ex, "An error occurred while initialising the database."); - throw; - } - } - - public async Task SeedAsync() - { - try - { - await TrySeedAsync(); - } - catch (Exception ex) - { - logger.LogError(ex, "An error occurred while seeding the database."); - throw; - } - } - - private async Task TrySeedAsync() - { - var administratorRole = new IdentityRole(KnownRoles.Administrator); - if (roleManager.Roles.All(r => r.Name != administratorRole.Name)) - { - await roleManager.CreateAsync(administratorRole); - } - - var roleCreator = new IdentityRole(KnownRoles.Creator); - if (roleManager.Roles.All(r => r.Name != roleCreator.Name)) - { - await roleManager.CreateAsync(roleCreator); - } - } -} diff --git a/backend/src/Web/Features/Users/Data/IdentityRole.cs b/backend/src/Web/Features/Users/Data/IdentityRole.cs deleted file mode 100644 index 66b999f..0000000 --- a/backend/src/Web/Features/Users/Data/IdentityRole.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Microsoft.AspNetCore.Identity; - -namespace Hutopy.Web.Features.Users.Data; - -public class IdentityRole : IdentityRole -{ - public IdentityRole() { } - public IdentityRole(string roleName) : base(roleName) { } -} diff --git a/backend/src/Web/Features/Users/Data/IdentityService.cs b/backend/src/Web/Features/Users/Data/IdentityService.cs deleted file mode 100644 index b55ea8c..0000000 --- a/backend/src/Web/Features/Users/Data/IdentityService.cs +++ /dev/null @@ -1,94 +0,0 @@ -using System.Security.Claims; -using Hutopy.Web.Features.Users.Models; - -namespace Hutopy.Web.Features.Users.Data; - -public class IdentityService( - IdentityUserManager userManager, - IHttpContextAccessor contextAccessor -) -{ - public async Task> CreateUserAsync(string email, string userName, string firstName, string lastName, - string password) - { - var applicationUser = new IdentityUser - { - UserName = userName, Email = email, Firstname = firstName, Lastname = lastName - }; - - var response = await userManager.CreateAsync(applicationUser, password); - - if (!response.Succeeded) - { - throw new BadHttpRequestException(response.Errors.First().Description); - } - - var result = new Result(applicationUser.Id, response.Succeeded, response.ToApplicationResult().Errors); - - return result; - } - - private static UserModel BuildModelFrom(IdentityUser response) - { - var userModel = new UserModel - { - Id = response.Id, - Username = response.UserName ?? string.Empty, - PhoneNumber = response.PhoneNumber ?? string.Empty, - Email = response.Email ?? string.Empty, - PortraitUrl = response.PortraitUrl, - Alias = response.Alias, - Firstname = response.Firstname, - Lastname = response.Lastname, - BirthDate = response.BirthDate, - Address = response.Address, - }; - - return userModel; - } - - public async Task FindUserByIdAsync(string id) - { - var user = await userManager.FindByIdAsync(id); - - if (user == null) return null; - - var userModel = BuildModelFrom(user); - - return userModel; - } - - public async Task FindUserByEmailAsync(string email) - { - var response = await userManager.FindByEmailAsync(email); - - if (response == null) return null; - - return BuildModelFrom(response); - } - - public async Task GetCurrentUserAsync() - { - var currentUserId = contextAccessor.HttpContext?.User.FindFirst(ClaimTypes.NameIdentifier)?.Value; - if (string.IsNullOrEmpty(currentUserId)) - { - return null; - } - - return await FindUserByIdAsync(currentUserId); - } - - public async Task> GetCurrentUserRolesAsync() - { - var currentUserModel = await GetCurrentUserAsync(); - - var currentUser = await userManager.FindByIdAsync(currentUserModel.Id.ToString()); - - if (currentUser is null) return []; - - var userRoles = await userManager.GetRolesAsync(currentUser); - - return userRoles; - } - -} diff --git a/backend/src/Web/Features/Users/Data/IdentityUser.cs b/backend/src/Web/Features/Users/Data/IdentityUser.cs deleted file mode 100644 index 3383a0b..0000000 --- a/backend/src/Web/Features/Users/Data/IdentityUser.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using Microsoft.AspNetCore.Identity; - -namespace Hutopy.Web.Features.Users.Data; - -public class IdentityUser : IdentityUser -{ - [MaxLength(255)] public string? Alias { get; set; } - [MaxLength(255)] public string? Firstname { get; set; } - [MaxLength(255)] public string? Lastname { get; set; } - public DateTime? BirthDate { get; set; } - [MaxLength(255)] public string? Address { get; set; } - [MaxLength(2048)] public string? PortraitUrl { get; set; } - [MaxLength(255)] public string? GoogleId { get; set; } - [MaxLength(255)] public string? FacebookId { get; set; } - public string RefreshToken { get; set; } - public DateTime RefreshTokenExpiryTime { get; set; } -} - diff --git a/backend/src/Web/Features/Users/Data/Migrations/20241020183421_Initial.Designer.cs b/backend/src/Web/Features/Users/Data/Migrations/20241020183421_Initial.Designer.cs deleted file mode 100644 index 31f6784..0000000 --- a/backend/src/Web/Features/Users/Data/Migrations/20241020183421_Initial.Designer.cs +++ /dev/null @@ -1,304 +0,0 @@ -// -using System; -using Hutopy.Web.Features.Users.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.Users.Data.Migrations -{ - [DbContext(typeof(ApplicationDbContext))] - [Migration("20241020183421_Initial")] - partial class Initial - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasDefaultSchema("Identity") - .HasAnnotation("ProductVersion", "8.0.10") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("Hutopy.Web.Features.Users.IdentityRole", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("text"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("NormalizedName") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedName") - .IsUnique() - .HasDatabaseName("RoleNameIndex"); - - b.ToTable("AspNetRoles", "Identity"); - }); - - modelBuilder.Entity("Hutopy.Web.Features.Users.IdentityUser", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("AccessFailedCount") - .HasColumnType("integer"); - - b.Property("Address") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.Property("Alias") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.Property("BirthDate") - .HasColumnType("timestamp with time zone"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("text"); - - b.Property("Email") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("EmailConfirmed") - .HasColumnType("boolean"); - - b.Property("Firstname") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.Property("GoogleId") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.Property("Lastname") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.Property("LockoutEnabled") - .HasColumnType("boolean"); - - b.Property("LockoutEnd") - .HasColumnType("timestamp with time zone"); - - b.Property("NormalizedEmail") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("NormalizedUserName") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("PasswordHash") - .HasColumnType("text"); - - b.Property("PhoneNumber") - .HasColumnType("text"); - - b.Property("PhoneNumberConfirmed") - .HasColumnType("boolean"); - - b.Property("PortraitUrl") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.Property("SecurityStamp") - .HasColumnType("text"); - - b.Property("TwoFactorEnabled") - .HasColumnType("boolean"); - - b.Property("UserName") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedEmail") - .HasDatabaseName("EmailIndex"); - - b.HasIndex("NormalizedUserName") - .IsUnique() - .HasDatabaseName("UserNameIndex"); - - b.ToTable("AspNetUsers", "Identity"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ClaimType") - .HasColumnType("text"); - - b.Property("ClaimValue") - .HasColumnType("text"); - - b.Property("RoleId") - .HasColumnType("uuid"); - - b.HasKey("Id"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetRoleClaims", "Identity"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ClaimType") - .HasColumnType("text"); - - b.Property("ClaimValue") - .HasColumnType("text"); - - b.Property("UserId") - .HasColumnType("uuid"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserClaims", "Identity"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.Property("LoginProvider") - .HasColumnType("text"); - - b.Property("ProviderKey") - .HasColumnType("text"); - - b.Property("ProviderDisplayName") - .HasColumnType("text"); - - b.Property("UserId") - .HasColumnType("uuid"); - - b.HasKey("LoginProvider", "ProviderKey"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserLogins", "Identity"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.Property("UserId") - .HasColumnType("uuid"); - - b.Property("RoleId") - .HasColumnType("uuid"); - - b.HasKey("UserId", "RoleId"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetUserRoles", "Identity"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.Property("UserId") - .HasColumnType("uuid"); - - b.Property("LoginProvider") - .HasColumnType("text"); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("Value") - .HasColumnType("text"); - - b.HasKey("UserId", "LoginProvider", "Name"); - - b.ToTable("AspNetUserTokens", "Identity"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.HasOne("Hutopy.Web.Features.Users.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.HasOne("Hutopy.Web.Features.Users.IdentityUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.HasOne("Hutopy.Web.Features.Users.IdentityUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.HasOne("Hutopy.Web.Features.Users.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Hutopy.Web.Features.Users.IdentityUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.HasOne("Hutopy.Web.Features.Users.IdentityUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/backend/src/Web/Features/Users/Data/Migrations/20250217034117_Add_FacebookId.Designer.cs b/backend/src/Web/Features/Users/Data/Migrations/20250217034117_Add_FacebookId.Designer.cs deleted file mode 100644 index 4ac6cd4..0000000 --- a/backend/src/Web/Features/Users/Data/Migrations/20250217034117_Add_FacebookId.Designer.cs +++ /dev/null @@ -1,308 +0,0 @@ -// -using System; -using Hutopy.Web.Features.Users.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.Users.Data.Migrations -{ - [DbContext(typeof(ApplicationDbContext))] - [Migration("20250217034117_Add_FacebookId")] - partial class Add_FacebookId - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasDefaultSchema("Identity") - .HasAnnotation("ProductVersion", "8.0.10") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("Hutopy.Web.Features.Users.IdentityRole", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("text"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("NormalizedName") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedName") - .IsUnique() - .HasDatabaseName("RoleNameIndex"); - - b.ToTable("AspNetRoles", "Identity"); - }); - - modelBuilder.Entity("Hutopy.Web.Features.Users.IdentityUser", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("AccessFailedCount") - .HasColumnType("integer"); - - b.Property("Address") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.Property("Alias") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.Property("BirthDate") - .HasColumnType("timestamp with time zone"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("text"); - - b.Property("Email") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("EmailConfirmed") - .HasColumnType("boolean"); - - b.Property("FacebookId") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.Property("Firstname") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.Property("GoogleId") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.Property("Lastname") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.Property("LockoutEnabled") - .HasColumnType("boolean"); - - b.Property("LockoutEnd") - .HasColumnType("timestamp with time zone"); - - b.Property("NormalizedEmail") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("NormalizedUserName") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("PasswordHash") - .HasColumnType("text"); - - b.Property("PhoneNumber") - .HasColumnType("text"); - - b.Property("PhoneNumberConfirmed") - .HasColumnType("boolean"); - - b.Property("PortraitUrl") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.Property("SecurityStamp") - .HasColumnType("text"); - - b.Property("TwoFactorEnabled") - .HasColumnType("boolean"); - - b.Property("UserName") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedEmail") - .HasDatabaseName("EmailIndex"); - - b.HasIndex("NormalizedUserName") - .IsUnique() - .HasDatabaseName("UserNameIndex"); - - b.ToTable("AspNetUsers", "Identity"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ClaimType") - .HasColumnType("text"); - - b.Property("ClaimValue") - .HasColumnType("text"); - - b.Property("RoleId") - .HasColumnType("uuid"); - - b.HasKey("Id"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetRoleClaims", "Identity"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ClaimType") - .HasColumnType("text"); - - b.Property("ClaimValue") - .HasColumnType("text"); - - b.Property("UserId") - .HasColumnType("uuid"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserClaims", "Identity"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.Property("LoginProvider") - .HasColumnType("text"); - - b.Property("ProviderKey") - .HasColumnType("text"); - - b.Property("ProviderDisplayName") - .HasColumnType("text"); - - b.Property("UserId") - .HasColumnType("uuid"); - - b.HasKey("LoginProvider", "ProviderKey"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserLogins", "Identity"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.Property("UserId") - .HasColumnType("uuid"); - - b.Property("RoleId") - .HasColumnType("uuid"); - - b.HasKey("UserId", "RoleId"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetUserRoles", "Identity"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.Property("UserId") - .HasColumnType("uuid"); - - b.Property("LoginProvider") - .HasColumnType("text"); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("Value") - .HasColumnType("text"); - - b.HasKey("UserId", "LoginProvider", "Name"); - - b.ToTable("AspNetUserTokens", "Identity"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.HasOne("Hutopy.Web.Features.Users.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.HasOne("Hutopy.Web.Features.Users.IdentityUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.HasOne("Hutopy.Web.Features.Users.IdentityUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.HasOne("Hutopy.Web.Features.Users.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Hutopy.Web.Features.Users.IdentityUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.HasOne("Hutopy.Web.Features.Users.IdentityUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/backend/src/Web/Features/Users/Data/Migrations/20250217034117_Add_FacebookId.cs b/backend/src/Web/Features/Users/Data/Migrations/20250217034117_Add_FacebookId.cs deleted file mode 100644 index e3248e3..0000000 --- a/backend/src/Web/Features/Users/Data/Migrations/20250217034117_Add_FacebookId.cs +++ /dev/null @@ -1,31 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Hutopy.Web.Features.Users.Data.Migrations -{ - /// - public partial class Add_FacebookId : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "FacebookId", - schema: "Identity", - table: "AspNetUsers", - type: "character varying(255)", - maxLength: 255, - nullable: true); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "FacebookId", - schema: "Identity", - table: "AspNetUsers"); - } - } -} diff --git a/backend/src/Web/Features/Users/Data/Migrations/20250402025554_FixUrlsTo1024Chars.Designer.cs b/backend/src/Web/Features/Users/Data/Migrations/20250402025554_FixUrlsTo1024Chars.Designer.cs deleted file mode 100644 index b93de58..0000000 --- a/backend/src/Web/Features/Users/Data/Migrations/20250402025554_FixUrlsTo1024Chars.Designer.cs +++ /dev/null @@ -1,308 +0,0 @@ -// -using System; -using Hutopy.Web.Features.Users.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.Users.Data.Migrations -{ - [DbContext(typeof(ApplicationDbContext))] - [Migration("20250402025554_FixUrlsTo1024Chars")] - partial class FixUrlsTo1024Chars - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasDefaultSchema("Identity") - .HasAnnotation("ProductVersion", "9.0.3") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("Hutopy.Web.Features.Users.IdentityRole", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("text"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("NormalizedName") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedName") - .IsUnique() - .HasDatabaseName("RoleNameIndex"); - - b.ToTable("AspNetRoles", "Identity"); - }); - - modelBuilder.Entity("Hutopy.Web.Features.Users.IdentityUser", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("AccessFailedCount") - .HasColumnType("integer"); - - b.Property("Address") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.Property("Alias") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.Property("BirthDate") - .HasColumnType("timestamp with time zone"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("text"); - - b.Property("Email") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("EmailConfirmed") - .HasColumnType("boolean"); - - b.Property("FacebookId") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.Property("Firstname") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.Property("GoogleId") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.Property("Lastname") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.Property("LockoutEnabled") - .HasColumnType("boolean"); - - b.Property("LockoutEnd") - .HasColumnType("timestamp with time zone"); - - b.Property("NormalizedEmail") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("NormalizedUserName") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("PasswordHash") - .HasColumnType("text"); - - b.Property("PhoneNumber") - .HasColumnType("text"); - - b.Property("PhoneNumberConfirmed") - .HasColumnType("boolean"); - - b.Property("PortraitUrl") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)"); - - b.Property("SecurityStamp") - .HasColumnType("text"); - - b.Property("TwoFactorEnabled") - .HasColumnType("boolean"); - - b.Property("UserName") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedEmail") - .HasDatabaseName("EmailIndex"); - - b.HasIndex("NormalizedUserName") - .IsUnique() - .HasDatabaseName("UserNameIndex"); - - b.ToTable("AspNetUsers", "Identity"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ClaimType") - .HasColumnType("text"); - - b.Property("ClaimValue") - .HasColumnType("text"); - - b.Property("RoleId") - .HasColumnType("uuid"); - - b.HasKey("Id"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetRoleClaims", "Identity"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ClaimType") - .HasColumnType("text"); - - b.Property("ClaimValue") - .HasColumnType("text"); - - b.Property("UserId") - .HasColumnType("uuid"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserClaims", "Identity"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.Property("LoginProvider") - .HasColumnType("text"); - - b.Property("ProviderKey") - .HasColumnType("text"); - - b.Property("ProviderDisplayName") - .HasColumnType("text"); - - b.Property("UserId") - .HasColumnType("uuid"); - - b.HasKey("LoginProvider", "ProviderKey"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserLogins", "Identity"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.Property("UserId") - .HasColumnType("uuid"); - - b.Property("RoleId") - .HasColumnType("uuid"); - - b.HasKey("UserId", "RoleId"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetUserRoles", "Identity"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.Property("UserId") - .HasColumnType("uuid"); - - b.Property("LoginProvider") - .HasColumnType("text"); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("Value") - .HasColumnType("text"); - - b.HasKey("UserId", "LoginProvider", "Name"); - - b.ToTable("AspNetUserTokens", "Identity"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.HasOne("Hutopy.Web.Features.Users.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.HasOne("Hutopy.Web.Features.Users.IdentityUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.HasOne("Hutopy.Web.Features.Users.IdentityUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.HasOne("Hutopy.Web.Features.Users.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Hutopy.Web.Features.Users.IdentityUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.HasOne("Hutopy.Web.Features.Users.IdentityUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/backend/src/Web/Features/Users/Data/Migrations/20250402025554_FixUrlsTo1024Chars.cs b/backend/src/Web/Features/Users/Data/Migrations/20250402025554_FixUrlsTo1024Chars.cs deleted file mode 100644 index ffaabcb..0000000 --- a/backend/src/Web/Features/Users/Data/Migrations/20250402025554_FixUrlsTo1024Chars.cs +++ /dev/null @@ -1,42 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Hutopy.Web.Features.Users.Data.Migrations -{ - /// - public partial class FixUrlsTo1024Chars : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AlterColumn( - name: "PortraitUrl", - schema: "Identity", - table: "AspNetUsers", - type: "character varying(1024)", - maxLength: 1024, - nullable: true, - oldClrType: typeof(string), - oldType: "character varying(255)", - oldMaxLength: 255, - oldNullable: true); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.AlterColumn( - name: "PortraitUrl", - schema: "Identity", - table: "AspNetUsers", - type: "character varying(255)", - maxLength: 255, - nullable: true, - oldClrType: typeof(string), - oldType: "character varying(1024)", - oldMaxLength: 1024, - oldNullable: true); - } - } -} diff --git a/backend/src/Web/Features/Users/Data/Migrations/20250403035646_UrlsTo2048Chars.Designer.cs b/backend/src/Web/Features/Users/Data/Migrations/20250403035646_UrlsTo2048Chars.Designer.cs deleted file mode 100644 index cf019b7..0000000 --- a/backend/src/Web/Features/Users/Data/Migrations/20250403035646_UrlsTo2048Chars.Designer.cs +++ /dev/null @@ -1,308 +0,0 @@ -// -using System; -using Hutopy.Web.Features.Users.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.Users.Data.Migrations -{ - [DbContext(typeof(ApplicationDbContext))] - [Migration("20250403035646_UrlsTo2048Chars")] - partial class UrlsTo2048Chars - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasDefaultSchema("Identity") - .HasAnnotation("ProductVersion", "9.0.3") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("Hutopy.Web.Features.Users.IdentityRole", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("text"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("NormalizedName") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedName") - .IsUnique() - .HasDatabaseName("RoleNameIndex"); - - b.ToTable("AspNetRoles", "Identity"); - }); - - modelBuilder.Entity("Hutopy.Web.Features.Users.IdentityUser", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("AccessFailedCount") - .HasColumnType("integer"); - - b.Property("Address") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.Property("Alias") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.Property("BirthDate") - .HasColumnType("timestamp with time zone"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("text"); - - b.Property("Email") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("EmailConfirmed") - .HasColumnType("boolean"); - - b.Property("FacebookId") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.Property("Firstname") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.Property("GoogleId") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.Property("Lastname") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.Property("LockoutEnabled") - .HasColumnType("boolean"); - - b.Property("LockoutEnd") - .HasColumnType("timestamp with time zone"); - - b.Property("NormalizedEmail") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("NormalizedUserName") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("PasswordHash") - .HasColumnType("text"); - - b.Property("PhoneNumber") - .HasColumnType("text"); - - b.Property("PhoneNumberConfirmed") - .HasColumnType("boolean"); - - b.Property("PortraitUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b.Property("SecurityStamp") - .HasColumnType("text"); - - b.Property("TwoFactorEnabled") - .HasColumnType("boolean"); - - b.Property("UserName") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedEmail") - .HasDatabaseName("EmailIndex"); - - b.HasIndex("NormalizedUserName") - .IsUnique() - .HasDatabaseName("UserNameIndex"); - - b.ToTable("AspNetUsers", "Identity"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ClaimType") - .HasColumnType("text"); - - b.Property("ClaimValue") - .HasColumnType("text"); - - b.Property("RoleId") - .HasColumnType("uuid"); - - b.HasKey("Id"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetRoleClaims", "Identity"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ClaimType") - .HasColumnType("text"); - - b.Property("ClaimValue") - .HasColumnType("text"); - - b.Property("UserId") - .HasColumnType("uuid"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserClaims", "Identity"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.Property("LoginProvider") - .HasColumnType("text"); - - b.Property("ProviderKey") - .HasColumnType("text"); - - b.Property("ProviderDisplayName") - .HasColumnType("text"); - - b.Property("UserId") - .HasColumnType("uuid"); - - b.HasKey("LoginProvider", "ProviderKey"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserLogins", "Identity"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.Property("UserId") - .HasColumnType("uuid"); - - b.Property("RoleId") - .HasColumnType("uuid"); - - b.HasKey("UserId", "RoleId"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetUserRoles", "Identity"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.Property("UserId") - .HasColumnType("uuid"); - - b.Property("LoginProvider") - .HasColumnType("text"); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("Value") - .HasColumnType("text"); - - b.HasKey("UserId", "LoginProvider", "Name"); - - b.ToTable("AspNetUserTokens", "Identity"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.HasOne("Hutopy.Web.Features.Users.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.HasOne("Hutopy.Web.Features.Users.IdentityUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.HasOne("Hutopy.Web.Features.Users.IdentityUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.HasOne("Hutopy.Web.Features.Users.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Hutopy.Web.Features.Users.IdentityUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.HasOne("Hutopy.Web.Features.Users.IdentityUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/backend/src/Web/Features/Users/Data/Migrations/20250403035646_UrlsTo2048Chars.cs b/backend/src/Web/Features/Users/Data/Migrations/20250403035646_UrlsTo2048Chars.cs deleted file mode 100644 index 31dfd76..0000000 --- a/backend/src/Web/Features/Users/Data/Migrations/20250403035646_UrlsTo2048Chars.cs +++ /dev/null @@ -1,42 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Hutopy.Web.Features.Users.Data.Migrations -{ - /// - public partial class UrlsTo2048Chars : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AlterColumn( - name: "PortraitUrl", - schema: "Identity", - table: "AspNetUsers", - type: "character varying(2048)", - maxLength: 2048, - nullable: true, - oldClrType: typeof(string), - oldType: "character varying(1024)", - oldMaxLength: 1024, - oldNullable: true); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.AlterColumn( - name: "PortraitUrl", - schema: "Identity", - table: "AspNetUsers", - type: "character varying(1024)", - maxLength: 1024, - nullable: true, - oldClrType: typeof(string), - oldType: "character varying(2048)", - oldMaxLength: 2048, - oldNullable: true); - } - } -} diff --git a/backend/src/Web/Features/Users/Data/Migrations/20250417060553_AddsRefreshToken.cs b/backend/src/Web/Features/Users/Data/Migrations/20250417060553_AddsRefreshToken.cs deleted file mode 100644 index 1ef2cf5..0000000 --- a/backend/src/Web/Features/Users/Data/Migrations/20250417060553_AddsRefreshToken.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Hutopy.Web.Features.Users.Data.Migrations -{ - /// - public partial class AddsRefreshToken : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "RefreshToken", - schema: "Identity", - table: "AspNetUsers", - type: "text", - nullable: false, - defaultValue: ""); - - migrationBuilder.AddColumn( - name: "RefreshTokenExpiryTime", - schema: "Identity", - table: "AspNetUsers", - type: "timestamp with time zone", - nullable: false, - defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified)); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "RefreshToken", - schema: "Identity", - table: "AspNetUsers"); - - migrationBuilder.DropColumn( - name: "RefreshTokenExpiryTime", - schema: "Identity", - table: "AspNetUsers"); - } - } -} diff --git a/backend/src/Web/Features/Users/DependencyInjection.cs b/backend/src/Web/Features/Users/DependencyInjection.cs deleted file mode 100644 index d1948af..0000000 --- a/backend/src/Web/Features/Users/DependencyInjection.cs +++ /dev/null @@ -1,44 +0,0 @@ -using Hutopy.Web.Common.BlobStorage; -using Hutopy.Web.Features.Messages.Data; -using Hutopy.Web.Features.Users.Data; -using Microsoft.AspNetCore.Identity; -using IdentityRole = Hutopy.Web.Features.Users.Data.IdentityRole; -using IdentityUser = Hutopy.Web.Features.Users.Data.IdentityUser; - -namespace Hutopy.Web.Features.Users; - -public static class DependencyInjection -{ - public static WebApplicationBuilder AddIdentityModule( - this WebApplicationBuilder builder, - Action? configureAction = null) - { - builder.Services.AddDbContext(configureAction); - builder.Services.AddScoped(); - - builder.Services.AddDbContext(configureAction); - builder.Services.AddScoped(); - - builder.Services.AddAuthentication() - .AddBearerToken(IdentityConstants.BearerScheme); - - builder.Services.AddAuthorizationBuilder(); - - builder.Services - .AddIdentityCore() - .AddUserManager() - .AddRoles() - .AddEntityFrameworkStores() - .AddApiEndpoints() - .AddDefaultTokenProviders(); - - // Singleton services - builder.Services.AddSingleton(TimeProvider.System); - builder.Services.AddSingleton(); - - // Scoped services - builder.Services.AddScoped(); - - return builder; - } -} diff --git a/backend/src/Web/Properties/launchSettings.json b/backend/src/Web/Properties/launchSettings.json deleted file mode 100644 index 39a9650..0000000 --- a/backend/src/Web/Properties/launchSettings.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:61846", - "sslPort": 44312 - } - }, - "profiles": { - "Hutopy.Web - DEV": { - "commandName": "Project", - "launchBrowser": false, - "applicationUrl": "https://localhost:5001;http://localhost:5000", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": false, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - } - } -} \ No newline at end of file diff --git a/backend/src/Web/appsettings.json b/backend/src/Web/appsettings.json deleted file mode 100644 index ad33e0e..0000000 --- a/backend/src/Web/appsettings.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft": "Information", - "Microsoft.AspNetCore.SpaProxy": "Information", - "Microsoft.Hosting.Lifetime": "Information" - } - }, - "AllowedHosts": "*", - "ConnectionStrings": { - "AzureBlob": "DefaultEndpointsProtocol=https;AccountName=hutopystorage;AccountKey=+3LZ3fgVvsOtaM9iYcsUhSyT2GZT4I6AQegE1FyIqr8NMv5Qa4Fm8GspckP/DCFN3jbFI8TnTLXY+AStWjvCkg==;EndpointSuffix=core.windows.net" - }, - "Authentication": { - "Jwt": { - "Lifetime": "00:30:00", - "Audience": "hutopy", - "Issuer": "https://auth.hutopy.com", - "Key": "b2df428b9929d3ace7c598bbf4e496b2f0b71ab3cd4f94540356cfc35b000000", - "RefreshTokenLifetime": "7.00:00:00", - "RefreshTokenRequireRotation": true - } - }, - "Contents": { - "SlugReservationDuration": "00:05:00" - } -} \ No newline at end of file diff --git a/backend/start-infrastructure.sh b/backend/start-infrastructure.sh deleted file mode 100644 index 520e062..0000000 --- a/backend/start-infrastructure.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/bash - -docker run \ - --cap-add SYS_PTRACE \ - -e 'POSTGRES_USER=sa' \ - -e 'POSTGRES_PASSWORD=P@ssword123!' \ - -p 5432:5432 \ - --name postgres \ - -d postgres:latest \ No newline at end of file diff --git a/backend/stripe.exe b/backend/stripe.exe deleted file mode 100644 index e7dab0e..0000000 Binary files a/backend/stripe.exe and /dev/null differ diff --git a/backend/update-databases.sh b/backend/update-databases.sh deleted file mode 100644 index 49c5ed6..0000000 --- a/backend/update-databases.sh +++ /dev/null @@ -1,30 +0,0 @@ -#!/bin/bash - -dotnet ef database update \ - --startup-project src/Web/Web.csproj \ - --project src/Web/Web.csproj \ - --context Hutopy.Web.Features.Users.Data.ApplicationDbContext \ - --configuration Debug \ - --no-build - -dotnet ef database update \ - --startup-project src/Web/Web.csproj \ - --project src/Web/Web.csproj \ - --context Hutopy.Web.Features.Messages.Data.MessagingDbContext \ - --configuration Debug \ - --no-build - -dotnet ef database update \ - --startup-project src/Web/Web.csproj \ - --project src/Web/Web.csproj \ - --context Hutopy.Web.Features.Contents.Data.ContentDbContext \ - --configuration Debug \ - --no-build - -dotnet ef database update \ - --startup-project src/Web/Web.csproj \ - --project src/Web/Web.csproj \ - --context Hutopy.Web.Features.Memberships.Data.MembershipDbContext \ - --configuration Debug \ - --no-build - diff --git a/frontend/.eslintrc.json b/frontend/.eslintrc.json index 863bfe6..4750a7a 100644 --- a/frontend/.eslintrc.json +++ b/frontend/.eslintrc.json @@ -1,4 +1,5 @@ { + "parser": "vue-eslint-parser", "env": { "browser": true, "es2021": true @@ -9,6 +10,7 @@ "plugin:tailwindcss/recommended" ], "parserOptions": { + "parser": "@typescript-eslint/parser", "ecmaVersion": "latest", "sourceType": "module" }, diff --git a/frontend/package-lock.json b/frontend/package-lock.json index e4f0691..50520ac 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -24,23 +24,27 @@ "vue-advanced-cropper": "^2.8.9", "vue-i18n": "^10.0.7", "vue-router": "^4.2.5", + "vue-toastification": "^2.0.0-rc.5", "vue3-google-login": "^2.0.26", "vuedraggable": "^4.1.0", "vuetify": "^3.5.6" }, "devDependencies": { "@types/webpack-env": "^1.18.8", + "@typescript-eslint/eslint-plugin": "^8.34.0", + "@typescript-eslint/parser": "^8.34.0", "@vitejs/plugin-vue": "^5.0.3", "@vue/eslint-config-prettier": "^10.2.0", "autoprefixer": "^10.4.21", - "eslint": "^8.57.0", + "eslint": "^8.57.1", "eslint-plugin-tailwindcss": "^3.18.0", "eslint-plugin-vue": "^9.22.0", "postcss": "^8.5.3", "prettier": "^3.5.3", "rollup-plugin-visualizer": "^6.0.1", "tailwindcss": "^3.4.17", - "vite": "^6.3.1" + "vite": "^6.3.1", + "vue-eslint-parser": "^10.1.3" } }, "node_modules/@alloc/quick-lru": { @@ -552,14 +556,22 @@ } }, "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, + "node_modules/@eslint/eslintrc/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "engines": { + "node": ">= 4" + } + }, "node_modules/@eslint/eslintrc/node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -594,9 +606,9 @@ } }, "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -1060,9 +1072,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.41.1.tgz", - "integrity": "sha512-NELNvyEWZ6R9QMkiytB4/L4zSEaBC03KIXEghptLGLZWJ6VPrL63ooZQCOnlx36aQPGhzuOMwDerC1Eb2VmrLw==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.43.0.tgz", + "integrity": "sha512-Krjy9awJl6rKbruhQDgivNbD1WuLb8xAclM4IR4cN5pHGAs2oIMMQJEiC3IC/9TZJ+QZkmZhlMO/6MBGxPidpw==", "cpu": [ "arm" ], @@ -1073,9 +1085,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.41.1.tgz", - "integrity": "sha512-DXdQe1BJ6TK47ukAoZLehRHhfKnKg9BjnQYUu9gzhI8Mwa1d2fzxA1aw2JixHVl403bwp1+/o/NhhHtxWJBgEA==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.43.0.tgz", + "integrity": "sha512-ss4YJwRt5I63454Rpj+mXCXicakdFmKnUNxr1dLK+5rv5FJgAxnN7s31a5VchRYxCFWdmnDWKd0wbAdTr0J5EA==", "cpu": [ "arm64" ], @@ -1086,9 +1098,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.41.1.tgz", - "integrity": "sha512-5afxvwszzdulsU2w8JKWwY8/sJOLPzf0e1bFuvcW5h9zsEg+RQAojdW0ux2zyYAz7R8HvvzKCjLNJhVq965U7w==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.43.0.tgz", + "integrity": "sha512-eKoL8ykZ7zz8MjgBenEF2OoTNFAPFz1/lyJ5UmmFSz5jW+7XbH1+MAgCVHy72aG59rbuQLcJeiMrP8qP5d/N0A==", "cpu": [ "arm64" ], @@ -1099,9 +1111,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.41.1.tgz", - "integrity": "sha512-egpJACny8QOdHNNMZKf8xY0Is6gIMz+tuqXlusxquWu3F833DcMwmGM7WlvCO9sB3OsPjdC4U0wHw5FabzCGZg==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.43.0.tgz", + "integrity": "sha512-SYwXJgaBYW33Wi/q4ubN+ldWC4DzQY62S4Ll2dgfr/dbPoF50dlQwEaEHSKrQdSjC6oIe1WgzosoaNoHCdNuMg==", "cpu": [ "x64" ], @@ -1112,9 +1124,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.41.1.tgz", - "integrity": "sha512-DBVMZH5vbjgRk3r0OzgjS38z+atlupJ7xfKIDJdZZL6sM6wjfDNo64aowcLPKIx7LMQi8vybB56uh1Ftck/Atg==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.43.0.tgz", + "integrity": "sha512-SV+U5sSo0yujrjzBF7/YidieK2iF6E7MdF6EbYxNz94lA+R0wKl3SiixGyG/9Klab6uNBIqsN7j4Y/Fya7wAjQ==", "cpu": [ "arm64" ], @@ -1125,9 +1137,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.41.1.tgz", - "integrity": "sha512-3FkydeohozEskBxNWEIbPfOE0aqQgB6ttTkJ159uWOFn42VLyfAiyD9UK5mhu+ItWzft60DycIN1Xdgiy8o/SA==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.43.0.tgz", + "integrity": "sha512-J7uCsiV13L/VOeHJBo5SjasKiGxJ0g+nQTrBkAsmQBIdil3KhPnSE9GnRon4ejX1XDdsmK/l30IYLiAaQEO0Cg==", "cpu": [ "x64" ], @@ -1138,9 +1150,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.41.1.tgz", - "integrity": "sha512-wC53ZNDgt0pqx5xCAgNunkTzFE8GTgdZ9EwYGVcg+jEjJdZGtq9xPjDnFgfFozQI/Xm1mh+D9YlYtl+ueswNEg==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.43.0.tgz", + "integrity": "sha512-gTJ/JnnjCMc15uwB10TTATBEhK9meBIY+gXP4s0sHD1zHOaIh4Dmy1X9wup18IiY9tTNk5gJc4yx9ctj/fjrIw==", "cpu": [ "arm" ], @@ -1151,9 +1163,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.41.1.tgz", - "integrity": "sha512-jwKCca1gbZkZLhLRtsrka5N8sFAaxrGz/7wRJ8Wwvq3jug7toO21vWlViihG85ei7uJTpzbXZRcORotE+xyrLA==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.43.0.tgz", + "integrity": "sha512-ZJ3gZynL1LDSIvRfz0qXtTNs56n5DI2Mq+WACWZ7yGHFUEirHBRt7fyIk0NsCKhmRhn7WAcjgSkSVVxKlPNFFw==", "cpu": [ "arm" ], @@ -1164,9 +1176,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.41.1.tgz", - "integrity": "sha512-g0UBcNknsmmNQ8V2d/zD2P7WWfJKU0F1nu0k5pW4rvdb+BIqMm8ToluW/eeRmxCared5dD76lS04uL4UaNgpNA==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.43.0.tgz", + "integrity": "sha512-8FnkipasmOOSSlfucGYEu58U8cxEdhziKjPD2FIa0ONVMxvl/hmONtX/7y4vGjdUhjcTHlKlDhw3H9t98fPvyA==", "cpu": [ "arm64" ], @@ -1177,9 +1189,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.41.1.tgz", - "integrity": "sha512-XZpeGB5TKEZWzIrj7sXr+BEaSgo/ma/kCgrZgL0oo5qdB1JlTzIYQKel/RmhT6vMAvOdM2teYlAaOGJpJ9lahg==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.43.0.tgz", + "integrity": "sha512-KPPyAdlcIZ6S9C3S2cndXDkV0Bb1OSMsX0Eelr2Bay4EsF9yi9u9uzc9RniK3mcUGCLhWY9oLr6er80P5DE6XA==", "cpu": [ "arm64" ], @@ -1190,9 +1202,9 @@ ] }, "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.41.1.tgz", - "integrity": "sha512-bkCfDJ4qzWfFRCNt5RVV4DOw6KEgFTUZi2r2RuYhGWC8WhCA8lCAJhDeAmrM/fdiAH54m0mA0Vk2FGRPyzI+tw==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.43.0.tgz", + "integrity": "sha512-HPGDIH0/ZzAZjvtlXj6g+KDQ9ZMHfSP553za7o2Odegb/BEfwJcR0Sw0RLNpQ9nC6Gy8s+3mSS9xjZ0n3rhcYg==", "cpu": [ "loong64" ], @@ -1203,9 +1215,9 @@ ] }, "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.41.1.tgz", - "integrity": "sha512-3mr3Xm+gvMX+/8EKogIZSIEF0WUu0HL9di+YWlJpO8CQBnoLAEL/roTCxuLncEdgcfJcvA4UMOf+2dnjl4Ut1A==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.43.0.tgz", + "integrity": "sha512-gEmwbOws4U4GLAJDhhtSPWPXUzDfMRedT3hFMyRAvM9Mrnj+dJIFIeL7otsv2WF3D7GrV0GIewW0y28dOYWkmw==", "cpu": [ "ppc64" ], @@ -1216,9 +1228,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.41.1.tgz", - "integrity": "sha512-3rwCIh6MQ1LGrvKJitQjZFuQnT2wxfU+ivhNBzmxXTXPllewOF7JR1s2vMX/tWtUYFgphygxjqMl76q4aMotGw==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.43.0.tgz", + "integrity": "sha512-XXKvo2e+wFtXZF/9xoWohHg+MuRnvO29TI5Hqe9xwN5uN8NKUYy7tXUG3EZAlfchufNCTHNGjEx7uN78KsBo0g==", "cpu": [ "riscv64" ], @@ -1229,9 +1241,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.41.1.tgz", - "integrity": "sha512-LdIUOb3gvfmpkgFZuccNa2uYiqtgZAz3PTzjuM5bH3nvuy9ty6RGc/Q0+HDFrHrizJGVpjnTZ1yS5TNNjFlklw==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.43.0.tgz", + "integrity": "sha512-ruf3hPWhjw6uDFsOAzmbNIvlXFXlBQ4nk57Sec8E8rUxs/AI4HD6xmiiasOOx/3QxS2f5eQMKTAwk7KHwpzr/Q==", "cpu": [ "riscv64" ], @@ -1242,9 +1254,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.41.1.tgz", - "integrity": "sha512-oIE6M8WC9ma6xYqjvPhzZYk6NbobIURvP/lEbh7FWplcMO6gn7MM2yHKA1eC/GvYwzNKK/1LYgqzdkZ8YFxR8g==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.43.0.tgz", + "integrity": "sha512-QmNIAqDiEMEvFV15rsSnjoSmO0+eJLoKRD9EAa9rrYNwO/XRCtOGM3A5A0X+wmG+XRrw9Fxdsw+LnyYiZWWcVw==", "cpu": [ "s390x" ], @@ -1255,9 +1267,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.41.1.tgz", - "integrity": "sha512-cWBOvayNvA+SyeQMp79BHPK8ws6sHSsYnK5zDcsC3Hsxr1dgTABKjMnMslPq1DvZIp6uO7kIWhiGwaTdR4Og9A==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.43.0.tgz", + "integrity": "sha512-jAHr/S0iiBtFyzjhOkAics/2SrXE092qyqEg96e90L3t9Op8OTzS6+IX0Fy5wCt2+KqeHAkti+eitV0wvblEoQ==", "cpu": [ "x64" ], @@ -1268,9 +1280,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.41.1.tgz", - "integrity": "sha512-y5CbN44M+pUCdGDlZFzGGBSKCA4A/J2ZH4edTYSSxFg7ce1Xt3GtydbVKWLlzL+INfFIZAEg1ZV6hh9+QQf9YQ==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.43.0.tgz", + "integrity": "sha512-3yATWgdeXyuHtBhrLt98w+5fKurdqvs8B53LaoKD7P7H7FKOONLsBVMNl9ghPQZQuYcceV5CDyPfyfGpMWD9mQ==", "cpu": [ "x64" ], @@ -1281,9 +1293,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.41.1.tgz", - "integrity": "sha512-lZkCxIrjlJlMt1dLO/FbpZbzt6J/A8p4DnqzSa4PWqPEUUUnzXLeki/iyPLfV0BmHItlYgHUqJe+3KiyydmiNQ==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.43.0.tgz", + "integrity": "sha512-wVzXp2qDSCOpcBCT5WRWLmpJRIzv23valvcTwMHEobkjippNf+C3ys/+wf07poPkeNix0paTNemB2XrHr2TnGw==", "cpu": [ "arm64" ], @@ -1294,9 +1306,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.41.1.tgz", - "integrity": "sha512-+psFT9+pIh2iuGsxFYYa/LhS5MFKmuivRsx9iPJWNSGbh2XVEjk90fmpUEjCnILPEPJnikAU6SFDiEUyOv90Pg==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.43.0.tgz", + "integrity": "sha512-fYCTEyzf8d+7diCw8b+asvWDCLMjsCEA8alvtAutqJOJp/wL5hs1rWSqJ1vkjgW0L2NB4bsYJrpKkiIPRR9dvw==", "cpu": [ "ia32" ], @@ -1307,9 +1319,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.41.1.tgz", - "integrity": "sha512-Wq2zpapRYLfi4aKxf2Xff0tN+7slj2d4R87WEzqw7ZLsVvO5zwYCIuEGSZYiK41+GlwUo1HiR+GdkLEJnCKTCw==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.43.0.tgz", + "integrity": "sha512-SnGhLiE5rlK0ofq8kzuDkM0g7FN1s5VYY+YSMTibP7CqShxCQvqtNxTARS4xX4PFJfHjG0ZQYX9iGzI3FQh5Aw==", "cpu": [ "x64" ], @@ -1339,9 +1351,9 @@ } }, "node_modules/@types/estree": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", - "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==" + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==" }, "node_modules/@types/lodash": { "version": "4.17.17", @@ -1359,13 +1371,66 @@ "integrity": "sha512-G9eAoJRMLjcvN4I08wB5I7YofOb/kaJNd5uoCMX+LbKXTPCF+ZIHuqTnFaK9Jz1rgs035f9JUPUhNFtqgucy/A==", "dev": true }, - "node_modules/@typescript-eslint/project-service": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.33.1.tgz", - "integrity": "sha512-DZR0efeNklDIHHGRpMpR5gJITQpu6tLr9lDJnKdONTC7vvzOlLAG/wcfxcdxEWrbiZApcoBCzXqU/Z458Za5Iw==", + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.34.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.34.0.tgz", + "integrity": "sha512-QXwAlHlbcAwNlEEMKQS2RCgJsgXrTJdjXT08xEgbPFa2yYQgVjBymxP5DrfrE7X7iodSzd9qBUHUycdyVJTW1w==", + "dev": true, "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.33.1", - "@typescript-eslint/types": "^8.33.1", + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.34.0", + "@typescript-eslint/type-utils": "8.34.0", + "@typescript-eslint/utils": "8.34.0", + "@typescript-eslint/visitor-keys": "8.34.0", + "graphemer": "^1.4.0", + "ignore": "^7.0.0", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.34.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.34.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.34.0.tgz", + "integrity": "sha512-vxXJV1hVFx3IXz/oy2sICsJukaBrtDEQSBiV48/YIV5KWjX1dO+bcIr/kCPrW6weKXvsaGKFNlwH0v2eYdRRbA==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "8.34.0", + "@typescript-eslint/types": "8.34.0", + "@typescript-eslint/typescript-estree": "8.34.0", + "@typescript-eslint/visitor-keys": "8.34.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.34.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.34.0.tgz", + "integrity": "sha512-iEgDALRf970/B2YExmtPMPF54NenZUf4xpL3wsCRx/lgjz6ul/l13R81ozP/ZNuXfnLCS+oPmG7JIxfdNYKELw==", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.34.0", + "@typescript-eslint/types": "^8.34.0", "debug": "^4.3.4" }, "engines": { @@ -1380,12 +1445,12 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.33.1.tgz", - "integrity": "sha512-dM4UBtgmzHR9bS0Rv09JST0RcHYearoEoo3pG5B6GoTR9XcyeqX87FEhPo+5kTvVfKCvfHaHrcgeJQc6mrDKrA==", + "version": "8.34.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.34.0.tgz", + "integrity": "sha512-9Ac0X8WiLykl0aj1oYQNcLZjHgBojT6cW68yAgZ19letYu+Hxd0rE0veI1XznSSst1X5lwnxhPbVdwjDRIomRw==", "dependencies": { - "@typescript-eslint/types": "8.33.1", - "@typescript-eslint/visitor-keys": "8.33.1" + "@typescript-eslint/types": "8.34.0", + "@typescript-eslint/visitor-keys": "8.34.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1396,9 +1461,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.33.1.tgz", - "integrity": "sha512-STAQsGYbHCF0/e+ShUQ4EatXQ7ceh3fBCXkNU7/MZVKulrlq1usH7t2FhxvCpuCi5O5oi1vmVaAjrGeL71OK1g==", + "version": "8.34.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.34.0.tgz", + "integrity": "sha512-+W9VYHKFIzA5cBeooqQxqNriAP0QeQ7xTiDuIOr71hzgffm3EL2hxwWBIIj4GuofIbKxGNarpKqIq6Q6YrShOA==", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -1410,10 +1475,33 @@ "typescript": ">=4.8.4 <5.9.0" } }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.34.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.34.0.tgz", + "integrity": "sha512-n7zSmOcUVhcRYC75W2pnPpbO1iwhJY3NLoHEtbJwJSNlVAZuwqu05zY3f3s2SDWWDSo9FdN5szqc73DCtDObAg==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "8.34.0", + "@typescript-eslint/utils": "8.34.0", + "debug": "^4.3.4", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, "node_modules/@typescript-eslint/types": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.33.1.tgz", - "integrity": "sha512-xid1WfizGhy/TKMTwhtVOgalHwPtV8T32MS9MaH50Cwvz6x6YqRIPdD2WvW0XaqOzTV9p5xdLY0h/ZusU5Lokg==", + "version": "8.34.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.34.0.tgz", + "integrity": "sha512-9V24k/paICYPniajHfJ4cuAWETnt7Ssy+R0Rbcqo5sSFr3QEZ/8TSoUi9XeXVBGXCaLtwTOKSLGcInCAvyZeMA==", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -1423,14 +1511,14 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.33.1.tgz", - "integrity": "sha512-+s9LYcT8LWjdYWu7IWs7FvUxpQ/DGkdjZeE/GGulHvv8rvYwQvVaUZ6DE+j5x/prADUgSbbCWZ2nPI3usuVeOA==", + "version": "8.34.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.34.0.tgz", + "integrity": "sha512-rOi4KZxI7E0+BMqG7emPSK1bB4RICCpF7QD3KCLXn9ZvWoESsOMlHyZPAHyG04ujVplPaHbmEvs34m+wjgtVtg==", "dependencies": { - "@typescript-eslint/project-service": "8.33.1", - "@typescript-eslint/tsconfig-utils": "8.33.1", - "@typescript-eslint/types": "8.33.1", - "@typescript-eslint/visitor-keys": "8.33.1", + "@typescript-eslint/project-service": "8.34.0", + "@typescript-eslint/tsconfig-utils": "8.34.0", + "@typescript-eslint/types": "8.34.0", + "@typescript-eslint/visitor-keys": "8.34.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -1449,12 +1537,35 @@ "typescript": ">=4.8.4 <5.9.0" } }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.33.1.tgz", - "integrity": "sha512-3i8NrFcZeeDHJ+7ZUuDkGT+UHq+XoFGsymNK2jZCOHcfEzRQ0BdpRtdpSx/Iyf3MHLWIcLS0COuOPibKQboIiQ==", + "node_modules/@typescript-eslint/utils": { + "version": "8.34.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.34.0.tgz", + "integrity": "sha512-8L4tWatGchV9A1cKbjaavS6mwYwp39jql8xUmIIKJdm+qiaeHy5KMKlBrf30akXAWBzn2SqKsNOtSENWUwg7XQ==", + "dev": true, "dependencies": { - "@typescript-eslint/types": "8.33.1", + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/scope-manager": "8.34.0", + "@typescript-eslint/types": "8.34.0", + "@typescript-eslint/typescript-estree": "8.34.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.34.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.34.0.tgz", + "integrity": "sha512-qHV7pW7E85A0x6qyrFn+O+q1k1p3tQCsqIZ1KZ5ESLXY57aTvUd3/a4rdPTeXisvhXn2VQG0VSKUqs8KHF2zcA==", + "dependencies": { + "@typescript-eslint/types": "8.34.0", "eslint-visitor-keys": "^4.2.0" }, "engines": { @@ -1466,9 +1577,9 @@ } }, "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -1547,11 +1658,11 @@ } }, "node_modules/@vee-validate/rules": { - "version": "4.15.0", - "resolved": "https://registry.npmjs.org/@vee-validate/rules/-/rules-4.15.0.tgz", - "integrity": "sha512-Cvll7r98O5tU6ew2AUifVpdhNnTkMTY7+D9N++J7apQXRXWfHMe4tNvjo4TJpKUPCtfLHbdTY/DCquDRc+uH4w==", + "version": "4.15.1", + "resolved": "https://registry.npmjs.org/@vee-validate/rules/-/rules-4.15.1.tgz", + "integrity": "sha512-2TGXq2MYt21nT8ysuxyFY/LBVQDcMPKn1hY0w3uIDmG333vzBkOtXAylmvrS93AsJ7N5coHMJjcCGG4JxCO2hQ==", "dependencies": { - "vee-validate": "4.15.0" + "vee-validate": "4.15.1" } }, "node_modules/@vitejs/plugin-vue": { @@ -1784,9 +1895,9 @@ } }, "node_modules/acorn": { - "version": "8.14.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", - "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "bin": { "acorn": "bin/acorn" }, @@ -2006,9 +2117,9 @@ "dev": true }, "node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dependencies": { "balanced-match": "^1.0.0" } @@ -2105,9 +2216,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001721", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001721.tgz", - "integrity": "sha512-cOuvmUVtKrtEaoKiO0rSc29jcjwMwX5tOHDy4MgVFEWiUXj4uBMJkwI8MDySkgXidpMiHUcviogAvFi4pA2hDQ==", + "version": "1.0.30001722", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001722.tgz", + "integrity": "sha512-DCQHBBZtiK6JVkAGw7drvAMK0Q0POD/xZvEmDp6baiMMP6QXXk9HpD6mNYBZWhOPG6LvIDb82ITqtWjhDckHCA==", "dev": true, "funding": [ { @@ -2252,9 +2363,9 @@ } }, "node_modules/core-js": { - "version": "3.42.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.42.0.tgz", - "integrity": "sha512-Sz4PP4ZA+Rq4II21qkNqOEDTDrCvcANId3xpIgB34NDkWc3UduWj2dqEtN9yZIq8Dk3HyPI33x9sqqU5C8sr0g==", + "version": "3.43.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.43.0.tgz", + "integrity": "sha512-N6wEbTTZSYOY2rYAn85CuvWWkCK6QweMn7/4Nr3w+gDBeBhk/x4EJeY6FPo4QzDoJZxVTv8U7CMvgWk6pOHHqA==", "hasInstallScript": true, "funding": { "type": "opencollective", @@ -2386,9 +2497,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.165", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.165.tgz", - "integrity": "sha512-naiMx1Z6Nb2TxPU6fiFrUrDTjyPMLdTtaOd2oLmG8zVSg2hCWGkhPyxwk+qRmZ1ytwVqUv0u7ZcDA5+ALhaUtw==", + "version": "1.5.166", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.166.tgz", + "integrity": "sha512-QPWqHL0BglzPYyJJ1zSSmwFFL6MFXhbACOCcsCdUMCkzPdS9/OIBVxg516X/Ado2qwAq8k0nJJ7phQPCqiaFAw==", "dev": true }, "node_modules/emoji-regex": { @@ -2666,6 +2777,30 @@ "eslint": "^6.2.0 || ^7.0.0 || ^8.0.0 || ^9.0.0" } }, + "node_modules/eslint-plugin-vue/node_modules/vue-eslint-parser": { + "version": "9.4.3", + "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.4.3.tgz", + "integrity": "sha512-2rYRLWlIpaiN8xbPiDyXZXRgLGOtWxERV7ND5fFAv5qo1D2N9Fu9MNajBNc6o13lZ+24DAWCkQCvj4klgmcITg==", + "dev": true, + "dependencies": { + "debug": "^4.3.4", + "eslint-scope": "^7.1.1", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.3.1", + "esquery": "^1.4.0", + "lodash": "^4.17.21", + "semver": "^7.3.6" + }, + "engines": { + "node": "^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=6.0.0" + } + }, "node_modules/eslint-scope": { "version": "7.2.2", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", @@ -2693,14 +2828,22 @@ } }, "node_modules/eslint/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, + "node_modules/eslint/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "engines": { + "node": ">= 4" + } + }, "node_modules/eslint/node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -2870,9 +3013,9 @@ "integrity": "sha512-66peeFF3epjeQR5l+aaB03aF8bEI+bQf+wMq3fjnUEyQ1d6qR+Z6rGHq9bSxuu2kzEtWG+0PfU5x2SiVEcYy0w==" }, "node_modules/fdir": { - "version": "6.4.5", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.5.tgz", - "integrity": "sha512-4BG7puHpVsIYxZUbiUE3RqGloLaSSwzYie5jvasC4LWuBWzZawynvYouhjbQKw2JuIGYdm0DzIxl8iVidKlUEw==", + "version": "6.4.6", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", + "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", "dev": true, "peerDependencies": { "picomatch": "^3 || ^4" @@ -3129,9 +3272,9 @@ } }, "node_modules/glob/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -3342,9 +3485,10 @@ } }, "node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, "engines": { "node": ">= 4" } @@ -4191,9 +4335,9 @@ "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==" }, "node_modules/postcss": { - "version": "8.5.4", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.4.tgz", - "integrity": "sha512-QSa9EBe+uwlGTFmHsPKokv3B/oEMQZxfqW0QqNCyhpa6mB1afzulwn8hihglqAb2pOw+BJgNlmXQ8la2VeHB7w==", + "version": "8.5.5", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.5.tgz", + "integrity": "sha512-d/jtm+rdNT8tpXuHY5MMtcbJFBkhXE6593XVR9UoGCH8jSFGci7jGvMGH5RYd5PBJW+00NZQt6gf7CbagJCrhg==", "funding": [ { "type": "opencollective", @@ -4516,9 +4660,9 @@ } }, "node_modules/rollup": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.41.1.tgz", - "integrity": "sha512-cPmwD3FnFv8rKMBc1MxWCwVQFxwf1JEmSX3iQXrRVVG15zerAIXRjMFVWnd5Q5QvgKF7Aj+5ykXFhUl+QGnyOw==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.43.0.tgz", + "integrity": "sha512-wdN2Kd3Twh8MAEOEJZsuxuLKCsBEo4PVNLK6tQWAn10VhsVewQLzcucMgLolRlhFybGxfclbPeEYBaP6RvUFGg==", "devOptional": true, "dependencies": { "@types/estree": "1.0.7" @@ -4531,33 +4675,33 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.41.1", - "@rollup/rollup-android-arm64": "4.41.1", - "@rollup/rollup-darwin-arm64": "4.41.1", - "@rollup/rollup-darwin-x64": "4.41.1", - "@rollup/rollup-freebsd-arm64": "4.41.1", - "@rollup/rollup-freebsd-x64": "4.41.1", - "@rollup/rollup-linux-arm-gnueabihf": "4.41.1", - "@rollup/rollup-linux-arm-musleabihf": "4.41.1", - "@rollup/rollup-linux-arm64-gnu": "4.41.1", - "@rollup/rollup-linux-arm64-musl": "4.41.1", - "@rollup/rollup-linux-loongarch64-gnu": "4.41.1", - "@rollup/rollup-linux-powerpc64le-gnu": "4.41.1", - "@rollup/rollup-linux-riscv64-gnu": "4.41.1", - "@rollup/rollup-linux-riscv64-musl": "4.41.1", - "@rollup/rollup-linux-s390x-gnu": "4.41.1", - "@rollup/rollup-linux-x64-gnu": "4.41.1", - "@rollup/rollup-linux-x64-musl": "4.41.1", - "@rollup/rollup-win32-arm64-msvc": "4.41.1", - "@rollup/rollup-win32-ia32-msvc": "4.41.1", - "@rollup/rollup-win32-x64-msvc": "4.41.1", + "@rollup/rollup-android-arm-eabi": "4.43.0", + "@rollup/rollup-android-arm64": "4.43.0", + "@rollup/rollup-darwin-arm64": "4.43.0", + "@rollup/rollup-darwin-x64": "4.43.0", + "@rollup/rollup-freebsd-arm64": "4.43.0", + "@rollup/rollup-freebsd-x64": "4.43.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.43.0", + "@rollup/rollup-linux-arm-musleabihf": "4.43.0", + "@rollup/rollup-linux-arm64-gnu": "4.43.0", + "@rollup/rollup-linux-arm64-musl": "4.43.0", + "@rollup/rollup-linux-loongarch64-gnu": "4.43.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.43.0", + "@rollup/rollup-linux-riscv64-gnu": "4.43.0", + "@rollup/rollup-linux-riscv64-musl": "4.43.0", + "@rollup/rollup-linux-s390x-gnu": "4.43.0", + "@rollup/rollup-linux-x64-gnu": "4.43.0", + "@rollup/rollup-linux-x64-musl": "4.43.0", + "@rollup/rollup-win32-arm64-msvc": "4.43.0", + "@rollup/rollup-win32-ia32-msvc": "4.43.0", + "@rollup/rollup-win32-x64-msvc": "4.43.0", "fsevents": "~2.3.2" } }, "node_modules/rollup-plugin-visualizer": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/rollup-plugin-visualizer/-/rollup-plugin-visualizer-6.0.1.tgz", - "integrity": "sha512-NjlGElvLXCSZSAi3gNRZbfX3qlQbQcJ9TW97c5JpqfVwMhttj9YwEdPwcvbKj91RnMX2PWAjonvSEv6UEYtnRQ==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/rollup-plugin-visualizer/-/rollup-plugin-visualizer-6.0.3.tgz", + "integrity": "sha512-ZU41GwrkDcCpVoffviuM9Clwjy5fcUxlz0oMoTXTYsK+tcIFzbdacnrr2n8TXcHxbGKKXtOdjxM2HUS4HjkwIw==", "dev": true, "dependencies": { "open": "^8.0.0", @@ -4572,7 +4716,7 @@ "node": ">=18" }, "peerDependencies": { - "rolldown": "1.x", + "rolldown": "1.x || ^1.0.0-beta", "rollup": "2.x || 3.x || 4.x" }, "peerDependenciesMeta": { @@ -4593,6 +4737,12 @@ "node": ">= 8" } }, + "node_modules/rollup/node_modules/@types/estree": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", + "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", + "devOptional": true + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -5167,9 +5317,9 @@ } }, "node_modules/vee-validate": { - "version": "4.15.0", - "resolved": "https://registry.npmjs.org/vee-validate/-/vee-validate-4.15.0.tgz", - "integrity": "sha512-PGJh1QCFwCBjbHu5aN6vB8macYVWrajbDvgo1Y/8fz9n/RVIkLmZCJDpUgu7+mUmCOPMxeyq7vXUOhbwAqdXcA==", + "version": "4.15.1", + "resolved": "https://registry.npmjs.org/vee-validate/-/vee-validate-4.15.1.tgz", + "integrity": "sha512-DkFsiTwEKau8VIxyZBGdO6tOudD+QoUBPuHj3e6QFqmbfCRj1ArmYWue9lEp6jLSWBIw4XPlDLjFIZNLdRAMSg==", "dependencies": { "@vue/devtools-api": "^7.5.2", "type-fest": "^4.8.3" @@ -5334,27 +5484,72 @@ } }, "node_modules/vue-eslint-parser": { - "version": "9.4.3", - "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.4.3.tgz", - "integrity": "sha512-2rYRLWlIpaiN8xbPiDyXZXRgLGOtWxERV7ND5fFAv5qo1D2N9Fu9MNajBNc6o13lZ+24DAWCkQCvj4klgmcITg==", + "version": "10.1.3", + "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-10.1.3.tgz", + "integrity": "sha512-dbCBnd2e02dYWsXoqX5yKUZlOt+ExIpq7hmHKPb5ZqKcjf++Eo0hMseFTZMLKThrUk61m+Uv6A2YSBve6ZvuDQ==", "dev": true, "dependencies": { - "debug": "^4.3.4", - "eslint-scope": "^7.1.1", - "eslint-visitor-keys": "^3.3.0", - "espree": "^9.3.1", - "esquery": "^1.4.0", + "debug": "^4.4.0", + "eslint-scope": "^8.2.0", + "eslint-visitor-keys": "^4.2.0", + "espree": "^10.3.0", + "esquery": "^1.6.0", "lodash": "^4.17.21", - "semver": "^7.3.6" + "semver": "^7.6.3" }, "engines": { - "node": "^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://github.com/sponsors/mysticatea" }, "peerDependencies": { - "eslint": ">=6.0.0" + "eslint": "^8.57.0 || ^9.0.0" + } + }, + "node_modules/vue-eslint-parser/node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/vue-eslint-parser/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/vue-eslint-parser/node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/vue-i18n": { @@ -5401,6 +5596,14 @@ "vue": "^3.2.0" } }, + "node_modules/vue-toastification": { + "version": "2.0.0-rc.5", + "resolved": "https://registry.npmjs.org/vue-toastification/-/vue-toastification-2.0.0-rc.5.tgz", + "integrity": "sha512-q73e5jy6gucEO/U+P48hqX+/qyXDozAGmaGgLFm5tXX4wJBcVsnGp4e/iJqlm9xzHETYOilUuwOUje2Qg1JdwA==", + "peerDependencies": { + "vue": "^3.0.2" + } + }, "node_modules/vue3-google-login": { "version": "2.0.33", "resolved": "https://registry.npmjs.org/vue3-google-login/-/vue3-google-login-2.0.33.tgz", @@ -5421,9 +5624,9 @@ } }, "node_modules/vuetify": { - "version": "3.8.8", - "resolved": "https://registry.npmjs.org/vuetify/-/vuetify-3.8.8.tgz", - "integrity": "sha512-EPFynvxh72PBgUVZnGpfYfGluz8dz/tXM1OzjszFOK7ywqS+bAm8K9jJq0MIlAG8HKE7gBFQwCJGkzIyuUDipA==", + "version": "3.8.9", + "resolved": "https://registry.npmjs.org/vuetify/-/vuetify-3.8.9.tgz", + "integrity": "sha512-X9kCxeqf7w5sca2Mfn4NCVsDDimi81jxKyqsZHjW0XG/rTdtwRFKttxOcv0Mmi+67ulPjDZywA7pBFK0rxoafA==", "engines": { "node": "^12.20 || >=14.13" }, diff --git a/frontend/package.json b/frontend/package.json index 3217f25..956c92e 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -25,22 +25,26 @@ "vue-advanced-cropper": "^2.8.9", "vue-i18n": "^10.0.7", "vue-router": "^4.2.5", + "vue-toastification": "^2.0.0-rc.5", "vue3-google-login": "^2.0.26", "vuedraggable": "^4.1.0", "vuetify": "^3.5.6" }, "devDependencies": { "@types/webpack-env": "^1.18.8", + "@typescript-eslint/eslint-plugin": "^8.34.0", + "@typescript-eslint/parser": "^8.34.0", "@vitejs/plugin-vue": "^5.0.3", "@vue/eslint-config-prettier": "^10.2.0", "autoprefixer": "^10.4.21", - "eslint": "^8.57.0", + "eslint": "^8.57.1", "eslint-plugin-tailwindcss": "^3.18.0", "eslint-plugin-vue": "^9.22.0", "postcss": "^8.5.3", "prettier": "^3.5.3", "rollup-plugin-visualizer": "^6.0.1", "tailwindcss": "^3.4.17", - "vite": "^6.3.1" + "vite": "^6.3.1", + "vue-eslint-parser": "^10.1.3" } } diff --git a/frontend/src/assets/hutopy-icon-white-circle.png b/frontend/src/assets/hutopy-icon-white-circle.png new file mode 100644 index 0000000..87730fa Binary files /dev/null and b/frontend/src/assets/hutopy-icon-white-circle.png differ diff --git a/frontend/src/main.js b/frontend/src/main.js index 9d3db68..b99bc6d 100644 --- a/frontend/src/main.js +++ b/frontend/src/main.js @@ -5,17 +5,19 @@ import { createPinia } from 'pinia'; import 'vuetify/styles'; import { createVuetify } from 'vuetify'; import { aliases, mdi } from 'vuetify/iconsets/mdi-svg'; -import { VDialog, VApp, VBtn, VProgressLinear, VProgressCircular, VIcon, VTextField, VSnackbar, VForm, VTextarea } from 'vuetify/components'; +import { VDialog, VApp, VBtn, VProgressLinear, VProgressCircular, VIcon, VTextField, VSnackbar, VForm, VTextarea, VAlert } from 'vuetify/components'; import { } from 'vuetify/directives'; import vueGoogleOauth from 'vue3-google-login'; import { useAuthStore } from "@/stores/authStore.js"; import { useUserProfileStore } from "@/stores/userProfileStore.js"; import { useCreatorProfileStore } from "@/stores/creatorProfileStore.js"; +import Toast, { POSITION } from 'vue-toastification'; +import 'vue-toastification/dist/index.css'; import './assets/main.css'; const vuetify = createVuetify({ components: { - VDialog, VApp, VBtn, VProgressLinear, VProgressCircular, VIcon, VTextField, VSnackbar, VForm, VTextarea + VDialog, VApp, VBtn, VProgressLinear, VProgressCircular, VIcon, VTextField, VSnackbar, VForm, VTextarea, VAlert }, directives: { }, @@ -50,7 +52,10 @@ const app = createApp(App) .use(i18n) .use(vueGoogleOauth, { clientId: import.meta.env.VITE_GOOGLE_CLIENT_ID, - }); + }) + .use(Toast, { + position: POSITION.TOP_CENTER, + }); useAuthStore(); useUserProfileStore(); diff --git a/frontend/src/views/PaymentCompleted.vue b/frontend/src/views/PaymentCompleted.vue index 98c9c58..b5e7072 100644 --- a/frontend/src/views/PaymentCompleted.vue +++ b/frontend/src/views/PaymentCompleted.vue @@ -1,147 +1,155 @@ { - "en": { - "title": "Payment Successful!", - "message": "Your payment has been processed successfully.", - "usernameDefault": "the creator", - "receipt": "A receipt has been sent to your email.", - "continue": "Continue to", - "returnToCreator": "Return to creator page" - }, - "fr": { - "title": "Paiement réussi !", - "message": "Votre paiement a été traité avec succès.", - "usernameDefault": "le créateur", - "receipt": "Un reçu a été envoyé à votre email.", - "continue": "Continuer vers", - "returnToCreator": "Retourner à la page du créateur" - }, - "es": { - "title": "¡Pago exitoso!", - "message": "Su pago ha sido procesado con éxito.", - "usernameDefault": "el creador", - "receipt": "Se ha enviado un recibo a su correo electrónico.", - "continue": "Continuar a", - "returnToCreator": "Volver a la página del creador" - } + "en": { + "title": "{creatorName} thanks you!", + "message": "Your payment has been processed successfully.", + "usernameDefault": "The creator", + "receipt": "A receipt has been sent to your email.", + "returnToCreator": "Return to creator page" + }, + "fr": { + "title": "{creatorName} vous remercie !", + "message": "Votre paiement a été traité avec succès.", + "usernameDefault": "Le créateur", + "receipt": "Un reçu a été envoyé à votre email.", + "returnToCreator": "Retourner à la page du créateur" + }, + "es": { + "title": "¡{creatorName} te agradece!", + "message": "Su pago ha sido procesado con éxito.", + "usernameDefault": "El creador", + "receipt": "Se ha enviado un recibo a su correo electrónico.", + "returnToCreator": "Volver a la página del creador" + } } diff --git a/frontend/src/views/PaymentFailed.vue b/frontend/src/views/PaymentFailed.vue index e353ae6..44668eb 100644 --- a/frontend/src/views/PaymentFailed.vue +++ b/frontend/src/views/PaymentFailed.vue @@ -1,115 +1,125 @@ { - "en": { - "title": "Payment Failed", - "message": "We couldn't process your payment.", - "retry": "Try Again", - "returnToCreator": "Return to creator page" - }, - "fr": { - "title": "Échec du paiement", - "message": "Nous n'avons pas pu traiter votre paiement.", - "retry": "Réessayer", - "returnToCreator": "Retourner à la page du créateur" - }, - "es": { - "title": "Pago fallido", - "message": "No pudimos procesar su pago.", - "retry": "Intentar de nuevo", - "returnToCreator": "Volver a la página del creador" - } + "en": { + "title": "Payment Failed", + "message": "We couldn't process your payment.", + "retry": "Try Again", + "returnToCreator": "Return to creator page" + }, + "fr": { + "title": "Échec du paiement", + "message": "Nous n'avons pas pu traiter votre paiement.", + "retry": "Réessayer", + "returnToCreator": "Retourner à la page du créateur" + }, + "es": { + "title": "Pago fallido", + "message": "No pudimos procesar su pago.", + "retry": "Intentar de nuevo", + "returnToCreator": "Volver a la página del creador" + } } diff --git a/frontend/src/views/ResetPasswordView.vue b/frontend/src/views/ResetPasswordView.vue index 4140011..0cc4e8b 100644 --- a/frontend/src/views/ResetPasswordView.vue +++ b/frontend/src/views/ResetPasswordView.vue @@ -1,229 +1,277 @@ { - "en": { - "title": "Reset Your Password", - "newPassword": "New Password", - "confirmPassword": "Confirm Password", - "passwordRequirements": "Password must be at least 8 characters", - "resetPassword": "Reset Password", - "passwordResetSuccess": "Your password has been reset successfully!", - "proceedToLogin": "Proceed to Login", - "passwordsDoNotMatch": "Passwords do not match", - "passwordTooShort": "Password must be at least 8 characters long", - "resetFailed": "Password reset failed. Please try again or request a new reset link.", - "invalidResetLink": "Invalid or expired reset link. Please request a new password reset." - }, - "fr": { - "title": "Réinitialiser Votre Mot de Passe", - "newPassword": "Nouveau Mot de Passe", - "confirmPassword": "Confirmer le Mot de Passe", - "passwordRequirements": "Le mot de passe doit comporter au moins 8 caractères", - "resetPassword": "Réinitialiser le Mot de Passe", - "passwordResetSuccess": "Votre mot de passe a été réinitialisé avec succès!", - "proceedToLogin": "Procéder à la Connexion", - "passwordsDoNotMatch": "Les mots de passe ne correspondent pas", - "passwordTooShort": "Le mot de passe doit comporter au moins 8 caractères", - "resetFailed": "Échec de la réinitialisation du mot de passe. Veuillez réessayer ou demander un nouveau lien de réinitialisation.", - "invalidResetLink": "Lien de réinitialisation invalide ou expiré. Veuillez demander une nouvelle réinitialisation de mot de passe." - }, - "es": { - "title": "Restablecer su Contraseña", - "newPassword": "Nueva Contraseña", - "confirmPassword": "Confirmar Contraseña", - "passwordRequirements": "La contraseña debe tener al menos 8 caracteres", - "resetPassword": "Restablecer Contraseña", - "passwordResetSuccess": "¡Su contraseña ha sido restablecida con éxito!", - "proceedToLogin": "Proceder al Inicio de Sesión", - "passwordsDoNotMatch": "Las contraseñas no coinciden", - "passwordTooShort": "La contraseña debe tener al menos 8 caracteres", - "resetFailed": "Error al restablecer la contraseña. Inténtelo de nuevo o solicite un nuevo enlace de restablecimiento.", - "invalidResetLink": "Enlace de restablecimiento inválido o caducado. Solicite un nuevo restablecimiento de contraseña." - } + "en": { + "title": "Reset Your Password", + "newPassword": "New Password", + "confirmPassword": "Confirm Password", + "passwordRequirements": "Password must be at least 8 characters", + "resetPassword": "Reset Password", + "passwordResetSuccess": "Your password has been reset successfully!", + "proceedToLogin": "Proceed to Login", + "passwordsDoNotMatch": "Passwords do not match", + "passwordTooShort": "Password must be at least 8 characters long", + "resetFailed": "Password reset failed. Please try again or request a new reset link.", + "invalidResetLink": "Invalid or expired reset link. Please request a new password reset." + }, + "fr": { + "title": "Réinitialiser Votre Mot de Passe", + "newPassword": "Nouveau Mot de Passe", + "confirmPassword": "Confirmer le Mot de Passe", + "passwordRequirements": "Le mot de passe doit comporter au moins 8 caractères", + "resetPassword": "Réinitialiser le Mot de Passe", + "passwordResetSuccess": "Votre mot de passe a été réinitialisé avec succès!", + "proceedToLogin": "Procéder à la Connexion", + "passwordsDoNotMatch": "Les mots de passe ne correspondent pas", + "passwordTooShort": "Le mot de passe doit comporter au moins 8 caractères", + "resetFailed": "Échec de la réinitialisation du mot de passe. Veuillez réessayer ou demander un nouveau lien de réinitialisation.", + "invalidResetLink": "Lien de réinitialisation invalide ou expiré. Veuillez demander une nouvelle réinitialisation de mot de passe." + }, + "es": { + "title": "Restablecer su Contraseña", + "newPassword": "Nueva Contraseña", + "confirmPassword": "Confirmar Contraseña", + "passwordRequirements": "La contraseña debe tener al menos 8 caracteres", + "resetPassword": "Restablecer Contraseña", + "passwordResetSuccess": "¡Su contraseña ha sido restablecida con éxito!", + "proceedToLogin": "Proceder al Inicio de Sesión", + "passwordsDoNotMatch": "Las contraseñas no coinciden", + "passwordTooShort": "La contraseña debe tener al menos 8 caracteres", + "resetFailed": "Error al restablecer la contraseña. Inténtelo de nuevo o solicite un nuevo enlace de restablecimiento.", + "invalidResetLink": "Enlace de restablecimiento inválido o caducado. Solicite un nuevo restablecimiento de contraseña." + } } diff --git a/frontend/src/views/creators/AboutCreator.vue b/frontend/src/views/creators/AboutCreator.vue index 7e4e5a9..869c963 100644 --- a/frontend/src/views/creators/AboutCreator.vue +++ b/frontend/src/views/creators/AboutCreator.vue @@ -87,16 +87,21 @@
+ -
- {{ phoneNumber }} +
+ + {{ phoneNumber }}
-
- {{ email }} +
+ + {{ email }}
+
+
@@ -112,12 +117,14 @@ import { buildEmbedUrl, isValidYouTubeUrlOrId, extractVideoId } from '@/utils/yo import AlbumEditor from "@/views/creators/AlbumEditor.vue"; import AlbumView from "@/views/creators/AlbumView.vue"; import AlbumViewer from './AlbumViewer.vue'; -import { mdiPencil, mdiCheck, mdiClose } from '@mdi/js'; +import { useToast } from 'vue-toastification'; +import { mdiPencil, mdiCheck, mdiClose, mdiPhone, mdiEmail } from '@mdi/js'; const { t } = useI18n(); const creatorProfileStore = useCreatorProfileStore(); const brandingStore = useBrandingStore(); const client = useClient(); +const toast = useToast(); const isLoading = ref(true); const isSaving = ref(false); @@ -143,6 +150,21 @@ const editableVideoUrl = ref(""); const videoUrlError = ref(""); const descriptionError = ref(""); +function callPhone() { + if (phoneNumber.value) { + toast.info('Calling your contact'); + // Remove formatting and create tel: link + const cleanPhone = phoneNumber.value.replace(/\D/g, ''); + window.location.href = `tel:+1${cleanPhone}`; + } +} + +function sendEmail() { + if (email.value) { + window.location.href = `mailto:${email.value}`; + } +} + // Computed property to check if we can save const canSave = computed(() => { if (isSaving.value == true) { return false; } @@ -400,11 +422,13 @@ function cancelEdit() { // Désactiver le mode édition isEditMode.value = false; } + // Add this function to handle photo clicks function handlePhotoClick(index) { selectedPhotoIndex.value = index; showAlbumViewer.value = true; } + { - "en": { - "common": { - "cancel": "Cancel" - }, - "creator": { - "donation": { - "isupport": "I Support", - "amount": "Amount ($)", - "message": "Message (optional)", - "send": "Send", - "processing": "Processing...", - "errors": { - "payment": "An error occurred during payment processing", - "invalidAmount": "Please enter a valid amount" + "en": { + "common": { + "cancel": "Cancel" + }, + "creator": { + "donation": { + "isupport": "I Support", + "amount": "Amount ($)", + "message": "Message (optional)", + "send": "Send", + "processing": "Processing...", + "errors": { + "payment": "An error occurred during payment processing", + "invalidAmount": "Please enter a valid amount" + } + } } - } - } - }, - "fr": { - "common": { - "cancel": "Annuler" }, - "creator": { - "donation": { - "isupport": "Je Soutiens", - "amount": "Montant ($)", - "message": "Message (optionnel)", - "send": "Envoyer", - "processing": "Traitement en cours...", - "errors": { - "payment": "Une erreur s'est produite lors du traitement du paiement", - "invalidAmount": "Veuillez entrer un montant valide" + "fr": { + "common": { + "cancel": "Annuler" + }, + "creator": { + "donation": { + "isupport": "Je Soutiens", + "amount": "Montant ($)", + "message": "Message (optionnel)", + "send": "Envoyer", + "processing": "Traitement en cours...", + "errors": { + "payment": "Une erreur s'est produite lors du traitement du paiement", + "invalidAmount": "Veuillez entrer un montant valide" + } + } } - } - } - }, - "es": { - "common": { - "cancel": "Cancelar" }, - "creator": { - "donation": { - "isupport": "Apoyo", - "amount": "Cantidad ($)", - "message": "Mensaje (opcional)", - "send": "Enviar", - "processing": "Procesando...", - "errors": { - "payment": "Ocurrió un error durante el procesamiento del pago", - "invalidAmount": "Por favor ingrese un monto válido" + "es": { + "common": { + "cancel": "Cancelar" + }, + "creator": { + "donation": { + "isupport": "Apoyo", + "amount": "Cantidad ($)", + "message": "Mensaje (opcional)", + "send": "Enviar", + "processing": "Procesando...", + "errors": { + "payment": "Ocurrió un error durante el procesamiento del pago", + "invalidAmount": "Por favor ingrese un monto válido" + } + } } - } } - } } - \ No newline at end of file + \ No newline at end of file diff --git a/frontend/src/views/creators/NameEditor.vue b/frontend/src/views/creators/NameEditor.vue index 3f4af7c..898b1df 100644 --- a/frontend/src/views/creators/NameEditor.vue +++ b/frontend/src/views/creators/NameEditor.vue @@ -66,7 +66,7 @@ onMounted(() => { emits('update:creatorNameReservationId', reservationId.value); } - // If the name is the same as the original slug, set reservation state to "reserved" + // If the name is the same as the original slug, set the reservation state to "reserved" if (isCurrentSlug.value) { reservationState.value = "reserved"; } @@ -166,7 +166,7 @@ onUnmounted(() => {