chore(codebase): full cleanup pass

This commit is contained in:
2025-06-21 01:58:48 -04:00
parent 8323477cd0
commit 81b5db34ef
92 changed files with 529 additions and 452 deletions

View File

@@ -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";

View File

@@ -3,7 +3,7 @@ namespace Hutopy.Infrastructure.BlobStorage.Contracts;
public interface IBlobStorage
{
/// <summary>
/// Upload a file to blob storage.
/// Upload a file to blob storage.
/// </summary>
/// <param name="containerName">The name of the container where the file is stored.</param>
/// <param name="blobName">The blob name (path within the container, include the file name).</param>
@@ -12,14 +12,14 @@ public interface IBlobStorage
/// <param name="ct">The cancellation token</param>
/// <returns></returns>
Task<string> UploadFileAsync(
string containerName,
string blobName,
string containerName,
string blobName,
Stream stream,
string contentType,
CancellationToken ct = default);
/// <summary>
/// Download a file to blob storage.
/// Download a file to blob storage.
/// </summary>
/// <param name="blobName">The blob name (path within the container).</param>
/// <param name="containerName">The name of the container where the file is stored. (users)</param>

View File

@@ -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<AzureBlobStorage> _logger;
public AzureBlobStorage(IConfiguration configuration, ILogger<AzureBlobStorage> logger)
{
_logger = logger;
var connectionString = configuration.GetConnectionString("AzureBlob");
string? connectionString = configuration.GetConnectionString("AzureBlob");
_blobServiceClient = new BlobServiceClient(connectionString);
}
/// <summary>
/// Upload a file to microsoft azure blob storage.
/// Upload a file to microsoft azure blob storage.
/// </summary>
/// <param name="containerName">The name of the container where the file is stored.</param>
/// <param name="blobName">The blob name (path within the container, include the file name).</param>
@@ -29,8 +29,8 @@ public class AzureBlobStorage : IBlobStorage
/// <param name="ct">The cancellation token</param>
/// <returns></returns>
public async Task<string> 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<BlobContentInfo>? 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
}
/// <summary>
/// Download a file to microsoft's azure blob storage.
/// Download a file to microsoft's azure blob storage.
/// </summary>
/// <param name="blobName">The blob name (path within the container).</param>
/// <param name="containerName">The name of the container where the file is stored. (users)</param>
@@ -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

View File

@@ -3,6 +3,6 @@ namespace Hutopy.Infrastructure.Configuration;
public class WebsiteOptions
{
public const string SectionName = "Website";
public string FrontendBaseUrl { get; set; } = "https://localhost:5173";
}

View File

@@ -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; }

View File

@@ -9,7 +9,7 @@ namespace Hutopy.Infrastructure.Payments.Stripe.Services;
public class MembershipPaymentProcessor(
IOptions<StripeOptions> stripeOptions)
: IMembershipPaymentProcessor
: IMembershipPaymentProcessor
{
public async Task<MembershipCheckoutSession> 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<string, string> { { "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);
}
}

View File

@@ -6,7 +6,7 @@ using Stripe;
namespace Hutopy.Infrastructure.Payments.Stripe.Services;
public sealed class MembershipTierProcessor(
IOptions<StripeOptions> stripeOptions)
IOptions<StripeOptions> stripeOptions)
: IMembershipTierProcessor
{
public async Task<string> 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
{

View File

@@ -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<string, string>
{
{ "creatorId", creator.Id.ToString() }, { "creatorName", creator.Name }, { "message", message },
{ "creatorId", creator.Id.ToString() }, { "creatorName", creator.Name }, { "message", message }
}
},
cancellationToken: ct);

View File

@@ -8,22 +8,22 @@ public static class ClaimsPrincipalExtensions
{
return (Guid)claims.GetRequiredClaim<Guid>(ClaimTypes.NameIdentifier);
}
public static string GetName(this ClaimsPrincipal claims)
{
return (string)claims.GetRequiredClaim<string>(ClaimTypes.Name);
}
public static string? GetAlias(this ClaimsPrincipal claims)
{
return (string?)claims.GetClaim<string?>(KnownClaims.Alias);
}
}
public static string? GetPortraitUrl(this ClaimsPrincipal claims)
{
return (string?)claims.GetClaim<string?>(KnownClaims.PortraitUrl);
}
}
public static string GetFirstName(this ClaimsPrincipal claims)
{
return (string)claims.GetRequiredClaim<string>(ClaimTypes.GivenName);
@@ -41,17 +41,22 @@ public static class ClaimsPrincipalExtensions
private static object? GetClaim<TValue>(this ClaimsPrincipal claims, string key)
{
var claim = claims.FindFirst(key);
Claim? claim = claims.FindFirst(key);
return claim is null ? null : claims.GetRequiredClaim<TValue>(key);
}
private static object GetRequiredClaim<TValue>(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));
}
}

View File

@@ -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<Claim>([
List<Claim> 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);

View File

@@ -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++)
{

View File

@@ -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);
}

View File

@@ -13,52 +13,62 @@ public static class YouTubeUrlHelper
RegexOptions.Compiled);
/// <summary>
/// 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.
/// </summary>
/// <param name="input">The YouTube URL or video ID</param>
/// <returns>The extracted video ID or null if invalid</returns>
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;
}
/// <summary>
/// Validates if the input is a valid YouTube video ID.
/// Validates if the input is a valid YouTube video ID.
/// </summary>
/// <param name="input">The video ID to validate</param>
/// <returns>True if the input is a valid video ID</returns>
public static bool IsValidVideoId(string? input)
{
if (string.IsNullOrWhiteSpace(input))
{
return false;
}
return ShortUrlRegex.IsMatch(input);
}
/// <summary>
/// Validates if the input is a valid YouTube URL or video ID.
/// Validates if the input is a valid YouTube URL or video ID.
/// </summary>
/// <param name="input">The URL or video ID to validate</param>
/// <returns>True if the input is a valid YouTube URL or video ID</returns>
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);
}
}
}