From 81b5db34ef0b8945425fd8d7b1fb20e864785407 Mon Sep 17 00:00:00 2001 From: Jonathan Bourdon Date: Sat, 21 Jun 2025 01:58:48 -0400 Subject: [PATCH] chore(codebase): full cleanup pass --- backend/.editorconfig | 56 +++++++++---------- backend/DependencyInjection.cs | 11 ++-- backend/Folder.DotSettings | 5 +- backend/Hutopy.csproj | 48 ++++++++-------- .../BlobStorage/Contracts/ContainerNames.cs | 2 +- .../BlobStorage/Contracts/IBlobStorage.cs | 8 +-- .../BlobStorage/Services/AzureBlobStorage.cs | 28 +++++----- .../Configuration/WebsiteOptions.cs | 2 +- .../Stripe/Configuration/StripeOptions.cs | 2 +- .../Services/MembershipPaymentProcessor.cs | 17 +++--- .../Services/MembershipTierProcessor.cs | 8 +-- .../Stripe/Services/StripeTipProcessor.cs | 10 ++-- .../Security/ClaimsPrincipalExtensions.cs | 25 +++++---- .../Security/GenerateJwtToken.cs | 14 ++--- .../Security/PasswordGenerator.cs | 32 ++++++++--- .../Security/RefreshTokenGenerator.cs | 2 +- .../YouTube/YouTubeUrlHelper.cs | 20 +++++-- backend/Modules/Contents/Data/Album.cs | 2 +- .../Contents/Data/ContentsDbContext.cs | 4 +- .../Modules/Contents/DependencyInjection.cs | 6 +- .../Contents/Features/AddPhotoToAlbum.cs | 43 +++++++------- .../Modules/Contents/Features/CreateAlbum.cs | 11 +--- backend/Modules/Contents/Features/GetAlbum.cs | 6 +- .../Modules/Contents/Features/RemoveAlbum.cs | 10 ++-- .../Contents/Features/RemovePhotoFromAlbum.cs | 12 ++-- .../Modules/Contents/Models/ContentModel.cs | 2 +- .../Creators/Configuration/CreatorOptions.cs | 2 +- backend/Modules/Creators/Data/Creator.cs | 12 ++-- .../Creators/Data/CreatorsDbContext.cs | 6 +- backend/Modules/Creators/Data/Socials.cs | 2 +- .../Modules/Creators/DependencyInjection.cs | 14 ++--- .../Modules/Creators/Features/ChangeBanner.cs | 6 +- .../Modules/Creators/Features/ChangeEmail.cs | 10 ++-- .../Modules/Creators/Features/ChangeLogo.cs | 6 +- .../Modules/Creators/Features/ChangeName.cs | 4 +- .../Creators/Features/ChangePhoneNumber.cs | 10 ++-- .../Features/ChangePresentationInfos.cs | 10 ++-- .../Modules/Creators/Features/ChangeSlug.cs | 13 +++-- .../Creators/Features/ChangeSocials.cs | 4 +- .../Modules/Creators/Features/ChangeTitle.cs | 6 +- .../Creators/Features/CreateCreator.cs | 20 +++---- .../Creators/Features/GetCreatorById.cs | 16 ++++-- .../Creators/Features/GetCreatorBySlug.cs | 6 +- .../Creators/Features/RemoveCreator.cs | 6 +- .../Modules/Creators/Features/ReserveSlug.cs | 19 +++---- .../Creators/Features/RestoreCreator.cs | 6 +- .../Modules/Creators/Services/SlugPurger.cs | 4 +- .../Identity/Configuration/JwtOptions.cs | 4 +- .../Identity/Data/IdentityDbContext.cs | 28 +++++----- .../Modules/Identity/Data/IdentityService.cs | 30 ++++++---- backend/Modules/Identity/Data/User.cs | 1 - .../Identity/Handlers/ChangeAddress.cs | 9 ++- .../Modules/Identity/Handlers/ChangeAlias.cs | 9 ++- .../Identity/Handlers/ChangeBirthDate.cs | 9 ++- .../Modules/Identity/Handlers/ChangeEmail.cs | 9 ++- .../Identity/Handlers/ChangeFullname.cs | 9 ++- .../Modules/Identity/Handlers/ChangePhone.cs | 9 ++- .../Identity/Handlers/ChangePortrait.cs | 7 ++- .../Identity/Handlers/GetCurrentUser.cs | 8 +-- .../Handlers/GetCurrentUserProfilePicture.cs | 5 +- .../Modules/Identity/Handlers/RefreshToken.cs | 28 +++++----- .../Identity/Handlers/ResetPassword.cs | 5 +- .../Modules/Identity/Handlers/SetPassword.cs | 15 ++--- backend/Modules/Identity/Models/Result.cs | 10 ++-- .../Modules/Identity/Services/UserLookup.cs | 2 +- .../Modules/Memberships/Data/Membership.cs | 4 +- .../Memberships/Data/MembershipsDbContext.cs | 6 +- backend/Modules/Memberships/Data/Payment.cs | 1 - .../Memberships/DependencyInjection.cs | 14 ++--- .../Memberships/Handlers/CancelMembership.cs | 8 +-- .../Handlers/CreateMembershipTier.cs | 8 +-- .../Handlers/GetActiveMemberships.cs | 6 +- .../Handlers/GetMembershipTiers.cs | 4 +- .../Handlers/SubscribeToCreator.cs | 8 +-- .../Services/MembershipNotifier.cs | 16 +++--- .../Messaging/Data/MessagingDbContext.cs | 20 +++---- .../Modules/Messaging/DependencyInjection.cs | 10 ++-- .../Modules/Messaging/Handlers/AddMessage.cs | 6 +- .../Modules/Messaging/Handlers/AddReply.cs | 6 +- .../Messaging/Handlers/ChangeMessage.cs | 10 ++-- .../Messaging/Handlers/DeleteMessage.cs | 8 +-- .../Messaging/Handlers/GetMessageCount.cs | 9 +-- .../Modules/Messaging/Handlers/GetMessages.cs | 4 +- .../Messaging/Handlers/GetMessagesByUser.cs | 8 +-- .../Modules/Messaging/Handlers/GetReplies.cs | 2 +- backend/Modules/Tipping/Data/Tip.cs | 2 +- .../Modules/Tipping/Data/TippingDbContext.cs | 4 +- .../Modules/Tipping/DependencyInjection.cs | 10 ++-- .../Tipping/Handlers/GetReceivedTips.cs | 8 +-- backend/Program.cs | 29 +++++----- backend/analyzers.ruleset | 3 +- backend/appsettings.Development.json | 2 +- 92 files changed, 529 insertions(+), 452 deletions(-) diff --git a/backend/.editorconfig b/backend/.editorconfig index b6fb7e3..b370652 100644 --- a/backend/.editorconfig +++ b/backend/.editorconfig @@ -112,7 +112,7 @@ csharp_style_conditional_delegate_call = true:suggestion # Modifier preferences csharp_prefer_static_local_function = true:warning -csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:silent +csharp_preferred_modifier_order = public, private, protected, internal, static, extern, new, virtual, abstract, sealed, override, readonly, unsafe, volatile, async:silent # Code-block preferences csharp_prefer_braces = true:silent @@ -273,31 +273,31 @@ dotnet_naming_rule.non_field_members_should_be_pascalcase.style = pascalcase dotnet_naming_symbols.interfaces.applicable_kinds = interface dotnet_naming_symbols.interfaces.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected -dotnet_naming_symbols.interfaces.required_modifiers = +dotnet_naming_symbols.interfaces.required_modifiers = dotnet_naming_symbols.enums.applicable_kinds = enum dotnet_naming_symbols.enums.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected -dotnet_naming_symbols.enums.required_modifiers = +dotnet_naming_symbols.enums.required_modifiers = dotnet_naming_symbols.events.applicable_kinds = event dotnet_naming_symbols.events.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected -dotnet_naming_symbols.events.required_modifiers = +dotnet_naming_symbols.events.required_modifiers = dotnet_naming_symbols.methods.applicable_kinds = method dotnet_naming_symbols.methods.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected -dotnet_naming_symbols.methods.required_modifiers = +dotnet_naming_symbols.methods.required_modifiers = dotnet_naming_symbols.properties.applicable_kinds = property dotnet_naming_symbols.properties.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected -dotnet_naming_symbols.properties.required_modifiers = +dotnet_naming_symbols.properties.required_modifiers = dotnet_naming_symbols.public_fields.applicable_kinds = field dotnet_naming_symbols.public_fields.applicable_accessibilities = public, internal -dotnet_naming_symbols.public_fields.required_modifiers = +dotnet_naming_symbols.public_fields.required_modifiers = dotnet_naming_symbols.private_fields.applicable_kinds = field dotnet_naming_symbols.private_fields.applicable_accessibilities = private, protected, protected_internal, private_protected -dotnet_naming_symbols.private_fields.required_modifiers = +dotnet_naming_symbols.private_fields.required_modifiers = dotnet_naming_symbols.private_static_fields.applicable_kinds = field dotnet_naming_symbols.private_static_fields.applicable_accessibilities = private, protected, protected_internal, private_protected @@ -305,15 +305,15 @@ dotnet_naming_symbols.private_static_fields.required_modifiers = static dotnet_naming_symbols.types_and_namespaces.applicable_kinds = namespace, class, struct, interface, enum dotnet_naming_symbols.types_and_namespaces.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected -dotnet_naming_symbols.types_and_namespaces.required_modifiers = +dotnet_naming_symbols.types_and_namespaces.required_modifiers = dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected -dotnet_naming_symbols.non_field_members.required_modifiers = +dotnet_naming_symbols.non_field_members.required_modifiers = dotnet_naming_symbols.type_parameters.applicable_kinds = namespace dotnet_naming_symbols.type_parameters.applicable_accessibilities = * -dotnet_naming_symbols.type_parameters.required_modifiers = +dotnet_naming_symbols.type_parameters.required_modifiers = dotnet_naming_symbols.private_constant_fields.applicable_kinds = field dotnet_naming_symbols.private_constant_fields.applicable_accessibilities = private, protected, protected_internal, private_protected @@ -321,7 +321,7 @@ dotnet_naming_symbols.private_constant_fields.required_modifiers = const dotnet_naming_symbols.local_variables.applicable_kinds = local dotnet_naming_symbols.local_variables.applicable_accessibilities = local -dotnet_naming_symbols.local_variables.required_modifiers = +dotnet_naming_symbols.local_variables.required_modifiers = dotnet_naming_symbols.local_constants.applicable_kinds = local dotnet_naming_symbols.local_constants.applicable_accessibilities = local @@ -329,7 +329,7 @@ dotnet_naming_symbols.local_constants.required_modifiers = const dotnet_naming_symbols.parameters.applicable_kinds = parameter dotnet_naming_symbols.parameters.applicable_accessibilities = * -dotnet_naming_symbols.parameters.required_modifiers = +dotnet_naming_symbols.parameters.required_modifiers = dotnet_naming_symbols.public_constant_fields.applicable_kinds = field dotnet_naming_symbols.public_constant_fields.applicable_accessibilities = public, internal @@ -345,38 +345,38 @@ dotnet_naming_symbols.private_static_readonly_fields.required_modifiers = readon dotnet_naming_symbols.local_functions.applicable_kinds = local_function dotnet_naming_symbols.local_functions.applicable_accessibilities = * -dotnet_naming_symbols.local_functions.required_modifiers = +dotnet_naming_symbols.local_functions.required_modifiers = # Naming styles -dotnet_naming_style.pascalcase.required_prefix = -dotnet_naming_style.pascalcase.required_suffix = -dotnet_naming_style.pascalcase.word_separator = +dotnet_naming_style.pascalcase.required_prefix = +dotnet_naming_style.pascalcase.required_suffix = +dotnet_naming_style.pascalcase.word_separator = dotnet_naming_style.pascalcase.capitalization = pascal_case dotnet_naming_style.ipascalcase.required_prefix = I -dotnet_naming_style.ipascalcase.required_suffix = -dotnet_naming_style.ipascalcase.word_separator = +dotnet_naming_style.ipascalcase.required_suffix = +dotnet_naming_style.ipascalcase.word_separator = dotnet_naming_style.ipascalcase.capitalization = pascal_case dotnet_naming_style.tpascalcase.required_prefix = T -dotnet_naming_style.tpascalcase.required_suffix = -dotnet_naming_style.tpascalcase.word_separator = +dotnet_naming_style.tpascalcase.required_suffix = +dotnet_naming_style.tpascalcase.word_separator = dotnet_naming_style.tpascalcase.capitalization = pascal_case dotnet_naming_style._camelcase.required_prefix = _ -dotnet_naming_style._camelcase.required_suffix = -dotnet_naming_style._camelcase.word_separator = +dotnet_naming_style._camelcase.required_suffix = +dotnet_naming_style._camelcase.word_separator = dotnet_naming_style._camelcase.capitalization = camel_case -dotnet_naming_style.camelcase.required_prefix = -dotnet_naming_style.camelcase.required_suffix = -dotnet_naming_style.camelcase.word_separator = +dotnet_naming_style.camelcase.required_prefix = +dotnet_naming_style.camelcase.required_suffix = +dotnet_naming_style.camelcase.word_separator = dotnet_naming_style.camelcase.capitalization = camel_case dotnet_naming_style.s_camelcase.required_prefix = s_ -dotnet_naming_style.s_camelcase.required_suffix = -dotnet_naming_style.s_camelcase.word_separator = +dotnet_naming_style.s_camelcase.required_suffix = +dotnet_naming_style.s_camelcase.word_separator = dotnet_naming_style.s_camelcase.capitalization = camel_case dotnet_style_namespace_match_folder = true:suggestion diff --git a/backend/DependencyInjection.cs b/backend/DependencyInjection.cs index 83ced38..b77ffae 100644 --- a/backend/DependencyInjection.cs +++ b/backend/DependencyInjection.cs @@ -1,5 +1,6 @@ using System.Text; using Hutopy.Modules.Identity.Data; +using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Facebook; using Microsoft.AspNetCore.Authentication.Google; using Microsoft.AspNetCore.Authentication.JwtBearer; @@ -29,18 +30,18 @@ public static class DependencyInjection return services; } - + public static IServiceCollection AddAuthorizationAndAuthentication(this IServiceCollection services, ConfigurationManager configuration) { - var authenticationBuilder = services + AuthenticationBuilder authenticationBuilder = services .AddAuthentication(options => { options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; }); - var authJwt = configuration.GetSection("Authentication:Jwt"); + IConfigurationSection authJwt = configuration.GetSection("Authentication:Jwt"); if (authJwt.Exists()) { authenticationBuilder.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, jwtBearerOptions => @@ -59,7 +60,7 @@ public static class DependencyInjection }); } - var authGoogle = configuration.GetSection("Authentication:Google"); + IConfigurationSection authGoogle = configuration.GetSection("Authentication:Google"); if (authGoogle.Exists()) { authenticationBuilder.AddGoogle(GoogleDefaults.AuthenticationScheme, options => @@ -71,7 +72,7 @@ public static class DependencyInjection }); } - var authFacebook = configuration.GetSection("Authentication:Facebook"); + IConfigurationSection authFacebook = configuration.GetSection("Authentication:Facebook"); if (authFacebook.Exists()) { authenticationBuilder.AddFacebook(FacebookDefaults.AuthenticationScheme, options => diff --git a/backend/Folder.DotSettings b/backend/Folder.DotSettings index 140a817..9cb8442 100644 --- a/backend/Folder.DotSettings +++ b/backend/Folder.DotSettings @@ -1,2 +1,5 @@ - + True \ No newline at end of file diff --git a/backend/Hutopy.csproj b/backend/Hutopy.csproj index f0ec526..626103a 100644 --- a/backend/Hutopy.csproj +++ b/backend/Hutopy.csproj @@ -10,38 +10,38 @@ true AllEnabledByDefault false - + analyzers.ruleset - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - - - - - - - - + + + + + + + + diff --git a/backend/Infrastructure/BlobStorage/Contracts/ContainerNames.cs b/backend/Infrastructure/BlobStorage/Contracts/ContainerNames.cs index 9ea3329..ab88a39 100644 --- a/backend/Infrastructure/BlobStorage/Contracts/ContainerNames.cs +++ b/backend/Infrastructure/BlobStorage/Contracts/ContainerNames.cs @@ -1,6 +1,6 @@ namespace Hutopy.Infrastructure.BlobStorage.Contracts; -public static class ContainerNames +internal static class ContainerNames { public const string Users = "users"; public const string Creators = "creators"; diff --git a/backend/Infrastructure/BlobStorage/Contracts/IBlobStorage.cs b/backend/Infrastructure/BlobStorage/Contracts/IBlobStorage.cs index 5ea333f..fd7b19d 100644 --- a/backend/Infrastructure/BlobStorage/Contracts/IBlobStorage.cs +++ b/backend/Infrastructure/BlobStorage/Contracts/IBlobStorage.cs @@ -3,7 +3,7 @@ namespace Hutopy.Infrastructure.BlobStorage.Contracts; public interface IBlobStorage { /// - /// Upload a file to blob storage. + /// 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). @@ -12,14 +12,14 @@ public interface IBlobStorage /// The cancellation token /// Task UploadFileAsync( - string containerName, - string blobName, + string containerName, + string blobName, Stream stream, string contentType, CancellationToken ct = default); /// - /// Download a file to blob storage. + /// Download a file to blob storage. /// /// The blob name (path within the container). /// The name of the container where the file is stored. (users) diff --git a/backend/Infrastructure/BlobStorage/Services/AzureBlobStorage.cs b/backend/Infrastructure/BlobStorage/Services/AzureBlobStorage.cs index f25d5c6..44f5e3e 100644 --- a/backend/Infrastructure/BlobStorage/Services/AzureBlobStorage.cs +++ b/backend/Infrastructure/BlobStorage/Services/AzureBlobStorage.cs @@ -8,19 +8,19 @@ namespace Hutopy.Infrastructure.BlobStorage.Services; public class AzureBlobStorage : IBlobStorage { private const long MaxUploadSize = 10 * 1024 * 1024; // 10 MB in bytes - + private readonly BlobServiceClient _blobServiceClient; private readonly ILogger _logger; public AzureBlobStorage(IConfiguration configuration, ILogger logger) { _logger = logger; - var connectionString = configuration.GetConnectionString("AzureBlob"); + string? connectionString = configuration.GetConnectionString("AzureBlob"); _blobServiceClient = new BlobServiceClient(connectionString); } /// - /// Upload a file to microsoft azure blob storage. + /// Upload a file to microsoft azure blob storage. /// /// The name of the container where the file is stored. /// The blob name (path within the container, include the file name). @@ -29,8 +29,8 @@ public class AzureBlobStorage : IBlobStorage /// The cancellation token /// public async Task UploadFileAsync( - string containerName, - string blobName, + string containerName, + string blobName, Stream stream, string contentType, CancellationToken ct = default) @@ -59,7 +59,7 @@ public class AzureBlobStorage : IBlobStorage try { // Get a reference to a container - var containerClient = _blobServiceClient.GetBlobContainerClient(containerName); + BlobContainerClient? containerClient = _blobServiceClient.GetBlobContainerClient(containerName); // Create the container if it does not exist await containerClient.CreateIfNotExistsAsync( @@ -67,18 +67,18 @@ public class AzureBlobStorage : IBlobStorage cancellationToken: ct); // Get a reference to a blob - var blobClient = containerClient.GetBlobClient(blobName); + BlobClient? blobClient = containerClient.GetBlobClient(blobName); // Define the BlobHttpHeaders to include the content type - var blobHttpHeaders = new BlobHttpHeaders { ContentType = contentType }; + BlobHttpHeaders blobHttpHeaders = new() { ContentType = contentType }; // Upload the file - var response = await blobClient.UploadAsync( + Response? response = await blobClient.UploadAsync( stream, new BlobUploadOptions { HttpHeaders = blobHttpHeaders }, ct); - var fileUri = blobClient.Uri.ToString(); + string fileUri = blobClient.Uri.ToString(); _logger.LogInformation( """ @@ -112,7 +112,7 @@ public class AzureBlobStorage : IBlobStorage } /// - /// Download a file to microsoft's azure blob storage. + /// Download a file to microsoft's azure blob storage. /// /// The blob name (path within the container). /// The name of the container where the file is stored. (users) @@ -126,14 +126,14 @@ public class AzureBlobStorage : IBlobStorage try { // Get a reference to a container - var containerClient = _blobServiceClient.GetBlobContainerClient(containerName); + BlobContainerClient? containerClient = _blobServiceClient.GetBlobContainerClient(containerName); // Get a reference to a blob - var blobClient = containerClient.GetBlobClient(blobName); + BlobClient? blobClient = containerClient.GetBlobClient(blobName); // Download the blob to a stream BlobDownloadInfo download = await blobClient.DownloadAsync(ct); - + MemoryStream memoryStream = new(); await download.Content.CopyToAsync(memoryStream, ct); memoryStream.Position = 0; // Ensure the stream is at the beginning diff --git a/backend/Infrastructure/Configuration/WebsiteOptions.cs b/backend/Infrastructure/Configuration/WebsiteOptions.cs index 6c2c238..25fe8eb 100644 --- a/backend/Infrastructure/Configuration/WebsiteOptions.cs +++ b/backend/Infrastructure/Configuration/WebsiteOptions.cs @@ -3,6 +3,6 @@ namespace Hutopy.Infrastructure.Configuration; public class WebsiteOptions { public const string SectionName = "Website"; - + public string FrontendBaseUrl { get; set; } = "https://localhost:5173"; } diff --git a/backend/Infrastructure/Payments/Stripe/Configuration/StripeOptions.cs b/backend/Infrastructure/Payments/Stripe/Configuration/StripeOptions.cs index 8cfff03..594aca4 100644 --- a/backend/Infrastructure/Payments/Stripe/Configuration/StripeOptions.cs +++ b/backend/Infrastructure/Payments/Stripe/Configuration/StripeOptions.cs @@ -5,7 +5,7 @@ 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; } diff --git a/backend/Infrastructure/Payments/Stripe/Services/MembershipPaymentProcessor.cs b/backend/Infrastructure/Payments/Stripe/Services/MembershipPaymentProcessor.cs index fd54310..84a2b71 100644 --- a/backend/Infrastructure/Payments/Stripe/Services/MembershipPaymentProcessor.cs +++ b/backend/Infrastructure/Payments/Stripe/Services/MembershipPaymentProcessor.cs @@ -9,7 +9,7 @@ namespace Hutopy.Infrastructure.Payments.Stripe.Services; public class MembershipPaymentProcessor( IOptions stripeOptions) -: IMembershipPaymentProcessor + : IMembershipPaymentProcessor { public async Task CreateCheckoutSessionAsync( Guid userId, @@ -22,16 +22,16 @@ public class MembershipPaymentProcessor( StripeConfiguration.ApiKey = stripeOptions.Value.SecretKey; // Create Stripe customer for the user if not already created - var customerService = new CustomerService(); - var customer = await customerService.CreateAsync( + CustomerService customerService = new(); + Customer? 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( + SessionService sessionService = new(); + Session? session = await sessionService.CreateAsync( new SessionCreateOptions { Customer = customer.Id, @@ -44,7 +44,11 @@ public class MembershipPaymentProcessor( SubscriptionData = new SessionSubscriptionDataOptions { ApplicationFeePercent = stripeOptions.Value.HutopyRate, - TransferData = new SessionSubscriptionDataTransferDataOptions { Destination = creatorReference.StripeAccountId } + TransferData = + new SessionSubscriptionDataTransferDataOptions + { + Destination = creatorReference.StripeAccountId + } }, SuccessUrl = successUrl, // Redirect after successful payment CancelUrl = cancelUrl, // Redirect after canceled payment @@ -61,5 +65,4 @@ public class MembershipPaymentProcessor( session.Id, session.Url); } - } diff --git a/backend/Infrastructure/Payments/Stripe/Services/MembershipTierProcessor.cs b/backend/Infrastructure/Payments/Stripe/Services/MembershipTierProcessor.cs index e2889e9..d12864f 100644 --- a/backend/Infrastructure/Payments/Stripe/Services/MembershipTierProcessor.cs +++ b/backend/Infrastructure/Payments/Stripe/Services/MembershipTierProcessor.cs @@ -6,7 +6,7 @@ using Stripe; namespace Hutopy.Infrastructure.Payments.Stripe.Services; public sealed class MembershipTierProcessor( - IOptions stripeOptions) + IOptions stripeOptions) : IMembershipTierProcessor { public async Task CreateAsync( @@ -19,8 +19,8 @@ public sealed class MembershipTierProcessor( StripeConfiguration.ApiKey = stripeOptions.Value.SecretKey; // Create the product - var productService = new ProductService(); - var product = await productService.CreateAsync( + ProductService productService = new(); + Product? product = await productService.CreateAsync( new ProductCreateOptions { Name = productName, @@ -28,7 +28,7 @@ public sealed class MembershipTierProcessor( }); // Create the price for the product - var priceService = new PriceService(); + PriceService priceService = new(); await priceService.CreateAsync( new PriceCreateOptions { diff --git a/backend/Infrastructure/Payments/Stripe/Services/StripeTipProcessor.cs b/backend/Infrastructure/Payments/Stripe/Services/StripeTipProcessor.cs index c335cd7..761748b 100644 --- a/backend/Infrastructure/Payments/Stripe/Services/StripeTipProcessor.cs +++ b/backend/Infrastructure/Payments/Stripe/Services/StripeTipProcessor.cs @@ -24,14 +24,14 @@ public class StripeTipProcessor( StripeConfiguration.ApiKey = stripeOptions.Value.SecretKey; // Create Stripe customer for the user if not already created - var customerService = new CustomerService(); - var customer = await customerService.CreateAsync( + CustomerService customerService = new(); + Customer? customer = await customerService.CreateAsync( new CustomerCreateOptions(), cancellationToken: ct); // Create paymentIntent for the user - var sessionService = new SessionService(); - var session = await sessionService.CreateAsync( + SessionService sessionService = new(); + Session? session = await sessionService.CreateAsync( new SessionCreateOptions { ClientReferenceId = tipId.ToString(), @@ -68,7 +68,7 @@ public class StripeTipProcessor( CancelUrl = cancelUrl, // Redirect after canceled payment Metadata = new Dictionary { - { "creatorId", creator.Id.ToString() }, { "creatorName", creator.Name }, { "message", message }, + { "creatorId", creator.Id.ToString() }, { "creatorName", creator.Name }, { "message", message } } }, cancellationToken: ct); diff --git a/backend/Infrastructure/Security/ClaimsPrincipalExtensions.cs b/backend/Infrastructure/Security/ClaimsPrincipalExtensions.cs index 3e29e5c..dd8a78e 100644 --- a/backend/Infrastructure/Security/ClaimsPrincipalExtensions.cs +++ b/backend/Infrastructure/Security/ClaimsPrincipalExtensions.cs @@ -8,22 +8,22 @@ public static class ClaimsPrincipalExtensions { return (Guid)claims.GetRequiredClaim(ClaimTypes.NameIdentifier); } - + public static string GetName(this ClaimsPrincipal claims) { return (string)claims.GetRequiredClaim(ClaimTypes.Name); } - + public static string? GetAlias(this ClaimsPrincipal claims) { return (string?)claims.GetClaim(KnownClaims.Alias); - } - + } + public static string? GetPortraitUrl(this ClaimsPrincipal claims) { return (string?)claims.GetClaim(KnownClaims.PortraitUrl); - } - + } + public static string GetFirstName(this ClaimsPrincipal claims) { return (string)claims.GetRequiredClaim(ClaimTypes.GivenName); @@ -41,17 +41,22 @@ public static class ClaimsPrincipalExtensions private static object? GetClaim(this ClaimsPrincipal claims, string key) { - var claim = claims.FindFirst(key); + Claim? claim = claims.FindFirst(key); return claim is null ? null : claims.GetRequiredClaim(key); } private static object GetRequiredClaim(this ClaimsPrincipal claims, string key) { - var claim = claims.FindFirst(key); + Claim? claim = claims.FindFirst(key); - if (claim is null) throw new MissingClaimException(key); + if (claim is null) + { + throw new MissingClaimException(key); + } - return typeof(TValue) == typeof(Guid) ? Guid.Parse(claim.Value) : 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/Infrastructure/Security/GenerateJwtToken.cs b/backend/Infrastructure/Security/GenerateJwtToken.cs index d03402c..3e88aff 100644 --- a/backend/Infrastructure/Security/GenerateJwtToken.cs +++ b/backend/Infrastructure/Security/GenerateJwtToken.cs @@ -19,10 +19,10 @@ public static class JwtTokenHelper string lastname, string? portraitUrl) { - var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key)); - var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256); + SymmetricSecurityKey securityKey = new(Encoding.UTF8.GetBytes(key)); + SigningCredentials credentials = new(securityKey, SecurityAlgorithms.HmacSha256); - var claims = new List([ + List claims = new([ new Claim(JwtRegisteredClaimNames.Sub, userId), new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()), new Claim(ClaimTypes.NameIdentifier, userId), new Claim(ClaimTypes.Email, email), @@ -40,10 +40,10 @@ public static class JwtTokenHelper claims.Add(new Claim(KnownClaims.PortraitUrl, portraitUrl)); } - var token = new JwtSecurityToken( - issuer: issuer, - audience: audience, - claims: claims, + JwtSecurityToken token = new( + issuer, + audience, + claims, expires: DateTime.Now.Add(expiresIn), signingCredentials: credentials); diff --git a/backend/Infrastructure/Security/PasswordGenerator.cs b/backend/Infrastructure/Security/PasswordGenerator.cs index 57c15d6..d1ae540 100644 --- a/backend/Infrastructure/Security/PasswordGenerator.cs +++ b/backend/Infrastructure/Security/PasswordGenerator.cs @@ -21,38 +21,54 @@ public static class PasswordGenerator bool requireSpecialCharacter = true) { // Create pools based on the requirements - var characterPool = new StringBuilder(); + StringBuilder characterPool = new(); if (requireNumber) + { characterPool.Append(LowerLetters); - + } + if (requireCapital) + { characterPool.Append(UpperLetters); + } if (requireNumber) + { characterPool.Append(Numbers); + } if (requireSpecialCharacter) + { characterPool.Append(SpecialCharacters); + } // Ensure that the length is within the specified bounds - var password = new char[length]; + char[] password = new char[length]; // Ensure at least one character from each required category is included int index = 0; - + if (requireLowercase) + { password[index++] = LowerLetters[Random.Next(LowerLetters.Length)]; - + } + if (requireCapital) + { password[index++] = UpperLetters[Random.Next(UpperLetters.Length)]; - + } + if (requireNumber) + { password[index++] = Numbers[Random.Next(Numbers.Length)]; - + } + if (requireSpecialCharacter) + { password[index++] = SpecialCharacters[Random.Next(SpecialCharacters.Length)]; - + } + // Fill the rest with the password for (int i = index; i < length; i++) { diff --git a/backend/Infrastructure/Security/RefreshTokenGenerator.cs b/backend/Infrastructure/Security/RefreshTokenGenerator.cs index 69d6e8e..3a786e4 100644 --- a/backend/Infrastructure/Security/RefreshTokenGenerator.cs +++ b/backend/Infrastructure/Security/RefreshTokenGenerator.cs @@ -6,7 +6,7 @@ public static class RefreshTokenGenerator { public static string Next() { - var randomNumber = new byte[32]; + byte[] randomNumber = new byte[32]; RandomNumberGenerator.Fill(randomNumber); return Convert.ToBase64String(randomNumber); } diff --git a/backend/Infrastructure/YouTube/YouTubeUrlHelper.cs b/backend/Infrastructure/YouTube/YouTubeUrlHelper.cs index a0db920..7a07527 100644 --- a/backend/Infrastructure/YouTube/YouTubeUrlHelper.cs +++ b/backend/Infrastructure/YouTube/YouTubeUrlHelper.cs @@ -13,52 +13,62 @@ public static class YouTubeUrlHelper RegexOptions.Compiled); /// - /// Extracts the video ID from a YouTube URL or returns the input if it's already a video ID. + /// Extracts the video ID from a YouTube URL or returns the input if it's already a video ID. /// /// The YouTube URL or video ID /// The extracted video ID or null if invalid public static string? ExtractVideoId(string? input) { if (string.IsNullOrWhiteSpace(input)) + { return null; + } // If it's already a valid video ID, return it if (IsValidVideoId(input)) + { return input; + } // Try to extract video ID from URL - var match = VideoIdRegex.Match(input); + Match match = VideoIdRegex.Match(input); return match.Success ? match.Groups[1].Value : null; } /// - /// Validates if the input is a valid YouTube video ID. + /// Validates if the input is a valid YouTube video ID. /// /// The video ID to validate /// True if the input is a valid video ID public static bool IsValidVideoId(string? input) { if (string.IsNullOrWhiteSpace(input)) + { return false; + } return ShortUrlRegex.IsMatch(input); } /// - /// Validates if the input is a valid YouTube URL or video ID. + /// Validates if the input is a valid YouTube URL or video ID. /// /// The URL or video ID to validate /// True if the input is a valid YouTube URL or video ID public static bool IsValidYouTubeUrlOrId(string? input) { if (string.IsNullOrWhiteSpace(input)) + { return false; + } // Check if it's a valid video ID if (IsValidVideoId(input)) + { return true; + } // Check if it's a valid YouTube URL return VideoIdRegex.IsMatch(input); } -} \ No newline at end of file +} diff --git a/backend/Modules/Contents/Data/Album.cs b/backend/Modules/Contents/Data/Album.cs index 430f912..8785663 100644 --- a/backend/Modules/Contents/Data/Album.cs +++ b/backend/Modules/Contents/Data/Album.cs @@ -5,7 +5,7 @@ namespace Hutopy.Modules.Contents.Data; public class Album : Entity { - public bool IsDeleted { get; private set; } // private set → EF updates it + 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/ContentsDbContext.cs b/backend/Modules/Contents/Data/ContentsDbContext.cs index 570da43..08ee5d3 100644 --- a/backend/Modules/Contents/Data/ContentsDbContext.cs +++ b/backend/Modules/Contents/Data/ContentsDbContext.cs @@ -24,7 +24,7 @@ public class ContentsDbContext( modelBuilder .Entity() .Property(c => c.IsDeleted) - .HasComputedColumnSql("\"DeletedAt\" IS NOT NULL", stored: true); + .HasComputedColumnSql("\"DeletedAt\" IS NOT NULL", true); modelBuilder .Entity() @@ -40,7 +40,7 @@ public class ContentsDbContext( modelBuilder .Entity() .Property(c => c.IsDeleted) - .HasComputedColumnSql("\"DeletedAt\" IS NOT NULL", stored: true); + .HasComputedColumnSql("\"DeletedAt\" IS NOT NULL", true); modelBuilder .Entity() diff --git a/backend/Modules/Contents/DependencyInjection.cs b/backend/Modules/Contents/DependencyInjection.cs index 336b92c..ee2a51e 100644 --- a/backend/Modules/Contents/DependencyInjection.cs +++ b/backend/Modules/Contents/DependencyInjection.cs @@ -17,9 +17,9 @@ public static class DependencyInjection this IApplicationBuilder app, CancellationToken cancellationToken = default) { - var scopeFactory = app.ApplicationServices.GetRequiredService(); - using var scope = scopeFactory.CreateScope(); - await using var context = scope.ServiceProvider.GetRequiredService(); + IServiceScopeFactory scopeFactory = app.ApplicationServices.GetRequiredService(); + using IServiceScope scope = scopeFactory.CreateScope(); + await using ContentsDbContext context = scope.ServiceProvider.GetRequiredService(); await context.Database.MigrateAsync(cancellationToken); return app; diff --git a/backend/Modules/Contents/Features/AddPhotoToAlbum.cs b/backend/Modules/Contents/Features/AddPhotoToAlbum.cs index eb3c61c..80cf6d5 100644 --- a/backend/Modules/Contents/Features/AddPhotoToAlbum.cs +++ b/backend/Modules/Contents/Features/AddPhotoToAlbum.cs @@ -23,6 +23,7 @@ public record AddPhotoToAlbumResponse( public sealed class AddPhotoToAlbumRequestValidator : Validator { private const int MaxFileSizeBytes = 10 * 1024 * 1024; // 10MB + private static readonly string[] AllowedImageTypes = [ "image/jpeg", @@ -74,14 +75,14 @@ public class AddPhotoToAlbumHandler( AddPhotoToAlbumRequest request, CancellationToken ct) { - var userId = User.GetUserId(); + Guid userId = User.GetUserId(); // Fetch the album we want to add photos to - var album = await context + Album? album = await context .Albums .SingleOrDefaultAsync( a => a.Id == request.AlbumId && a.CreatedBy == userId, - cancellationToken: ct); + ct); if (album is null) { @@ -90,7 +91,7 @@ public class AddPhotoToAlbumHandler( } // Check if a photo with the same ID already exists - var existingPhoto = await context + bool existingPhoto = await context .AlbumPhotos .AnyAsync(p => p.Id == request.PhotoId, ct); @@ -102,16 +103,16 @@ public class AddPhotoToAlbumHandler( try { - var (originalUrl, thumbnailUrl) = await ProcessAndUploadImage(request, ct); + (string originalUrl, string thumbnailUrl) = await ProcessAndUploadImage(request, ct); // Get the next order number - var nextOrder = await context + int nextOrder = await context .AlbumPhotos .Where(p => p.AlbumId == request.AlbumId) .MaxAsync(p => (int?)p.Order, ct) ?? 0; // Create the album photo - var photo = new AlbumPhoto + AlbumPhoto photo = new() { Id = request.PhotoId, CreatedBy = userId, @@ -143,23 +144,23 @@ public class AddPhotoToAlbumHandler( AddPhotoToAlbumRequest request, CancellationToken ct) { - var originalFileName = Path.GetFileName(request.File.FileName); - var nameWithoutExt = Path.GetFileNameWithoutExtension(originalFileName); - var extension = Path.GetExtension(originalFileName); + string originalFileName = Path.GetFileName(request.File.FileName); + string nameWithoutExt = Path.GetFileNameWithoutExtension(originalFileName); + string extension = Path.GetExtension(originalFileName); - var filenameOriginal = $"{nameWithoutExt}{extension}"; - var filenameThumbnail = $"{nameWithoutExt}.thumbnail{extension}"; + string filenameOriginal = $"{nameWithoutExt}{extension}"; + string filenameThumbnail = $"{nameWithoutExt}.thumbnail{extension}"; - var blobOriginal = $"{SubDirectoryNames.Albums}/{request.AlbumId}/{filenameOriginal}"; - var blobThumbnail = $"{SubDirectoryNames.Albums}/{request.AlbumId}/{filenameThumbnail}"; + string blobOriginal = $"{SubDirectoryNames.Albums}/{request.AlbumId}/{filenameOriginal}"; + string blobThumbnail = $"{SubDirectoryNames.Albums}/{request.AlbumId}/{filenameThumbnail}"; // Process the original image - await using var originalStream = request.File.OpenReadStream(); - using var image = await Image.LoadAsync(originalStream, ct); + await using Stream originalStream = request.File.OpenReadStream(); + using Image image = await Image.LoadAsync(originalStream, ct); // Calculate target size while preserving the original aspect ratio - var originalWidth = image.Width; - var originalHeight = image.Height; + int originalWidth = image.Width; + int originalHeight = image.Height; double ratioX = (double)MaxThumbnailWidth / originalWidth; double ratioY = (double)MaxThumbnailHeight / originalHeight; @@ -169,20 +170,20 @@ public class AddPhotoToAlbumHandler( int newHeight = (int)(originalHeight * ratio); // Create thumbnail - using var thumbnailStream = new MemoryStream(); + using MemoryStream thumbnailStream = new(); image.Mutate(x => x.Resize(newWidth, newHeight)); await image.SaveAsync(thumbnailStream, image.Metadata.DecodedImageFormat!, ct); thumbnailStream.Position = 0; // Upload both versions - var originalUrl = await blobStorage.UploadFileAsync( + string originalUrl = await blobStorage.UploadFileAsync( ContainerNames.Creators, blobOriginal, request.File.OpenReadStream(), request.File.ContentType, ct); - var thumbnailUrl = await blobStorage.UploadFileAsync( + string thumbnailUrl = await blobStorage.UploadFileAsync( ContainerNames.Creators, blobThumbnail, thumbnailStream, diff --git a/backend/Modules/Contents/Features/CreateAlbum.cs b/backend/Modules/Contents/Features/CreateAlbum.cs index c0593d7..9fa923a 100644 --- a/backend/Modules/Contents/Features/CreateAlbum.cs +++ b/backend/Modules/Contents/Features/CreateAlbum.cs @@ -48,7 +48,7 @@ public class CreateAlbumHandler( CancellationToken ct) { // Check if an album with the same ID already exists - var existingAlbum = await context + bool existingAlbum = await context .Albums .AnyAsync(a => a.Id == request.AlbumId, ct); @@ -58,12 +58,7 @@ public class CreateAlbumHandler( return; } - var album = new Album - { - Id = request.AlbumId, - CreatedBy = User.GetUserId(), - Title = request.Title - }; + Album album = new() { Id = request.AlbumId, CreatedBy = User.GetUserId(), Title = request.Title }; context.Albums.Add(album); await context.SaveChangesAsync(ct); @@ -72,4 +67,4 @@ public class CreateAlbumHandler( new CreateAlbumResponse(album.Id), ct); } -} +} diff --git a/backend/Modules/Contents/Features/GetAlbum.cs b/backend/Modules/Contents/Features/GetAlbum.cs index e2ee516..e2ea44e 100644 --- a/backend/Modules/Contents/Features/GetAlbum.cs +++ b/backend/Modules/Contents/Features/GetAlbum.cs @@ -49,12 +49,12 @@ public class GetAlbumHandler( GetAlbumRequest request, CancellationToken ct) { - var album = await context + Album? album = await context .Albums .Include(a => a.Photos.OrderBy(p => p.Order)) .SingleOrDefaultAsync( a => a.Id == request.AlbumId, - cancellationToken: ct); + ct); if (album is null) { @@ -62,7 +62,7 @@ public class GetAlbumHandler( return; } - var photos = album.Photos + List photos = album.Photos .Select(p => new AlbumPhotoDto( p.Id, p.OriginalUrl, diff --git a/backend/Modules/Contents/Features/RemoveAlbum.cs b/backend/Modules/Contents/Features/RemoveAlbum.cs index 20c5e13..dcef46c 100644 --- a/backend/Modules/Contents/Features/RemoveAlbum.cs +++ b/backend/Modules/Contents/Features/RemoveAlbum.cs @@ -33,14 +33,14 @@ public class RemoveAlbumHandler( RemoveAlbumRequest request, CancellationToken ct) { - var userId = User.GetUserId(); + Guid userId = User.GetUserId(); - var album = await context + Album? album = await context .Albums .Include(a => a.Photos) .SingleOrDefaultAsync( a => a.Id == request.AlbumId && a.CreatedBy == userId, - cancellationToken: ct); + ct); if (album is null) { @@ -53,7 +53,7 @@ public class RemoveAlbumHandler( album.DeletedAt = DateTimeOffset.UtcNow; // Soft delete all photos in the album - foreach (var photo in album.Photos) + foreach (AlbumPhoto photo in album.Photos) { photo.DeletedBy = userId; photo.DeletedAt = DateTimeOffset.UtcNow; @@ -63,4 +63,4 @@ public class RemoveAlbumHandler( await SendNoContentAsync(ct); } -} +} diff --git a/backend/Modules/Contents/Features/RemovePhotoFromAlbum.cs b/backend/Modules/Contents/Features/RemovePhotoFromAlbum.cs index d93370b..c0d3d57 100644 --- a/backend/Modules/Contents/Features/RemovePhotoFromAlbum.cs +++ b/backend/Modules/Contents/Features/RemovePhotoFromAlbum.cs @@ -38,14 +38,14 @@ public class RemovePhotoFromAlbumHandler( RemovePhotoFromAlbumRequest request, CancellationToken ct) { - var userId = User.GetUserId(); + Guid userId = User.GetUserId(); - var album = await context + Album? album = await context .Albums .Include(a => a.Photos) .SingleOrDefaultAsync( a => a.Id == request.AlbumId && a.CreatedBy == userId, - cancellationToken: ct); + ct); if (album is null) { @@ -53,7 +53,7 @@ public class RemovePhotoFromAlbumHandler( return; } - var photo = album.Photos + AlbumPhoto? photo = album.Photos .SingleOrDefault(p => p.Id == request.PhotoId); if (photo is null) @@ -65,9 +65,9 @@ public class RemovePhotoFromAlbumHandler( // Soft delete the photo photo.DeletedBy = userId; photo.DeletedAt = DateTimeOffset.UtcNow; - + await context.SaveChangesAsync(ct); await SendNoContentAsync(ct); } -} +} diff --git a/backend/Modules/Contents/Models/ContentModel.cs b/backend/Modules/Contents/Models/ContentModel.cs index 874be1f..1e90d96 100644 --- a/backend/Modules/Contents/Models/ContentModel.cs +++ b/backend/Modules/Contents/Models/ContentModel.cs @@ -8,7 +8,7 @@ public class ContentModel public required string CreatedByName { get; init; } public required string? CreatedByPortraitUrl { get; init; } public required DateTimeOffset CreatedAt { get; init; } - public Guid? DeletedBy { get; init; } + public Guid? DeletedBy { get; init; } public DateTimeOffset? DeletedAt { get; init; } public required string Title { get; init; } public required string Description { get; init; } diff --git a/backend/Modules/Creators/Configuration/CreatorOptions.cs b/backend/Modules/Creators/Configuration/CreatorOptions.cs index 500720b..0711f44 100644 --- a/backend/Modules/Creators/Configuration/CreatorOptions.cs +++ b/backend/Modules/Creators/Configuration/CreatorOptions.cs @@ -3,6 +3,6 @@ public class CreatorOptions { public const string ConfigurationSection = "Creators"; - + public TimeSpan SlugReservationDuration { get; set; } } diff --git a/backend/Modules/Creators/Data/Creator.cs b/backend/Modules/Creators/Data/Creator.cs index 7f32a86..6534420 100644 --- a/backend/Modules/Creators/Data/Creator.cs +++ b/backend/Modules/Creators/Data/Creator.cs @@ -5,16 +5,16 @@ 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 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) + /// Soft‑delete flag (false by default, true once DeletedAt is set) /// - public bool IsDeleted { get; private set; } // private set → EF updates it + 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; } diff --git a/backend/Modules/Creators/Data/CreatorsDbContext.cs b/backend/Modules/Creators/Data/CreatorsDbContext.cs index d9f347a..bf0744e 100644 --- a/backend/Modules/Creators/Data/CreatorsDbContext.cs +++ b/backend/Modules/Creators/Data/CreatorsDbContext.cs @@ -17,7 +17,7 @@ public class CreatorsDbContext( modelBuilder .Entity() .Property(x => x.NormalizedName) - .HasComputedColumnSql("LOWER(\"Name\")", stored: true); + .HasComputedColumnSql("LOWER(\"Name\")", true); modelBuilder .Entity() @@ -27,7 +27,7 @@ public class CreatorsDbContext( modelBuilder .Entity() .Property(c => c.IsDeleted) - .HasComputedColumnSql("\"DeletedAt\" IS NOT NULL", stored: true); // bool + .HasComputedColumnSql("\"DeletedAt\" IS NOT NULL", true); // bool modelBuilder .Entity() @@ -38,7 +38,7 @@ public class CreatorsDbContext( .Entity() .OwnsOne(x => x.Presentation) .ToTable(nameof(Presentation)); - + modelBuilder .Entity() .HasQueryFilter(c => !c.IsDeleted); diff --git a/backend/Modules/Creators/Data/Socials.cs b/backend/Modules/Creators/Data/Socials.cs index 6d1a35f..d85b5da 100644 --- a/backend/Modules/Creators/Data/Socials.cs +++ b/backend/Modules/Creators/Data/Socials.cs @@ -12,4 +12,4 @@ public class Socials [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 index 5b33ce8..72ea53f 100644 --- a/backend/Modules/Creators/DependencyInjection.cs +++ b/backend/Modules/Creators/DependencyInjection.cs @@ -14,21 +14,21 @@ public static class DependencyInjection 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); + IServiceScopeFactory scopeFactory = app.ApplicationServices.GetRequiredService(); + using IServiceScope scope = scopeFactory.CreateScope(); + await using CreatorsDbContext context = scope.ServiceProvider.GetRequiredService(); + await context.Database.MigrateAsync(cancellationToken); return app; } diff --git a/backend/Modules/Creators/Features/ChangeBanner.cs b/backend/Modules/Creators/Features/ChangeBanner.cs index 90b91f4..ef0f62a 100644 --- a/backend/Modules/Creators/Features/ChangeBanner.cs +++ b/backend/Modules/Creators/Features/ChangeBanner.cs @@ -29,11 +29,11 @@ public static class ChangeBanner Request request, CancellationToken ct) { - var creator = await context + Creator? creator = await context .Creators .SingleOrDefaultAsync( c => c.Id == request.CreatorId, - cancellationToken: ct); + ct); if (creator is null) { @@ -41,7 +41,7 @@ public static class ChangeBanner return; } - var blobUrl = await blobStorage.UploadFileAsync( + string blobUrl = await blobStorage.UploadFileAsync( ContainerNames.Creators, $"{request.CreatorId}/{SubDirectoryNames.Profile}/{CommonFileNames.BannerPicture}", request.File.OpenReadStream(), diff --git a/backend/Modules/Creators/Features/ChangeEmail.cs b/backend/Modules/Creators/Features/ChangeEmail.cs index 80a1556..00cb221 100644 --- a/backend/Modules/Creators/Features/ChangeEmail.cs +++ b/backend/Modules/Creators/Features/ChangeEmail.cs @@ -38,12 +38,12 @@ public class ChangeEmailHandler( ChangeEmailRequest request, CancellationToken ct) { - var creator = await context + Creator? creator = await context .Creators .Include(c => c.Presentation) .SingleOrDefaultAsync( c => c.Id == request.CreatorId, - cancellationToken: ct); + ct); if (creator is null) { @@ -59,9 +59,9 @@ public class ChangeEmailHandler( } creator.Presentation.Email = request.Email?.Trim(); - + await context.SaveChangesAsync(ct); - + await SendOkAsync(ct); } -} +} diff --git a/backend/Modules/Creators/Features/ChangeLogo.cs b/backend/Modules/Creators/Features/ChangeLogo.cs index 2aa7aea..84a9d48 100644 --- a/backend/Modules/Creators/Features/ChangeLogo.cs +++ b/backend/Modules/Creators/Features/ChangeLogo.cs @@ -44,11 +44,11 @@ public class ChangeLogoHandler( ChangeLogoRequest request, CancellationToken ct) { - var creator = await context + Creator? creator = await context .Creators .SingleOrDefaultAsync( c => c.Id == request.CreatorId, - cancellationToken: ct); + ct); if (creator is null) { @@ -56,7 +56,7 @@ public class ChangeLogoHandler( return; } - var blobUrl = await blobStorage.UploadFileAsync( + string blobUrl = await blobStorage.UploadFileAsync( ContainerNames.Creators, $"{request.CreatorId}/{SubDirectoryNames.Profile}/{CommonFileNames.ProfilePicture}", request.File.OpenReadStream(), diff --git a/backend/Modules/Creators/Features/ChangeName.cs b/backend/Modules/Creators/Features/ChangeName.cs index 9a737c0..5d0bd49 100644 --- a/backend/Modules/Creators/Features/ChangeName.cs +++ b/backend/Modules/Creators/Features/ChangeName.cs @@ -34,11 +34,11 @@ public class ChangeNameHandler( ChangeNameRequest request, CancellationToken ct) { - var creator = await context + Creator creator = await context .Creators .SingleAsync( c => c.Id == request.CreatorId, - cancellationToken: ct); + ct); creator.Name = request.Name; diff --git a/backend/Modules/Creators/Features/ChangePhoneNumber.cs b/backend/Modules/Creators/Features/ChangePhoneNumber.cs index 70c49f0..54d22d7 100644 --- a/backend/Modules/Creators/Features/ChangePhoneNumber.cs +++ b/backend/Modules/Creators/Features/ChangePhoneNumber.cs @@ -38,12 +38,12 @@ public class ChangePhoneNumberHandler( ChangePhoneNumberRequest request, CancellationToken ct) { - var creator = await context + Creator? creator = await context .Creators .Include(c => c.Presentation) .SingleOrDefaultAsync( c => c.Id == request.CreatorId, - cancellationToken: ct); + ct); if (creator is null) { @@ -59,9 +59,9 @@ public class ChangePhoneNumberHandler( } creator.Presentation.PhoneNumber = request.PhoneNumber?.Trim(); - + await context.SaveChangesAsync(ct); - + await SendOkAsync(ct); } -} +} diff --git a/backend/Modules/Creators/Features/ChangePresentationInfos.cs b/backend/Modules/Creators/Features/ChangePresentationInfos.cs index 626ab9f..a9955b4 100644 --- a/backend/Modules/Creators/Features/ChangePresentationInfos.cs +++ b/backend/Modules/Creators/Features/ChangePresentationInfos.cs @@ -45,12 +45,12 @@ public class ChangePresentationInfosHandler( ChangePresentationInfosRequest request, CancellationToken ct) { - var creator = await context + Creator? creator = await context .Creators .Include(c => c.Presentation) .SingleOrDefaultAsync( c => c.Id == request.CreatorId, - cancellationToken: ct); + ct); if (creator is null) { @@ -60,12 +60,12 @@ public class ChangePresentationInfosHandler( // Update the presentation info with the new values creator.Presentation.Description = request.Description.Trim(); - creator.Presentation.VideoUrl = request.VideoUrl != null + creator.Presentation.VideoUrl = request.VideoUrl != null ? YouTubeUrlHelper.ExtractVideoId(request.VideoUrl.Trim()) : null; - + await context.SaveChangesAsync(ct); - + await SendOkAsync(ct); } } diff --git a/backend/Modules/Creators/Features/ChangeSlug.cs b/backend/Modules/Creators/Features/ChangeSlug.cs index 6244a55..c209fcc 100644 --- a/backend/Modules/Creators/Features/ChangeSlug.cs +++ b/backend/Modules/Creators/Features/ChangeSlug.cs @@ -1,5 +1,6 @@ using Hutopy.Infrastructure.Security; using Hutopy.Modules.Creators.Data; +using Microsoft.EntityFrameworkCore.Storage; namespace Hutopy.Modules.Creators.Features; @@ -17,7 +18,7 @@ internal sealed class ChangeSlugRequestValidator RuleFor(r => r.CreatorId) .NotNull().WithMessage("You should specify the CreatorId") .NotEmpty().WithMessage("You should specify a valid/not empty CreatorId"); - + RuleFor(r => r.SlugReservationId) .NotNull().WithMessage("You should specify the SlugReservationId") .NotEmpty().WithMessage("You should specify a valid/not empty SlugReservationId"); @@ -39,15 +40,15 @@ public class ChangeSlugHandler( ChangeSlugRequest request, CancellationToken ct) { - await using var transaction = await context.Database.BeginTransactionAsync(ct); + await using IDbContextTransaction transaction = await context.Database.BeginTransactionAsync(ct); try { - var creator = await context + Creator creator = await context .Creators .SingleAsync( c => c.Id == request.CreatorId, - cancellationToken: ct); + ct); if (creator.CreatedBy != User.GetUserId()) { @@ -55,7 +56,7 @@ public class ChangeSlugHandler( return; } - var reservation = await context + Slugs? reservation = await context .Slugs .FirstOrDefaultAsync( s => s.Id == request.SlugReservationId, @@ -67,7 +68,7 @@ public class ChangeSlugHandler( return; } - var previousReservation = await context + Slugs? previousReservation = await context .Slugs .FirstOrDefaultAsync( s => s.UsedBy == request.CreatorId, diff --git a/backend/Modules/Creators/Features/ChangeSocials.cs b/backend/Modules/Creators/Features/ChangeSocials.cs index 4ccb9e3..6dbd462 100644 --- a/backend/Modules/Creators/Features/ChangeSocials.cs +++ b/backend/Modules/Creators/Features/ChangeSocials.cs @@ -27,12 +27,12 @@ public class ChangeSocialsHandler( public override async Task HandleAsync(ChangeSocialsRequest request, CancellationToken ct) { - var creator = await context + Creator creator = await context .Creators .Include(c => c.Socials) .SingleAsync( c => c.Id == request.CreatorId, - cancellationToken: ct); + ct); creator.Socials.FacebookUrl = request.FacebookUrl; creator.Socials.InstagramUrl = request.InstagramUrl; diff --git a/backend/Modules/Creators/Features/ChangeTitle.cs b/backend/Modules/Creators/Features/ChangeTitle.cs index fecb2dc..49d42a5 100644 --- a/backend/Modules/Creators/Features/ChangeTitle.cs +++ b/backend/Modules/Creators/Features/ChangeTitle.cs @@ -19,14 +19,14 @@ public class ChangeTitleHandler( } public override async Task HandleAsync( - ChangeTitleRequest request, + ChangeTitleRequest request, CancellationToken ct) { - var creator = await context + Creator creator = await context .Creators .SingleAsync( c => c.Id == request.CreatorId, - cancellationToken: ct); + ct); creator.Title = request.Title; diff --git a/backend/Modules/Creators/Features/CreateCreator.cs b/backend/Modules/Creators/Features/CreateCreator.cs index d32096d..35d764e 100644 --- a/backend/Modules/Creators/Features/CreateCreator.cs +++ b/backend/Modules/Creators/Features/CreateCreator.cs @@ -1,5 +1,6 @@ using Hutopy.Infrastructure.Security; using Hutopy.Modules.Creators.Data; +using Microsoft.EntityFrameworkCore.Storage; namespace Hutopy.Modules.Creators.Features; @@ -17,7 +18,7 @@ public sealed class CreateCreatorRequestValidator : Validator r.CreatorId) .NotNull() .NotEmpty() @@ -40,11 +41,11 @@ public sealed class CreateCreatorHandler( CreateCreatorRequest req, CancellationToken ct) { - await using var transaction = await context.Database.BeginTransactionAsync(ct); - + await using IDbContextTransaction transaction = await context.Database.BeginTransactionAsync(ct); + try { - var slug = await context + Slugs slug = await context .Slugs .SingleAsync(s => s.Id == req.SlugReservationId, ct); @@ -55,23 +56,20 @@ public sealed class CreateCreatorHandler( await SendErrorsAsync(500, ct); return; } - + slug.UsedBy = req.CreatorId; - + await context.Creators.AddAsync( new Creator { - Id = req.CreatorId, - CreatedBy = User.GetUserId(), - Name = slug.Name, - Slug = slug.NormalizedName + Id = req.CreatorId, CreatedBy = User.GetUserId(), Name = slug.Name, Slug = slug.NormalizedName }, ct); await context.SaveChangesAsync(ct); await transaction.CommitAsync(ct); - + await SendOkAsync(ct); } catch (Exception) diff --git a/backend/Modules/Creators/Features/GetCreatorById.cs b/backend/Modules/Creators/Features/GetCreatorById.cs index c78edfd..7659831 100644 --- a/backend/Modules/Creators/Features/GetCreatorById.cs +++ b/backend/Modules/Creators/Features/GetCreatorById.cs @@ -28,7 +28,7 @@ public class GetCreatorByIdHandler( public override void Configure() { Get("/api/creators/{CreatorId}"); - Options((o => o.WithTags("Creators"))); + Options(o => o.WithTags("Creators")); AllowAnonymous(); } @@ -36,13 +36,19 @@ public class GetCreatorByIdHandler( GetCreatorByIdRequest req, CancellationToken ct) { - var creator = await context + Creator? creator = await context .Creators .FindAsync( [req.CreatorId], - cancellationToken: ct); + 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/Modules/Creators/Features/GetCreatorBySlug.cs b/backend/Modules/Creators/Features/GetCreatorBySlug.cs index cbfc4b4..d8490bd 100644 --- a/backend/Modules/Creators/Features/GetCreatorBySlug.cs +++ b/backend/Modules/Creators/Features/GetCreatorBySlug.cs @@ -49,7 +49,7 @@ public class GetCreatorBySlugHandler( public override void Configure() { Get("/api/creators/@{Name}"); - Options((o => o.WithTags("Creators"))); + Options(o => o.WithTags("Creators")); AllowAnonymous(); } @@ -57,9 +57,9 @@ public class GetCreatorBySlugHandler( GetCreatorBySlugRequest req, CancellationToken ct) { - var creatorName = req.Name.ToLower(); + string creatorName = req.Name.ToLower(); - var response = await context + GetCreatorBySlugResponse? response = await context .Creators .IgnoreQueryFilters() .Where(c => EF.Functions.ILike(c.Slug, creatorName)) diff --git a/backend/Modules/Creators/Features/RemoveCreator.cs b/backend/Modules/Creators/Features/RemoveCreator.cs index d4306f1..884ecd9 100644 --- a/backend/Modules/Creators/Features/RemoveCreator.cs +++ b/backend/Modules/Creators/Features/RemoveCreator.cs @@ -34,12 +34,12 @@ public sealed class RemoveCreatorHandler( RemoveCreatorRequest req, CancellationToken ct) { - var creatorSlug = req.CreatorSlug.ToLower(); + string creatorSlug = req.CreatorSlug.ToLower(); - var creator = await context + Creator? creator = await context .Creators .Where(c => EF.Functions.ILike(c.Slug, creatorSlug)) - .SingleOrDefaultAsync(cancellationToken: ct); + .SingleOrDefaultAsync(ct); if (creator is null) { diff --git a/backend/Modules/Creators/Features/ReserveSlug.cs b/backend/Modules/Creators/Features/ReserveSlug.cs index 6f61bd6..68b3189 100644 --- a/backend/Modules/Creators/Features/ReserveSlug.cs +++ b/backend/Modules/Creators/Features/ReserveSlug.cs @@ -4,6 +4,7 @@ using Hutopy.Infrastructure.Security; using Hutopy.Modules.Creators.Configuration; using Hutopy.Modules.Creators.Data; using Hutopy.Modules.Creators.Services; +using Microsoft.EntityFrameworkCore.Storage; using Microsoft.Extensions.Options; using Npgsql; @@ -45,33 +46,31 @@ public sealed class ReserveSlug( ReserveSlugRequest req, CancellationToken ct) { - await using var transaction = await context.Database.BeginTransactionAsync(ct); + await using IDbContextTransaction transaction = await context.Database.BeginTransactionAsync(ct); try { // First, purge any expired slugs await slugPurger.PurgeExpiredSlugsAsync(ct); - var reservation = await context.Slugs.FirstOrDefaultAsync( - s => s.Id == req.ReservationId && s.CreatedBy == User.GetUserId(), - cancellationToken: ct); + Slugs? reservation = await context.Slugs.FirstOrDefaultAsync( + s => s.Id == req.ReservationId && s.CreatedBy == User.GetUserId(), + ct); if (reservation == null) { reservation = new Slugs { - Id = req.ReservationId, - CreatedBy = User.GetUserId(), - CreatedAt = DateTimeOffset.UtcNow, + Id = req.ReservationId, CreatedBy = User.GetUserId(), CreatedAt = DateTimeOffset.UtcNow }; - + context.Slugs.Attach(reservation); context.Entry(reservation).State = EntityState.Added; } reservation.Name = req.Slug; reservation.ReservedUntil = DateTimeOffset.UtcNow + opts.Value.SlugReservationDuration; - + await context.SaveChangesAsync(ct); await transaction.CommitAsync(ct); @@ -81,7 +80,7 @@ public sealed class ReserveSlug( catch (Exception e) { await transaction.RollbackAsync(ct); - + Logger.LogError("Transaction failed: {Message}", e.Message); if (e.InnerException is PostgresException innerException) diff --git a/backend/Modules/Creators/Features/RestoreCreator.cs b/backend/Modules/Creators/Features/RestoreCreator.cs index 91333b6..bbf4b0e 100644 --- a/backend/Modules/Creators/Features/RestoreCreator.cs +++ b/backend/Modules/Creators/Features/RestoreCreator.cs @@ -34,13 +34,13 @@ public sealed class RestoreCreatorHandler( RestoreCreatorRequest req, CancellationToken ct) { - var creatorSlug = req.CreatorSlug.ToLower(); + string creatorSlug = req.CreatorSlug.ToLower(); - var creator = await context + Creator? creator = await context .Creators .IgnoreQueryFilters() .Where(c => EF.Functions.ILike(c.Slug, creatorSlug)) - .SingleOrDefaultAsync(cancellationToken: ct); + .SingleOrDefaultAsync(ct); if (creator is null) { diff --git a/backend/Modules/Creators/Services/SlugPurger.cs b/backend/Modules/Creators/Services/SlugPurger.cs index 4b880e5..e605c71 100644 --- a/backend/Modules/Creators/Services/SlugPurger.cs +++ b/backend/Modules/Creators/Services/SlugPurger.cs @@ -19,7 +19,7 @@ public class SlugPurger(CreatorsDbContext context) try { - var now = DateTimeOffset.UtcNow; + DateTimeOffset now = DateTimeOffset.UtcNow; if (now - s_lastPurgeTime < MinTimeBetweenPurges) { // Not enough time has passed since the last purge @@ -40,4 +40,4 @@ public class SlugPurger(CreatorsDbContext context) Semaphore.Release(); } } -} +} diff --git a/backend/Modules/Identity/Configuration/JwtOptions.cs b/backend/Modules/Identity/Configuration/JwtOptions.cs index a8c0bd6..7c4c822 100644 --- a/backend/Modules/Identity/Configuration/JwtOptions.cs +++ b/backend/Modules/Identity/Configuration/JwtOptions.cs @@ -3,12 +3,12 @@ public record JwtOptions { public const string SectionName = "Authentication:Jwt"; - + public required TimeSpan Lifetime { get; init; } public required string Issuer { get; init; } public required string Audience { get; init; } public required string Key { get; init; } - + public TimeSpan RefreshTokenLifetime { get; init; } public bool RefreshTokenRequireRotation { get; init; } } diff --git a/backend/Modules/Identity/Data/IdentityDbContext.cs b/backend/Modules/Identity/Data/IdentityDbContext.cs index d4bd96b..f15ce65 100644 --- a/backend/Modules/Identity/Data/IdentityDbContext.cs +++ b/backend/Modules/Identity/Data/IdentityDbContext.cs @@ -1,20 +1,18 @@ using Microsoft.AspNetCore.Identity.EntityFrameworkCore; -namespace Hutopy.Modules.Identity.Data -{ - public class IdentityDbContext( - DbContextOptions options) - : IdentityDbContext(options) - { - public const string SchemaName = "Identity"; - - protected override void OnModelCreating(ModelBuilder - modelBuilder) - { - base.OnModelCreating(modelBuilder); - - modelBuilder.HasDefaultSchema(SchemaName); - } +namespace Hutopy.Modules.Identity.Data; +public class IdentityDbContext( + DbContextOptions options) + : IdentityDbContext(options) +{ + public const string SchemaName = "Identity"; + + protected override void OnModelCreating(ModelBuilder + modelBuilder) + { + base.OnModelCreating(modelBuilder); + + modelBuilder.HasDefaultSchema(SchemaName); } } diff --git a/backend/Modules/Identity/Data/IdentityService.cs b/backend/Modules/Identity/Data/IdentityService.cs index 9174dcb..8521e25 100644 --- a/backend/Modules/Identity/Data/IdentityService.cs +++ b/backend/Modules/Identity/Data/IdentityService.cs @@ -10,19 +10,22 @@ public class IdentityService( { public async Task GetCurrentUserAsync() { - var currentUserId = contextAccessor.HttpContext?.User.FindFirst(ClaimTypes.NameIdentifier)?.Value; + string? currentUserId = contextAccessor.HttpContext?.User.FindFirst(ClaimTypes.NameIdentifier)?.Value; if (string.IsNullOrEmpty(currentUserId)) { return null; } UserModel? ret; - var user = await userManager.FindByIdAsync(currentUserId); + User? user = await userManager.FindByIdAsync(currentUserId); - if (user == null) ret = null; + if (user == null) + { + ret = null; + } else { - var userModel = new UserModel + UserModel userModel = new() { Id = user.Id, Username = user.UserName ?? string.Empty, @@ -44,17 +47,22 @@ public class IdentityService( public async Task> GetCurrentUserRolesAsync() { - var currentUserModel = await GetCurrentUserAsync(); + UserModel? currentUserModel = await GetCurrentUserAsync(); - if (currentUserModel is null) return []; - - var currentUser = await userManager.FindByIdAsync(currentUserModel.Id.ToString()); + if (currentUserModel is null) + { + return []; + } - if (currentUser is null) return []; + User? currentUser = await userManager.FindByIdAsync(currentUserModel.Id.ToString()); - var userRoles = await userManager.GetRolesAsync(currentUser); + if (currentUser is null) + { + return []; + } + + IList userRoles = await userManager.GetRolesAsync(currentUser); return userRoles; } - } diff --git a/backend/Modules/Identity/Data/User.cs b/backend/Modules/Identity/Data/User.cs index 43c028d..2f14f48 100644 --- a/backend/Modules/Identity/Data/User.cs +++ b/backend/Modules/Identity/Data/User.cs @@ -17,4 +17,3 @@ public class User : IdentityUser public DateTime RefreshTokenExpiryTime { get; set; } public string Fullname => $"{Lastname}, {Firstname}"; } - diff --git a/backend/Modules/Identity/Handlers/ChangeAddress.cs b/backend/Modules/Identity/Handlers/ChangeAddress.cs index 0ceadb8..d586c84 100644 --- a/backend/Modules/Identity/Handlers/ChangeAddress.cs +++ b/backend/Modules/Identity/Handlers/ChangeAddress.cs @@ -1,5 +1,6 @@ using Hutopy.Infrastructure.Security; using Hutopy.Modules.Identity.Data; +using Microsoft.AspNetCore.Identity; namespace Hutopy.Modules.Identity.Handlers; @@ -22,7 +23,7 @@ public class ChangeAddressHandler( ChangeAddressRequest request, CancellationToken ct) { - var user = await userManager.FindByIdAsync(HttpContext.User.GetUserId().ToString()); + User? user = await userManager.FindByIdAsync(HttpContext.User.GetUserId().ToString()); if (user is null) { @@ -32,11 +33,15 @@ public class ChangeAddressHandler( user.Address = request.Address; - var result = await userManager.UpdateAsync(user); + IdentityResult result = await userManager.UpdateAsync(user); if (result.Succeeded) + { await SendOkAsync(ct); + } else + { await SendUnauthorizedAsync(ct); + } } } diff --git a/backend/Modules/Identity/Handlers/ChangeAlias.cs b/backend/Modules/Identity/Handlers/ChangeAlias.cs index 64730e0..14c25fd 100644 --- a/backend/Modules/Identity/Handlers/ChangeAlias.cs +++ b/backend/Modules/Identity/Handlers/ChangeAlias.cs @@ -1,5 +1,6 @@ using Hutopy.Infrastructure.Security; using Hutopy.Modules.Identity.Data; +using Microsoft.AspNetCore.Identity; namespace Hutopy.Modules.Identity.Handlers; @@ -22,7 +23,7 @@ public class ChangeAliasHandler( ChangeAliasRequest request, CancellationToken ct) { - var user = await userManager.FindByIdAsync(HttpContext.User.GetUserId().ToString()); + User? user = await userManager.FindByIdAsync(HttpContext.User.GetUserId().ToString()); if (user is null) { @@ -32,11 +33,15 @@ public class ChangeAliasHandler( user.Alias = request.Alias; - var result = await userManager.UpdateAsync(user); + IdentityResult result = await userManager.UpdateAsync(user); if (result.Succeeded) + { await SendOkAsync(ct); + } else + { await SendUnauthorizedAsync(ct); + } } } diff --git a/backend/Modules/Identity/Handlers/ChangeBirthDate.cs b/backend/Modules/Identity/Handlers/ChangeBirthDate.cs index 533a874..a9c8c55 100644 --- a/backend/Modules/Identity/Handlers/ChangeBirthDate.cs +++ b/backend/Modules/Identity/Handlers/ChangeBirthDate.cs @@ -1,5 +1,6 @@ using Hutopy.Infrastructure.Security; using Hutopy.Modules.Identity.Data; +using Microsoft.AspNetCore.Identity; namespace Hutopy.Modules.Identity.Handlers; @@ -22,7 +23,7 @@ public class ChangeBirthDateHandler( ChangeBirthDateRequest request, CancellationToken ct) { - var user = await userManager.FindByIdAsync(HttpContext.User.GetUserId().ToString()); + User? user = await userManager.FindByIdAsync(HttpContext.User.GetUserId().ToString()); if (user is null) { @@ -32,11 +33,15 @@ public class ChangeBirthDateHandler( user.BirthDate = request.BirthDate; - var result = await userManager.UpdateAsync(user); + IdentityResult result = await userManager.UpdateAsync(user); if (result.Succeeded) + { await SendOkAsync(ct); + } else + { await SendUnauthorizedAsync(ct); + } } } diff --git a/backend/Modules/Identity/Handlers/ChangeEmail.cs b/backend/Modules/Identity/Handlers/ChangeEmail.cs index e1ecc58..e9d4349 100644 --- a/backend/Modules/Identity/Handlers/ChangeEmail.cs +++ b/backend/Modules/Identity/Handlers/ChangeEmail.cs @@ -1,5 +1,6 @@ using Hutopy.Infrastructure.Security; using Hutopy.Modules.Identity.Data; +using Microsoft.AspNetCore.Identity; namespace Hutopy.Modules.Identity.Handlers; @@ -22,7 +23,7 @@ public class ChangeEmailHandler( ChangeEmailRequest request, CancellationToken ct) { - var user = await userManager.FindByIdAsync(HttpContext.User.GetUserId().ToString()); + User? user = await userManager.FindByIdAsync(HttpContext.User.GetUserId().ToString()); if (user is null) { @@ -33,11 +34,15 @@ public class ChangeEmailHandler( user.Email = request.Email; // TODO: check to see if identity resets the `email confirmed` flag - @jonathan - var result = await userManager.UpdateAsync(user); + IdentityResult result = await userManager.UpdateAsync(user); if (result.Succeeded) + { await SendOkAsync(ct); + } else + { await SendUnauthorizedAsync(ct); + } } } diff --git a/backend/Modules/Identity/Handlers/ChangeFullname.cs b/backend/Modules/Identity/Handlers/ChangeFullname.cs index c7a66e1..40bb5ec 100644 --- a/backend/Modules/Identity/Handlers/ChangeFullname.cs +++ b/backend/Modules/Identity/Handlers/ChangeFullname.cs @@ -1,5 +1,6 @@ using Hutopy.Infrastructure.Security; using Hutopy.Modules.Identity.Data; +using Microsoft.AspNetCore.Identity; namespace Hutopy.Modules.Identity.Handlers; @@ -23,7 +24,7 @@ public class ChangeFullnameHandler( ChangeFullnameRequest request, CancellationToken ct) { - var user = await userManager.FindByIdAsync(HttpContext.User.GetUserId().ToString()); + User? user = await userManager.FindByIdAsync(HttpContext.User.GetUserId().ToString()); if (user is null) { @@ -34,11 +35,15 @@ public class ChangeFullnameHandler( user.Firstname = request.Firstname; user.Lastname = request.Lastname; - var result = await userManager.UpdateAsync(user); + IdentityResult result = await userManager.UpdateAsync(user); if (result.Succeeded) + { await SendOkAsync(ct); + } else + { await SendUnauthorizedAsync(ct); + } } } diff --git a/backend/Modules/Identity/Handlers/ChangePhone.cs b/backend/Modules/Identity/Handlers/ChangePhone.cs index 833b33c..fe93120 100644 --- a/backend/Modules/Identity/Handlers/ChangePhone.cs +++ b/backend/Modules/Identity/Handlers/ChangePhone.cs @@ -1,5 +1,6 @@ using Hutopy.Infrastructure.Security; using Hutopy.Modules.Identity.Data; +using Microsoft.AspNetCore.Identity; namespace Hutopy.Modules.Identity.Handlers; @@ -22,7 +23,7 @@ public class ChangePhoneHandler( ChangePhoneRequest request, CancellationToken ct) { - var user = await userManager.FindByIdAsync(HttpContext.User.GetUserId().ToString()); + User? user = await userManager.FindByIdAsync(HttpContext.User.GetUserId().ToString()); if (user is null) { @@ -33,11 +34,15 @@ public class ChangePhoneHandler( user.PhoneNumber = request.PhoneNumber; // TODO: check to see if identity resets the `phone confirmed` flag - @jonathan - var result = await userManager.UpdateAsync(user); + IdentityResult result = await userManager.UpdateAsync(user); if (result.Succeeded) + { await SendOkAsync(ct); + } else + { await SendUnauthorizedAsync(ct); + } } } diff --git a/backend/Modules/Identity/Handlers/ChangePortrait.cs b/backend/Modules/Identity/Handlers/ChangePortrait.cs index 7869585..b6f0c61 100644 --- a/backend/Modules/Identity/Handlers/ChangePortrait.cs +++ b/backend/Modules/Identity/Handlers/ChangePortrait.cs @@ -1,6 +1,7 @@ using Hutopy.Infrastructure.BlobStorage.Contracts; using Hutopy.Infrastructure.Security; using Hutopy.Modules.Identity.Data; +using Microsoft.AspNetCore.Identity; namespace Hutopy.Modules.Identity.Handlers; @@ -40,7 +41,7 @@ public class ChangePortraitHandler( ChangePortraitRequest request, CancellationToken ct) { - var user = await userManager.FindByIdAsync(HttpContext.User.GetUserId().ToString()); + User? user = await userManager.FindByIdAsync(HttpContext.User.GetUserId().ToString()); if (user is null) { @@ -48,7 +49,7 @@ public class ChangePortraitHandler( return; } - var blobUrl = await blobStorage.UploadFileAsync( + string blobUrl = await blobStorage.UploadFileAsync( ContainerNames.Users, $"{user.Id}/{SubDirectoryNames.Profile}/{CommonFileNames.ProfilePicture}", request.File.OpenReadStream(), @@ -57,7 +58,7 @@ public class ChangePortraitHandler( user.PortraitUrl = blobUrl; - var result = await userManager.UpdateAsync(user); + IdentityResult result = await userManager.UpdateAsync(user); if (result.Succeeded) { diff --git a/backend/Modules/Identity/Handlers/GetCurrentUser.cs b/backend/Modules/Identity/Handlers/GetCurrentUser.cs index a8fd411..eff14d3 100644 --- a/backend/Modules/Identity/Handlers/GetCurrentUser.cs +++ b/backend/Modules/Identity/Handlers/GetCurrentUser.cs @@ -17,16 +17,16 @@ public class GetCurrentUserQueryHandler( public override async Task HandleAsync( CancellationToken cancellationToken) { - var userModel = await identityService.GetCurrentUserAsync(); + UserModel? userModel = await identityService.GetCurrentUserAsync(); if (userModel is null) { await SendNotFoundAsync(cancellationToken); return; } - - var roles = await identityService.GetCurrentUserRolesAsync(); - + + IList roles = await identityService.GetCurrentUserRolesAsync(); + await SendOkAsync( new UserDto { diff --git a/backend/Modules/Identity/Handlers/GetCurrentUserProfilePicture.cs b/backend/Modules/Identity/Handlers/GetCurrentUserProfilePicture.cs index a26825d..71d8732 100644 --- a/backend/Modules/Identity/Handlers/GetCurrentUserProfilePicture.cs +++ b/backend/Modules/Identity/Handlers/GetCurrentUserProfilePicture.cs @@ -1,5 +1,6 @@ using Hutopy.Infrastructure.BlobStorage.Contracts; using Hutopy.Modules.Identity.Data; +using Hutopy.Modules.Identity.Models; namespace Hutopy.Modules.Identity.Handlers; @@ -19,7 +20,7 @@ public class GetCurrentUserPortraitHandler( public override async Task HandleAsync( CancellationToken cancellationToken) { - var identityUser = await identityService.GetCurrentUserAsync(); + UserModel? identityUser = await identityService.GetCurrentUserAsync(); if (identityUser is null) { @@ -27,7 +28,7 @@ public class GetCurrentUserPortraitHandler( return; } - var stream = await blobStorage.DownloadFileAsync( + MemoryStream stream = await blobStorage.DownloadFileAsync( ContainerNames.Users, $"{identityUser.Id.ToString()}/{SubDirectoryNames.Profile}/{CommonFileNames.ProfilePicture}", cancellationToken); diff --git a/backend/Modules/Identity/Handlers/RefreshToken.cs b/backend/Modules/Identity/Handlers/RefreshToken.cs index c3304b9..2950509 100644 --- a/backend/Modules/Identity/Handlers/RefreshToken.cs +++ b/backend/Modules/Identity/Handlers/RefreshToken.cs @@ -32,7 +32,7 @@ public class RefreshTokenHandler( CancellationToken ct) { // Find the user using the refresh token - var user = await userManager.Users + User? user = await userManager.Users .FirstOrDefaultAsync(u => u.RefreshToken == request.RefreshToken, ct); if (user == null || user.RefreshTokenExpiryTime <= DateTime.UtcNow) @@ -52,20 +52,20 @@ public class RefreshTokenHandler( await userManager.UpdateAsync(user); // Generate a new access token - var accessToken = JwtTokenHelper.GenerateJwtToken( - expiresIn: jwtOptions.Value.Lifetime, - issuer: jwtOptions.Value.Issuer, - audience: jwtOptions.Value.Audience, - key: jwtOptions.Value.Key, - userId: user.Id.ToString(), - email: user.Email ?? string.Empty, - alias: user.Alias, - firstname: user.Firstname ?? string.Empty, - lastname: user.Lastname ?? string.Empty, - portraitUrl: user.PortraitUrl); + string accessToken = JwtTokenHelper.GenerateJwtToken( + jwtOptions.Value.Lifetime, + jwtOptions.Value.Issuer, + jwtOptions.Value.Audience, + jwtOptions.Value.Key, + user.Id.ToString(), + user.Email ?? string.Empty, + user.Alias, + user.Firstname ?? string.Empty, + user.Lastname ?? string.Empty, + user.PortraitUrl); await SendOkAsync( new RefreshTokenResponse(accessToken, user.RefreshToken), - cancellation: ct); + ct); } -} +} diff --git a/backend/Modules/Identity/Handlers/ResetPassword.cs b/backend/Modules/Identity/Handlers/ResetPassword.cs index e65c0c7..d1286da 100644 --- a/backend/Modules/Identity/Handlers/ResetPassword.cs +++ b/backend/Modules/Identity/Handlers/ResetPassword.cs @@ -1,4 +1,5 @@ using Hutopy.Modules.Identity.Data; +using Microsoft.AspNetCore.Identity; namespace Hutopy.Modules.Identity.Handlers; @@ -25,7 +26,7 @@ public class ResetPasswordHandler( CancellationToken ct) { // Find user by email - var user = await userManager.FindByEmailAsync(request.Email); + User? user = await userManager.FindByEmailAsync(request.Email); if (user is null) { await SendStringAsync( @@ -36,7 +37,7 @@ public class ResetPasswordHandler( } // Reset password with token - var result = await userManager.ResetPasswordAsync( + IdentityResult result = await userManager.ResetPasswordAsync( user, request.Token, request.NewPassword); diff --git a/backend/Modules/Identity/Handlers/SetPassword.cs b/backend/Modules/Identity/Handlers/SetPassword.cs index 12215a5..2e80c1c 100644 --- a/backend/Modules/Identity/Handlers/SetPassword.cs +++ b/backend/Modules/Identity/Handlers/SetPassword.cs @@ -1,5 +1,6 @@ using Hutopy.Infrastructure.Security; using Hutopy.Modules.Identity.Data; +using Microsoft.AspNetCore.Identity; namespace Hutopy.Modules.Identity.Handlers; @@ -23,19 +24,19 @@ public class SetPasswordHandler( CancellationToken ct) { // Get current user id from claims - var userId = User.GetUserId().ToString(); - + string userId = User.GetUserId().ToString(); + // Get user from database - var user = await userManager.FindByIdAsync(userId); + User? user = await userManager.FindByIdAsync(userId); if (user is null) { await SendForbiddenAsync(ct); return; } - var resetToken = await userManager.GeneratePasswordResetTokenAsync(user); - var result = await userManager.ResetPasswordAsync(user, resetToken, request.NewPassword); - + string resetToken = await userManager.GeneratePasswordResetTokenAsync(user); + IdentityResult result = await userManager.ResetPasswordAsync(user, resetToken, request.NewPassword); + if (!result.Succeeded) { await SendStringAsync( @@ -44,7 +45,7 @@ public class SetPasswordHandler( cancellation: ct); return; } - + await SendOkAsync(ct); } } diff --git a/backend/Modules/Identity/Models/Result.cs b/backend/Modules/Identity/Models/Result.cs index 1cb4786..960b530 100644 --- a/backend/Modules/Identity/Models/Result.cs +++ b/backend/Modules/Identity/Models/Result.cs @@ -1,12 +1,12 @@ namespace Hutopy.Modules.Identity.Models; public class Result( - bool succeeded, + bool succeeded, IEnumerable errors) { public bool Succeeded { get; init; } = succeeded; public string[] Errors { get; init; } = errors.ToArray(); - + public static Result Success() { return new Result(true, Array.Empty()); @@ -20,18 +20,18 @@ public class Result( public class Result( T? value, - bool succeeded, + bool succeeded, IEnumerable errors) { public bool Succeeded { get; init; } = succeeded; public string[] Errors { get; init; } = errors.ToArray(); public T? Value { get; set; } = value; - + public T GetValueOrDefault() { return Value ?? default(T)!; } - + public string GetErrorsAsString() { return Errors.Length == 0 ? string.Empty : string.Join(", ", Errors); diff --git a/backend/Modules/Identity/Services/UserLookup.cs b/backend/Modules/Identity/Services/UserLookup.cs index 0e8ad68..f5315e4 100644 --- a/backend/Modules/Identity/Services/UserLookup.cs +++ b/backend/Modules/Identity/Services/UserLookup.cs @@ -9,7 +9,7 @@ public sealed class UserLookup( { public async Task GetUserAsync(Guid userId, CancellationToken cancellationToken = default) { - var user = await userManager.FindByIdAsync(userId.ToString()); + User? user = await userManager.FindByIdAsync(userId.ToString()); return user is null ? null diff --git a/backend/Modules/Memberships/Data/Membership.cs b/backend/Modules/Memberships/Data/Membership.cs index 1edc7de..3daceba 100644 --- a/backend/Modules/Memberships/Data/Membership.cs +++ b/backend/Modules/Memberships/Data/Membership.cs @@ -14,7 +14,7 @@ public class Membership 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; } - + [MaxLength(256)] public string? StripeSubscriptionId { get; set; } + public ICollection Payments { get; set; } = []; } diff --git a/backend/Modules/Memberships/Data/MembershipsDbContext.cs b/backend/Modules/Memberships/Data/MembershipsDbContext.cs index 73c2d88..f5f1190 100644 --- a/backend/Modules/Memberships/Data/MembershipsDbContext.cs +++ b/backend/Modules/Memberships/Data/MembershipsDbContext.cs @@ -9,8 +9,8 @@ public sealed class MembershipsDbContext( public DbSet MembershipTiers => Set(); public DbSet Memberships => Set(); public DbSet Payments => Set(); - - + + protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.HasDefaultSchema(SchemaName); @@ -20,7 +20,7 @@ public sealed class MembershipsDbContext( .Property(c => c.CreatedAt) .ValueGeneratedOnAdd() .HasDefaultValueSql("CURRENT_TIMESTAMP"); - + modelBuilder .Entity() .Property(c => c.CreatedAt) diff --git a/backend/Modules/Memberships/Data/Payment.cs b/backend/Modules/Memberships/Data/Payment.cs index e7d4163..eda0325 100644 --- a/backend/Modules/Memberships/Data/Payment.cs +++ b/backend/Modules/Memberships/Data/Payment.cs @@ -9,5 +9,4 @@ public class Payment 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 index 9a798b5..1edfc8f 100644 --- a/backend/Modules/Memberships/DependencyInjection.cs +++ b/backend/Modules/Memberships/DependencyInjection.cs @@ -13,19 +13,19 @@ public static class DependencyInjection 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); + IServiceScopeFactory scopeFactory = app.ApplicationServices.GetRequiredService(); + using IServiceScope scope = scopeFactory.CreateScope(); + await using MembershipsDbContext context = scope.ServiceProvider.GetRequiredService(); + await context.Database.MigrateAsync(cancellationToken); return app; } diff --git a/backend/Modules/Memberships/Handlers/CancelMembership.cs b/backend/Modules/Memberships/Handlers/CancelMembership.cs index 55f5489..e093048 100644 --- a/backend/Modules/Memberships/Handlers/CancelMembership.cs +++ b/backend/Modules/Memberships/Handlers/CancelMembership.cs @@ -19,18 +19,18 @@ public class CancelMembershipHandler( Delete("/api/memberships"); Options(o => o.WithTags("Memberships")); } - + public override async Task HandleAsync( CancelMembershipRequest req, CancellationToken ct) { - var subscription = await dbContext + Membership? subscription = await dbContext .Memberships .FindAsync( [req.SubscriptionId], - cancellationToken: ct); + ct); - if (subscription is not { EndDate: null } + if (subscription is not { EndDate: null } || subscription.StripeSubscriptionId is null) { await SendNotFoundAsync(ct); diff --git a/backend/Modules/Memberships/Handlers/CreateMembershipTier.cs b/backend/Modules/Memberships/Handlers/CreateMembershipTier.cs index 9a09f40..741ee26 100644 --- a/backend/Modules/Memberships/Handlers/CreateMembershipTier.cs +++ b/backend/Modules/Memberships/Handlers/CreateMembershipTier.cs @@ -27,9 +27,9 @@ public class CreateMembershipTierEndpoint( CreateMembershipTierRequest req, CancellationToken ct) { - var tierId = Guid.CreateVersion7(); + Guid tierId = Guid.CreateVersion7(); - var productId = await membershipTierProcessor.CreateAsync( + string productId = await membershipTierProcessor.CreateAsync( req.CreatorId, tierId, req.Name, @@ -37,14 +37,14 @@ public class CreateMembershipTierEndpoint( req.Price); // Record the new Tier - var tier = new MembershipTier + MembershipTier tier = new() { Id = tierId, CreatorId = req.CreatorId, Price = req.Price, Name = req.Name, Description = req.Description, - StripeProductId = productId, + StripeProductId = productId }; dbContext.MembershipTiers.Add(tier); diff --git a/backend/Modules/Memberships/Handlers/GetActiveMemberships.cs b/backend/Modules/Memberships/Handlers/GetActiveMemberships.cs index 1124db4..346c13a 100644 --- a/backend/Modules/Memberships/Handlers/GetActiveMemberships.cs +++ b/backend/Modules/Memberships/Handlers/GetActiveMemberships.cs @@ -28,16 +28,16 @@ public class GetActiveMembershipsHandler( public override async Task HandleAsync( CancellationToken ct) { - var subscriptions = await dbContext + List subscriptions = await dbContext .Memberships .Where(subscription => subscription.UserId == User.GetUserId()) .Where(subscription => subscription.State == MembershipState.Active) .ToListAsync(ct); - var result = await Task.WhenAll( + GetActiveMembershipsResponse[] result = await Task.WhenAll( subscriptions.Select(async subscription => { - var creator = await creatorLookup.GetCreatorAsync(subscription.CreatorId, ct); + CreatorReference? creator = await creatorLookup.GetCreatorAsync(subscription.CreatorId, ct); return new GetActiveMembershipsResponse( subscription.Id, diff --git a/backend/Modules/Memberships/Handlers/GetMembershipTiers.cs b/backend/Modules/Memberships/Handlers/GetMembershipTiers.cs index f0cc625..cd44a1b 100644 --- a/backend/Modules/Memberships/Handlers/GetMembershipTiers.cs +++ b/backend/Modules/Memberships/Handlers/GetMembershipTiers.cs @@ -4,7 +4,7 @@ namespace Hutopy.Modules.Memberships.Handlers; [PublicAPI] public record GetMembershipTiersRequest -{ +{ public Guid CreatorId { get; set; } } @@ -34,7 +34,7 @@ public class GetMembershipTiersEndpoint( GetMembershipTiersRequest req, CancellationToken ct) { - var tiers = await dbContext + List tiers = await dbContext .MembershipTiers .Where(tier => tier.CreatorId == req.CreatorId) .Select(tier => new TierModel( diff --git a/backend/Modules/Memberships/Handlers/SubscribeToCreator.cs b/backend/Modules/Memberships/Handlers/SubscribeToCreator.cs index c027a75..4f17458 100644 --- a/backend/Modules/Memberships/Handlers/SubscribeToCreator.cs +++ b/backend/Modules/Memberships/Handlers/SubscribeToCreator.cs @@ -44,7 +44,7 @@ public class SubscribeHandler( SubscribeRequest req, CancellationToken ct) { - var tier = await dbContext + MembershipTier? tier = await dbContext .MembershipTiers .Where(tier => tier.Id == req.MembershipTierId) .FirstOrDefaultAsync(ct); @@ -54,7 +54,7 @@ public class SubscribeHandler( return; } - var creator = await creatorLookup.GetCreatorAsync(tier.CreatorId, ct); + CreatorReference? creator = await creatorLookup.GetCreatorAsync(tier.CreatorId, ct); if (creator == null) { await SendNotFoundAsync(ct); @@ -68,7 +68,7 @@ public class SubscribeHandler( } // Process Stripe subscription - var checkoutSession = await membershipPaymentProcessor.CreateCheckoutSessionAsync( + MembershipCheckoutSession checkoutSession = await membershipPaymentProcessor.CreateCheckoutSessionAsync( User.GetUserId(), creator, tier.Id, @@ -78,6 +78,6 @@ public class SubscribeHandler( await SendOkAsync( new SubscriptionResponse { StripeCheckoutUrl = checkoutSession.Url }, - cancellation: ct); + ct); } } diff --git a/backend/Modules/Memberships/Services/MembershipNotifier.cs b/backend/Modules/Memberships/Services/MembershipNotifier.cs index 6914554..76184f7 100644 --- a/backend/Modules/Memberships/Services/MembershipNotifier.cs +++ b/backend/Modules/Memberships/Services/MembershipNotifier.cs @@ -15,7 +15,7 @@ public class MembershipNotifier( string tierId, CancellationToken cancellationToken = default) { - var membership = new Membership + Membership membership = new() { Id = Guid.CreateVersion7(), CreatedAt = DateTimeOffset.UtcNow, @@ -40,17 +40,17 @@ public class MembershipNotifier( string currency, CancellationToken cancellationToken = default) { - var membership = await dbContext + Membership? membership = await dbContext .Memberships .SingleOrDefaultAsync( m => m.StripeSubscriptionId == stripeSubscriptionId, - cancellationToken: cancellationToken); + cancellationToken); if (membership is null) { return; } - var payment = new Payment + Payment payment = new() { Id = Guid.CreateVersion7(), CreatedAt = DateTimeOffset.UtcNow, @@ -73,11 +73,11 @@ public class MembershipNotifier( DateTimeOffset? endDate, CancellationToken cancellationToken = default) { - var membership = await dbContext + Membership? membership = await dbContext .Memberships .SingleOrDefaultAsync( s => s.StripeSubscriptionId == subscriptionId, - cancellationToken: cancellationToken); + cancellationToken); if (membership == null) { return; @@ -92,11 +92,11 @@ public class MembershipNotifier( string subscriptionId, CancellationToken cancellationToken) { - var membership = await dbContext + Membership? membership = await dbContext .Memberships .SingleOrDefaultAsync( s => s.StripeSubscriptionId == subscriptionId, - cancellationToken: cancellationToken); + cancellationToken); if (membership == null) { return; diff --git a/backend/Modules/Messaging/Data/MessagingDbContext.cs b/backend/Modules/Messaging/Data/MessagingDbContext.cs index 6b576e9..1381b91 100644 --- a/backend/Modules/Messaging/Data/MessagingDbContext.cs +++ b/backend/Modules/Messaging/Data/MessagingDbContext.cs @@ -10,6 +10,8 @@ public class MessagingDbContext( { public const string SchemaName = "Messaging"; + public DbSet Messages { get; set; } + protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.HasDefaultSchema(SchemaName); @@ -21,8 +23,6 @@ public class MessagingDbContext( .HasDefaultValueSql("CURRENT_TIMESTAMP"); } - public DbSet Messages { get; set; } - public async Task> GetMessagesAsync( Guid subjectId, Guid? parentId, @@ -30,7 +30,7 @@ public class MessagingDbContext( int pageSize, CancellationToken ct = default) { - var query = Messages + IQueryable query = Messages .Where(c => c.SubjectId == subjectId) .Where(c => c.ParentId == parentId); @@ -39,7 +39,7 @@ public class MessagingDbContext( var lastMessage = await Messages .Where(c => c.Id == lastId.Value) .Select(c => new { c.CreatedAt, c.Id }) - .FirstOrDefaultAsync(cancellationToken: ct); + .FirstOrDefaultAsync(ct); if (lastMessage != null) { @@ -49,17 +49,17 @@ public class MessagingDbContext( } } - var messages = await query + List messages = await query .OrderByDescending(c => c.CreatedAt) .ThenByDescending(c => c.Id) .Take(pageSize) - .ToListAsync(cancellationToken: ct); + .ToListAsync(ct); - var result = await Task.WhenAll( + MessageDto[] result = await Task.WhenAll( messages.Select(async message => { - var writer = await userLookup.GetUserAsync(message.CreatedBy, ct); + UserReference? writer = await userLookup.GetUserAsync(message.CreatedBy, ct); return new MessageDto( message.Id, message.SubjectId, @@ -80,11 +80,11 @@ public class MessagingDbContext( int pageSize, CancellationToken ct = default) { - var query = Messages + IQueryable query = Messages .Where(c => c.SubjectId == subjectId) .Where(c => c.ParentId == parentId); - var messageCount = await query + int messageCount = await query .Take(pageSize) .CountAsync(ct); diff --git a/backend/Modules/Messaging/DependencyInjection.cs b/backend/Modules/Messaging/DependencyInjection.cs index 1ceb2b4..6c76563 100644 --- a/backend/Modules/Messaging/DependencyInjection.cs +++ b/backend/Modules/Messaging/DependencyInjection.cs @@ -9,7 +9,7 @@ public static class DependencyInjection Action? configureAction = null) { builder.Services.AddDbContext(configureAction); - + return builder; } @@ -17,10 +17,10 @@ public static class DependencyInjection 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); + IServiceScopeFactory scopeFactory = app.ApplicationServices.GetRequiredService(); + using IServiceScope scope = scopeFactory.CreateScope(); + await using MessagingDbContext context = scope.ServiceProvider.GetRequiredService(); + await context.Database.MigrateAsync(cancellationToken); return app; } diff --git a/backend/Modules/Messaging/Handlers/AddMessage.cs b/backend/Modules/Messaging/Handlers/AddMessage.cs index 99a0383..307cb41 100644 --- a/backend/Modules/Messaging/Handlers/AddMessage.cs +++ b/backend/Modules/Messaging/Handlers/AddMessage.cs @@ -40,14 +40,14 @@ public class AddMessage( AddMessageRequest req, CancellationToken ct) { - var message = new Message + Message message = new() { Id = req.Id ?? Guid.CreateVersion7(), SubjectId = req.SubjectId, CreatedBy = User.GetUserId(), Value = req.Message - }; - + }; + await context.Messages.AddAsync(message, ct); await context.SaveChangesAsync(ct); diff --git a/backend/Modules/Messaging/Handlers/AddReply.cs b/backend/Modules/Messaging/Handlers/AddReply.cs index a547b40..f5fb4ee 100644 --- a/backend/Modules/Messaging/Handlers/AddReply.cs +++ b/backend/Modules/Messaging/Handlers/AddReply.cs @@ -20,7 +20,7 @@ internal sealed class AddReplyRequestValidator RuleFor(r => r.ParentId) .NotNull().WithMessage("You must specify a ParentId") .NotEmpty().WithMessage("You must specify a non-empty ParentId"); - + RuleFor(r => r.SubjectId) .NotNull().WithMessage("You must specify a SubjectId") .NotEmpty().WithMessage("You must specify a non-empty SubjectId"); @@ -45,10 +45,10 @@ internal sealed class AddReply( AddReplyRequest req, CancellationToken ct) { - var message = new Message + Message message = new() { Id = Guid.CreateVersion7(), - SubjectId = req.SubjectId, + SubjectId = req.SubjectId, ParentId = req.ParentId, CreatedBy = User.GetUserId(), Value = req.Message diff --git a/backend/Modules/Messaging/Handlers/ChangeMessage.cs b/backend/Modules/Messaging/Handlers/ChangeMessage.cs index f1a8d7e..2edc357 100644 --- a/backend/Modules/Messaging/Handlers/ChangeMessage.cs +++ b/backend/Modules/Messaging/Handlers/ChangeMessage.cs @@ -39,15 +39,15 @@ public class ChangeMessage( ChangeMessageRequest req, CancellationToken ct) { - var message = await context.Messages.FirstOrDefaultAsync(x => x.Id == req.Id, ct); - + Message? message = await context.Messages.FirstOrDefaultAsync(x => x.Id == req.Id, ct); + if (message is null) { await SendNotFoundAsync(ct); return; } - - var userId = HttpContext.User.GetUserId(); + + Guid userId = HttpContext.User.GetUserId(); if (message.CreatedBy != userId) { await SendForbiddenAsync(ct); @@ -58,7 +58,7 @@ public class ChangeMessage( message.Value = req.Message; context.Update(message); - + await context.SaveChangesAsync(ct); } } diff --git a/backend/Modules/Messaging/Handlers/DeleteMessage.cs b/backend/Modules/Messaging/Handlers/DeleteMessage.cs index 5fe55e0..8bbeb9b 100644 --- a/backend/Modules/Messaging/Handlers/DeleteMessage.cs +++ b/backend/Modules/Messaging/Handlers/DeleteMessage.cs @@ -30,21 +30,21 @@ public class DeleteMessage( DeleteMessageRequest req, CancellationToken ct) { - var message = await context.Messages.FirstOrDefaultAsync(x => x.Id == req.MessageId, ct); + Message? message = await context.Messages.FirstOrDefaultAsync(x => x.Id == req.MessageId, ct); if (message is null) { await SendNotFoundAsync(ct); return; } - - var userId = HttpContext.User.GetUserId(); + + Guid userId = HttpContext.User.GetUserId(); if (message.CreatedBy != userId) { await SendForbiddenAsync(ct); return; } - + context.Messages.Remove(message); await context.SaveChangesAsync(ct); diff --git a/backend/Modules/Messaging/Handlers/GetMessageCount.cs b/backend/Modules/Messaging/Handlers/GetMessageCount.cs index b9d1b2e..d13d7d4 100644 --- a/backend/Modules/Messaging/Handlers/GetMessageCount.cs +++ b/backend/Modules/Messaging/Handlers/GetMessageCount.cs @@ -28,17 +28,14 @@ public class GetMessageCount( GetMessageCountRequest req, CancellationToken ct) { - var messageCount = await context.GetMessageCountAsync( + int messageCount = await context.GetMessageCountAsync( req.SubjectId, null, req.PageSize, ct); - + await SendAsync( - new() - { - Count = messageCount - }, + new GetMessageCountResponse { Count = messageCount }, cancellation: ct); } } diff --git a/backend/Modules/Messaging/Handlers/GetMessages.cs b/backend/Modules/Messaging/Handlers/GetMessages.cs index b9d6065..63a7ae4 100644 --- a/backend/Modules/Messaging/Handlers/GetMessages.cs +++ b/backend/Modules/Messaging/Handlers/GetMessages.cs @@ -30,13 +30,13 @@ public class GetMessages( GetMessagesRequest req, CancellationToken ct) { - var messages = await context.GetMessagesAsync( + IEnumerable messages = await context.GetMessagesAsync( req.SubjectId, null, req.LastId, req.PageSize, ct); - + await SendOkAsync(new GetMessagesResponse(messages), ct); } } diff --git a/backend/Modules/Messaging/Handlers/GetMessagesByUser.cs b/backend/Modules/Messaging/Handlers/GetMessagesByUser.cs index dfb4764..9d9db35 100644 --- a/backend/Modules/Messaging/Handlers/GetMessagesByUser.cs +++ b/backend/Modules/Messaging/Handlers/GetMessagesByUser.cs @@ -30,16 +30,16 @@ public class GetMessagesByUser( GetMessagesByUserRequest req, CancellationToken ct) { - var messages = await context + List messages = await context .Messages .Where(c => c.CreatedBy == req.UserId) .Where(c => c.ParentId == null) - .ToListAsync(cancellationToken: ct); + .ToListAsync(ct); - var result = await Task.WhenAll( + MessageDto[] result = await Task.WhenAll( messages.Select(async message => { - var user = await userLookup.GetUserAsync(message.CreatedBy, ct); + UserReference? user = await userLookup.GetUserAsync(message.CreatedBy, ct); return new MessageDto { diff --git a/backend/Modules/Messaging/Handlers/GetReplies.cs b/backend/Modules/Messaging/Handlers/GetReplies.cs index a7d7fc2..52546db 100644 --- a/backend/Modules/Messaging/Handlers/GetReplies.cs +++ b/backend/Modules/Messaging/Handlers/GetReplies.cs @@ -31,7 +31,7 @@ public class GetReplies( GetRepliesRequest req, CancellationToken ct) { - var replies = await context.GetMessagesAsync( + IEnumerable replies = await context.GetMessagesAsync( req.SubjectId, req.ParentId, req.LastId, diff --git a/backend/Modules/Tipping/Data/Tip.cs b/backend/Modules/Tipping/Data/Tip.cs index a9cd709..432a143 100644 --- a/backend/Modules/Tipping/Data/Tip.cs +++ b/backend/Modules/Tipping/Data/Tip.cs @@ -17,5 +17,5 @@ public class Tip : Entity public enum TipStatus : short { Pending = 0, - Paid = 1, + Paid = 1 } diff --git a/backend/Modules/Tipping/Data/TippingDbContext.cs b/backend/Modules/Tipping/Data/TippingDbContext.cs index 997f8c1..7501b28 100644 --- a/backend/Modules/Tipping/Data/TippingDbContext.cs +++ b/backend/Modules/Tipping/Data/TippingDbContext.cs @@ -7,8 +7,8 @@ public sealed class TippingDbContext( public const string SchemaName = "Tipping"; public DbSet Tips => Set(); - - + + protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.HasDefaultSchema(SchemaName); diff --git a/backend/Modules/Tipping/DependencyInjection.cs b/backend/Modules/Tipping/DependencyInjection.cs index 674378b..1b26545 100644 --- a/backend/Modules/Tipping/DependencyInjection.cs +++ b/backend/Modules/Tipping/DependencyInjection.cs @@ -13,7 +13,7 @@ public static class DependencyInjection { builder.Services.AddDbContext(configureAction); builder.Services.AddTransient(); - + return builder; } @@ -21,10 +21,10 @@ public static class DependencyInjection 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); + IServiceScopeFactory scopeFactory = app.ApplicationServices.GetRequiredService(); + using IServiceScope scope = scopeFactory.CreateScope(); + await using MessagingDbContext context = scope.ServiceProvider.GetRequiredService(); + await context.Database.MigrateAsync(cancellationToken); return app; } diff --git a/backend/Modules/Tipping/Handlers/GetReceivedTips.cs b/backend/Modules/Tipping/Handlers/GetReceivedTips.cs index 89fceba..e43bb17 100644 --- a/backend/Modules/Tipping/Handlers/GetReceivedTips.cs +++ b/backend/Modules/Tipping/Handlers/GetReceivedTips.cs @@ -24,16 +24,16 @@ public class GetReceivedTipsHandler( public override async Task HandleAsync( CancellationToken ct) { - var tips = await dbContext + List tips = await dbContext .Tips .Where(tip => tip.CreatorId == User.GetUserId()) .ToListAsync(ct); - var result = await Task.WhenAll( + TipReceivedModel[] result = await Task.WhenAll( tips.Select(async tip => { - var tipper = await userLookup.GetUserAsync(tip.CreatorId, ct); - + UserReference? tipper = await userLookup.GetUserAsync(tip.CreatorId, ct); + return new TipReceivedModel( tip.Id, tip.CreatedAt, diff --git a/backend/Program.cs b/backend/Program.cs index 6e8344e..d912e93 100644 --- a/backend/Program.cs +++ b/backend/Program.cs @@ -18,14 +18,17 @@ using NSwag; using NSwag.Generation.AspNetCore.Processors; using NSwag.Generation.Processors.Security; -var builder = WebApplication.CreateBuilder(args); +WebApplicationBuilder builder = WebApplication.CreateBuilder(args); if (!builder.Environment.IsDevelopment()) { - var vaultUri = Environment.GetEnvironmentVariable("VaultUri"); - - if (vaultUri is null) throw new InvalidOperationException("Missing VaultUri configuration setting"); - + string? vaultUri = Environment.GetEnvironmentVariable("VaultUri"); + + if (vaultUri is null) + { + throw new InvalidOperationException("Missing VaultUri configuration setting"); + } + builder.Configuration.AddAzureKeyVault(new Uri(vaultUri), new DefaultAzureCredential()); } @@ -60,15 +63,16 @@ builder.Services.AddOpenApiDocument(( Type = OpenApiSecuritySchemeType.ApiKey, Name = "Authorization", In = OpenApiSecurityApiKeyLocation.Header, - Description = "Type into the textbox: Bearer {your JWT token}.", + Description = "Type into the textbox: Bearer {your JWT token}." }); configure.OperationProcessors.Add(new AspNetCoreOperationTagsProcessor()); configure.OperationProcessors.Add(new AspNetCoreOperationSecurityScopeProcessor("JWT")); }); -var postgresConnectionString = builder.Configuration.GetConnectionString("PostgresConnection") - ?? throw new InvalidOperationException("Missing ConnectionStrings:PostgresConnection"); +string postgresConnectionString = builder.Configuration.GetConnectionString("PostgresConnection") + ?? throw new InvalidOperationException( + "Missing ConnectionStrings:PostgresConnection"); builder.Services.AddFastEndpoints(); builder.AddInfrastructureModule(); @@ -84,10 +88,9 @@ builder.AddContentModule(options => options.UseNpgsql( postgresConnectionString, o => o.MigrationsHistoryTable("__EFMigrationsHistory", ContentsDbContext.SchemaName))); -builder.AddMembershipModule( - options => options.UseNpgsql( - postgresConnectionString, - o => o.MigrationsHistoryTable("__EFMigrationsHistory", MembershipsDbContext.SchemaName))); +builder.AddMembershipModule(options => options.UseNpgsql( + postgresConnectionString, + o => o.MigrationsHistoryTable("__EFMigrationsHistory", MembershipsDbContext.SchemaName))); builder.AddTippingModule(options => options.UseNpgsql( postgresConnectionString, @@ -97,7 +100,7 @@ builder.AddMessagingModule(options => postgresConnectionString, o => o.MigrationsHistoryTable("__EFMigrationsHistory", MessagingDbContext.SchemaName))); -var app = builder.Build(); +WebApplication app = builder.Build(); app.UseForwardedHeaders( new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.XForwardedProto } diff --git a/backend/analyzers.ruleset b/backend/analyzers.ruleset index b7f5693..c918410 100644 --- a/backend/analyzers.ruleset +++ b/backend/analyzers.ruleset @@ -1,7 +1,8 @@ - + + diff --git a/backend/appsettings.Development.json b/backend/appsettings.Development.json index 33a0982..d44cffb 100644 --- a/backend/appsettings.Development.json +++ b/backend/appsettings.Development.json @@ -1,6 +1,6 @@ { "ConnectionStrings": { - "PostgresConnection": "Server=localhost,5432;Database=Hutopy;Uid=sa;Pwd=P@ssword123!;" + "PostgresConnection": "Server=localhost,5432;Database=Hutopy;Uid=sa;Pwd=P@ssword123!;" }, "Stripe": { "SecretKey": "sk_test_51OoveVDrRyqXtNdBaOs1DFFja0XhrQtJoAo83uSySMuqw4Wyt9NsuugrIHRqet9a50cr5GvolpTP8EZuTSttcgYx00gOUPNDoI",