diff --git a/Directory.Packages.props b/Directory.Packages.props
index 438dfe2..ada3c11 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -46,7 +46,7 @@
-
+
\ No newline at end of file
diff --git a/Hutopy.sln b/Hutopy.sln
index cf13057..451a7bc 100644
--- a/Hutopy.sln
+++ b/Hutopy.sln
@@ -13,8 +13,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{6ED356A7-8B4
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{664D406C-2F83-48F0-BFC3-408D5CB53C65}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Application.UnitTests", "tests\Application.UnitTests\Application.UnitTests.csproj", "{DEFF4009-1FAB-4392-80B6-707E2DC5C00B}"
-EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Domain.UnitTests", "tests\Domain.UnitTests\Domain.UnitTests.csproj", "{DC37FD87-552C-4613-9F16-1537CA522898}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{E2DA20AA-28D1-455C-BF50-C49A8F831633}"
@@ -54,10 +52,6 @@ Global
{117DA02F-5274-4565-ACC6-DA9B6E568B09}.Debug|Any CPU.Build.0 = Debug|Any CPU
{117DA02F-5274-4565-ACC6-DA9B6E568B09}.Release|Any CPU.ActiveCfg = Release|Any CPU
{117DA02F-5274-4565-ACC6-DA9B6E568B09}.Release|Any CPU.Build.0 = Release|Any CPU
- {DEFF4009-1FAB-4392-80B6-707E2DC5C00B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {DEFF4009-1FAB-4392-80B6-707E2DC5C00B}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {DEFF4009-1FAB-4392-80B6-707E2DC5C00B}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {DEFF4009-1FAB-4392-80B6-707E2DC5C00B}.Release|Any CPU.Build.0 = Release|Any CPU
{DC37FD87-552C-4613-9F16-1537CA522898}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DC37FD87-552C-4613-9F16-1537CA522898}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DC37FD87-552C-4613-9F16-1537CA522898}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -82,7 +76,6 @@ Global
{C7E89A3E-A631-4760-8D61-BD1EAB1C4E69} = {6ED356A7-8B47-4613-AD01-C85CF28491BD}
{34C0FACD-F3D9-400C-8945-554DD6B0819A} = {6ED356A7-8B47-4613-AD01-C85CF28491BD}
{117DA02F-5274-4565-ACC6-DA9B6E568B09} = {6ED356A7-8B47-4613-AD01-C85CF28491BD}
- {DEFF4009-1FAB-4392-80B6-707E2DC5C00B} = {664D406C-2F83-48F0-BFC3-408D5CB53C65}
{DC37FD87-552C-4613-9F16-1537CA522898} = {664D406C-2F83-48F0-BFC3-408D5CB53C65}
{4E4EE20C-F06A-4A1B-851F-C5577796941C} = {6ED356A7-8B47-4613-AD01-C85CF28491BD}
{EA6127A5-94C9-4C31-AD11-E6811B92B520} = {664D406C-2F83-48F0-BFC3-408D5CB53C65}
diff --git a/global.json b/global.json
index 05cf0d8..b5b37b6 100644
--- a/global.json
+++ b/global.json
@@ -1,7 +1,7 @@
{
"sdk": {
- "version": "8.0.203",
- "rollForward": "latestFeature",
- "allowPrerelease": true
+ "version": "8.0.0",
+ "rollForward": "latestMajor",
+ "allowPrerelease": false
}
}
\ No newline at end of file
diff --git a/src/Application/Common/Behaviours/AuthorizationBehaviour.cs b/src/Application/Common/Behaviours/AuthorizationBehaviour.cs
deleted file mode 100644
index 8012cce..0000000
--- a/src/Application/Common/Behaviours/AuthorizationBehaviour.cs
+++ /dev/null
@@ -1,84 +0,0 @@
-using System.Reflection;
-using Hutopy.Application.Common.Exceptions;
-using Hutopy.Application.Common.Interfaces;
-using Hutopy.Application.Common.Security;
-
-namespace Hutopy.Application.Common.Behaviours;
-
-public class AuthorizationBehaviour(
- IUser user,
- IIdentityService identityService)
- : IPipelineBehavior
- where TRequest : notnull
-{
- public async Task Handle(TRequest request, RequestHandlerDelegate next,
- CancellationToken cancellationToken)
- {
- var authorizeAttributes = request
- .GetType()
- .GetCustomAttributes()
- .ToArray();
-
- if (authorizeAttributes.Length == 0)
- {
- return await next();
- }
-
- if (user.Id is null)
- {
- throw new UnauthorizedAccessException();
- }
-
- // Role-based authorization
- var authorizeAttributesWithRoles = authorizeAttributes
- .Where(a => !string.IsNullOrWhiteSpace(a.Roles))
- .ToArray();
-
- if (authorizeAttributesWithRoles.Length != 0)
- {
- var authorized = false;
-
- foreach (var roles in authorizeAttributesWithRoles.Select(a => a.Roles.Split(',')))
- {
- foreach (var role in roles)
- {
- var isInRole = await identityService.IsInRoleAsync(user.Id.Value, role.Trim());
- if (isInRole)
- {
- authorized = true;
- break;
- }
- }
- }
-
- // Must be a member of at least one role in roles
- if (!authorized)
- {
- throw new ForbiddenAccessException();
- }
- }
-
- // Policy-based authorization
- var authorizeAttributesWithPolicies = authorizeAttributes
- .Where(a => !string.IsNullOrWhiteSpace(a.Policy))
- .ToArray();
-
- if (authorizeAttributesWithPolicies.Length == 0)
- {
- return await next();
- }
-
- foreach (var policy in authorizeAttributesWithPolicies.Select(a => a.Policy))
- {
- var authorized = await identityService.AuthorizeAsync(user.Id.Value, policy);
-
- if (!authorized)
- {
- throw new ForbiddenAccessException();
- }
- }
-
- // User is authorized / authorization not required
- return await next();
- }
-}
diff --git a/src/Application/Common/Behaviours/LoggingBehaviour.cs b/src/Application/Common/Behaviours/LoggingBehaviour.cs
deleted file mode 100644
index def9dca..0000000
--- a/src/Application/Common/Behaviours/LoggingBehaviour.cs
+++ /dev/null
@@ -1,30 +0,0 @@
-using Hutopy.Application.Common.Interfaces;
-using MediatR.Pipeline;
-using Microsoft.Extensions.Logging;
-
-namespace Hutopy.Application.Common.Behaviours;
-
-public class LoggingBehaviour(
- ILogger logger,
- IUser user,
- IIdentityService identityService)
- : IRequestPreProcessor
- where TRequest : notnull
-{
- private readonly ILogger _logger = logger;
-
- public async Task Process(TRequest request, CancellationToken cancellationToken)
- {
- var requestName = typeof(TRequest).Name;
- string? userName = string.Empty;
-
- if (user.Id.HasValue)
- {
- userName = await identityService.GetUserNameAsync(user.Id.Value);
- }
-
- _logger.LogInformation(
- "Hutopy Request: {Name} {@UserId} {@UserName} {@Request}",
- requestName, user.Id ?? Guid.Empty, userName, request);
- }
-}
diff --git a/src/Application/Common/Behaviours/PerformanceBehaviour.cs b/src/Application/Common/Behaviours/PerformanceBehaviour.cs
deleted file mode 100644
index 20a82a2..0000000
--- a/src/Application/Common/Behaviours/PerformanceBehaviour.cs
+++ /dev/null
@@ -1,38 +0,0 @@
-using System.Diagnostics;
-using Hutopy.Application.Common.Interfaces;
-using Microsoft.Extensions.Logging;
-
-namespace Hutopy.Application.Common.Behaviours;
-
-public class PerformanceBehaviour(
- ILogger logger,
- IUser user,
- IIdentityService identityService)
- : IPipelineBehavior
- where TRequest : notnull
-{
- private readonly Stopwatch _timer = new();
-
- public async Task Handle(TRequest request, RequestHandlerDelegate next, CancellationToken cancellationToken)
- {
- _timer.Start();
-
- var response = await next();
-
- _timer.Stop();
-
- var elapsedMilliseconds = _timer.ElapsedMilliseconds;
-
- if (elapsedMilliseconds <= 500) return response;
-
- var requestName = typeof(TRequest).Name;
- var userName = string.Empty;
-
- if (user.Id.HasValue) userName = await identityService.GetUserNameAsync(user.Id.Value);
-
- logger.LogWarning("Hutopy Long Running Request: {Name} ({ElapsedMilliseconds} milliseconds) {@UserId} {@UserName} {@Request}",
- requestName, elapsedMilliseconds, user.Id ?? Guid.Empty, userName, request);
-
- return response;
- }
-}
diff --git a/src/Application/Common/Behaviours/UnhandledExceptionBehaviour.cs b/src/Application/Common/Behaviours/UnhandledExceptionBehaviour.cs
deleted file mode 100644
index 28d2a94..0000000
--- a/src/Application/Common/Behaviours/UnhandledExceptionBehaviour.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-using Microsoft.Extensions.Logging;
-
-namespace Hutopy.Application.Common.Behaviours;
-
-public class UnhandledExceptionBehaviour(
- ILogger logger)
- : IPipelineBehavior
- where TRequest : notnull
-{
- public async Task Handle(TRequest request, RequestHandlerDelegate next, CancellationToken cancellationToken)
- {
- try
- {
- return await next();
- }
- catch (Exception ex)
- {
- var requestName = typeof(TRequest).Name;
-
- logger.LogError(ex, "Hutopy Request: Unhandled Exception for Request {Name} {@Request}", requestName, request);
-
- throw;
- }
- }
-}
diff --git a/src/Application/Common/Behaviours/ValidationBehaviour.cs b/src/Application/Common/Behaviours/ValidationBehaviour.cs
deleted file mode 100644
index a564099..0000000
--- a/src/Application/Common/Behaviours/ValidationBehaviour.cs
+++ /dev/null
@@ -1,28 +0,0 @@
-using ValidationException = Hutopy.Application.Common.Exceptions.ValidationException;
-
-namespace Hutopy.Application.Common.Behaviours;
-
-public class ValidationBehaviour(IEnumerable> validators)
- : IPipelineBehavior
- where TRequest : notnull
-{
- public async Task Handle(TRequest request, RequestHandlerDelegate next, CancellationToken cancellationToken)
- {
- if (!validators.Any()) return await next();
-
- var context = new ValidationContext(request);
-
- var validationResults = await Task.WhenAll(
- validators.Select(v =>
- v.ValidateAsync(context, cancellationToken)));
-
- var failures = validationResults
- .Where(r => r.Errors.Any())
- .SelectMany(r => r.Errors)
- .ToList();
-
- if (failures.Count != 0) throw new ValidationException(failures);
-
- return await next();
- }
-}
diff --git a/src/Application/Common/Exceptions/ForbiddenAccessException.cs b/src/Application/Common/Exceptions/ForbiddenAccessException.cs
deleted file mode 100644
index 41f91eb..0000000
--- a/src/Application/Common/Exceptions/ForbiddenAccessException.cs
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace Hutopy.Application.Common.Exceptions;
-
-public class ForbiddenAccessException : Exception
-{
- public ForbiddenAccessException() : base() { }
-}
diff --git a/src/Application/Common/Exceptions/ValidationException.cs b/src/Application/Common/Exceptions/ValidationException.cs
deleted file mode 100644
index f167275..0000000
--- a/src/Application/Common/Exceptions/ValidationException.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-using FluentValidation.Results;
-
-namespace Hutopy.Application.Common.Exceptions;
-
-public class ValidationException()
- : Exception("One or more validation failures have occurred.")
-{
- public ValidationException(IEnumerable failures)
- : this()
- {
- Errors = failures
- .GroupBy(e => e.PropertyName, e => e.ErrorMessage)
- .ToDictionary(failureGroup => failureGroup.Key, failureGroup => failureGroup.ToArray());
- }
-
- public IDictionary Errors { get; } = new Dictionary();
-}
diff --git a/src/Application/Common/Interfaces/IApplicationDbContext.cs b/src/Application/Common/Interfaces/IApplicationDbContext.cs
deleted file mode 100644
index 75eabb6..0000000
--- a/src/Application/Common/Interfaces/IApplicationDbContext.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-using Hutopy.Domain.Entities;
-
-namespace Hutopy.Application.Common.Interfaces;
-
-public interface IApplicationDbContext
-{
- DbSet FutureCreators { get; }
- DbSet UserTransactions { get; }
- Task SaveChangesAsync(CancellationToken cancellationToken);
-}
diff --git a/src/Application/Common/Interfaces/IBlobStorage.cs b/src/Application/Common/Interfaces/IBlobStorage.cs
deleted file mode 100644
index 7dac254..0000000
--- a/src/Application/Common/Interfaces/IBlobStorage.cs
+++ /dev/null
@@ -1,8 +0,0 @@
-namespace Hutopy.Application.Common.Interfaces;
-
-public interface IBlobStorage
-{
- Task UploadFileAsync(string containerName, string blobName, Stream stream, string contentType,
- CancellationToken ct = default);
- Task DownloadFileAsync(string containerName, string blobName, CancellationToken ct = default);
-}
diff --git a/src/Application/Common/Interfaces/IIdentityService.cs b/src/Application/Common/Interfaces/IIdentityService.cs
deleted file mode 100644
index 3ebbfb3..0000000
--- a/src/Application/Common/Interfaces/IIdentityService.cs
+++ /dev/null
@@ -1,28 +0,0 @@
-using Hutopy.Application.Common.Models;
-
-namespace Hutopy.Application.Common.Interfaces;
-
-public interface IIdentityService
-{
- Task> CreateUserAsync(
- string email,
- string userName,
- string firstName,
- string lastName,
- string password);
-
- Task GetCurrentUserAsync();
- Task UpdateCurrentUserPortraitUrlAsync(string url);
- Task> UpdateCurrentUserAsync(UserModel userModel);
- Task> GetCurrentUserRolesAsync();
- Task FindUserByIdAsync(string id);
- Task FindUserByEmailAsync(string email);
- Task GetUserByUserNameAsync(string userName);
- Task LoginAsync(string email, string password);
- Task IsInRoleAsync(Guid userId, string role);
- Task AuthorizeAsync(Guid userId, string policyName);
- Task GetUserNameAsync(Guid userId);
-
- Task AddRoleAsync(string userId, string role);
- Task DeleteUserAsync(string userId);
-}
diff --git a/src/Application/Common/Interfaces/IRoleService.cs b/src/Application/Common/Interfaces/IRoleService.cs
deleted file mode 100644
index 96ed4d2..0000000
--- a/src/Application/Common/Interfaces/IRoleService.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-using Hutopy.Application.Common.Models;
-
-namespace Hutopy.Application.Common.Interfaces;
-
-public interface IRoleService
-{
- public Task CreateRoleAsync(string roleName);
- public Task DeleteRoleAsync(string roleName);
- public Task FindRoleByIdAsync(string roleId);
-}
diff --git a/src/Application/Common/Interfaces/IStripeService.cs b/src/Application/Common/Interfaces/IStripeService.cs
deleted file mode 100644
index 6737a3d..0000000
--- a/src/Application/Common/Interfaces/IStripeService.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-using Hutopy.Application.Common.Models;
-using Hutopy.Application.Stripe.Commands;
-
-namespace Hutopy.Application.Common.Interfaces;
-
-
-public interface IStripeService
-{
- public Task CreateCheckoutSession(int amount, string creatorId, string currency);
- public Result ValidateTransaction(ConfirmStripeTransactionCommand request);
-}
diff --git a/src/Application/Common/Interfaces/IUser.cs b/src/Application/Common/Interfaces/IUser.cs
deleted file mode 100644
index ede555b..0000000
--- a/src/Application/Common/Interfaces/IUser.cs
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace Hutopy.Application.Common.Interfaces;
-
-public interface IUser
-{
- Guid? Id { get; }
-}
diff --git a/src/Application/Common/Mappings/MappingExtensions.cs b/src/Application/Common/Mappings/MappingExtensions.cs
deleted file mode 100644
index bdbb707..0000000
--- a/src/Application/Common/Mappings/MappingExtensions.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-using Hutopy.Application.Common.Models;
-
-namespace Hutopy.Application.Common.Mappings;
-
-public static class MappingExtensions
-{
- public static Task> PaginatedListAsync(this IQueryable queryable, int pageNumber, int pageSize) where TDestination : class
- => PaginatedList.CreateAsync(queryable.AsNoTracking(), pageNumber, pageSize);
-
- public static Task> ProjectToListAsync(this IQueryable queryable, IConfigurationProvider configuration) where TDestination : class
- => queryable.ProjectTo(configuration).AsNoTracking().ToListAsync();
-}
diff --git a/src/Application/Common/Models/PaginatedList.cs b/src/Application/Common/Models/PaginatedList.cs
deleted file mode 100644
index e0a4e59..0000000
--- a/src/Application/Common/Models/PaginatedList.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-namespace Hutopy.Application.Common.Models;
-
-public class PaginatedList(
- IReadOnlyCollection items,
- int count,
- int pageNumber,
- int pageSize)
-{
- public IReadOnlyCollection Items { get; } = items;
- public int PageNumber { get; } = pageNumber;
- public int TotalPages { get; } = (int)Math.Ceiling(count / (double)pageSize);
- public int TotalCount { get; } = count;
-
- public bool HasPreviousPage => PageNumber > 1;
-
- public bool HasNextPage => PageNumber < TotalPages;
-
- public static async Task> CreateAsync(IQueryable source, int pageNumber, int pageSize)
- {
- var count = await source.CountAsync();
- var items = await source.Skip((pageNumber - 1) * pageSize).Take(pageSize).ToListAsync();
-
- return new PaginatedList(items, count, pageNumber, pageSize);
- }
-}
diff --git a/src/Application/Common/Security/AuthorizeAttribute.cs b/src/Application/Common/Security/AuthorizeAttribute.cs
deleted file mode 100644
index ad2e1e1..0000000
--- a/src/Application/Common/Security/AuthorizeAttribute.cs
+++ /dev/null
@@ -1,23 +0,0 @@
-namespace Hutopy.Application.Common.Security;
-
-///
-/// Specifies the class this attribute is applied to requires authorization.
-///
-[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
-public class AuthorizeAttribute : Attribute
-{
- ///
- /// Initializes a new instance of the class.
- ///
- public AuthorizeAttribute() { }
-
- ///
- /// Gets or sets a comma delimited list of roles that are allowed to access the resource.
- ///
- public string Roles { get; set; } = string.Empty;
-
- ///
- /// Gets or sets the policy name that determines access to the resource.
- ///
- public string Policy { get; set; } = string.Empty;
-}
diff --git a/src/Application/DependencyInjection.cs b/src/Application/DependencyInjection.cs
deleted file mode 100644
index 41c71da..0000000
--- a/src/Application/DependencyInjection.cs
+++ /dev/null
@@ -1,26 +0,0 @@
-using System.Reflection;
-using Hutopy.Application.Common.Behaviours;
-using Microsoft.Extensions.DependencyInjection;
-
-namespace Hutopy.Application;
-
-public static class DependencyInjection
-{
- public static IServiceCollection AddApplicationServices(this IServiceCollection services)
- {
- services.AddAutoMapper(Assembly.GetExecutingAssembly());
-
- services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly());
-
- services.AddMediatR(cfg =>
- {
- cfg.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly());
- //cfg.AddBehavior(typeof(IPipelineBehavior<,>), typeof(UnhandledExceptionBehaviour<,>));
- cfg.AddBehavior(typeof(IPipelineBehavior<,>), typeof(AuthorizationBehaviour<,>));
- //cfg.AddBehavior(typeof(IPipelineBehavior<,>), typeof(ValidationBehaviour<,>));
- //cfg.AddBehavior(typeof(IPipelineBehavior<,>), typeof(PerformanceBehaviour<,>));
- });
-
- return services;
- }
-}
diff --git a/src/Application/FutureCreators/Commands/CreateFutureCreator.cs b/src/Application/FutureCreators/Commands/CreateFutureCreator.cs
deleted file mode 100644
index 7aa7f50..0000000
--- a/src/Application/FutureCreators/Commands/CreateFutureCreator.cs
+++ /dev/null
@@ -1,38 +0,0 @@
-using Hutopy.Application.Common.Interfaces;
-using Hutopy.Domain.Entities;
-
-namespace Hutopy.Application.FutureCreators.Commands;
-
-public record CreateFutureCreatorCommand : IRequest
-{
- public required string FirstName { get; init; }
- public required string LastName { get; init; }
- public required string EmailAddress { get; init; }
- public required string PhoneNumber { get; init; }
- public required string SocialNetworkAccount { get; init; }
- public required string ReasonToJoin { get; init; }
-}
-
-public class CreateFutureCreatorCommandHandler(
- IApplicationDbContext context)
- : IRequestHandler
-{
- public async Task Handle(CreateFutureCreatorCommand request, CancellationToken cancellationToken)
- {
- var entity = new FutureCreator
- {
- FirstName = request.FirstName,
- LastName = request.LastName,
- EmailAddress = request.EmailAddress,
- PhoneNumber = request.PhoneNumber,
- SocialNetworkAccount = request.SocialNetworkAccount,
- ReasonToJoin = request.ReasonToJoin,
- };
-
- context.FutureCreators.Add(entity);
-
- await context.SaveChangesAsync(cancellationToken);
-
- return entity.Id;
- }
-}
diff --git a/src/Application/FutureCreators/Queries/FutureCreatorListDto.cs b/src/Application/FutureCreators/Queries/FutureCreatorListDto.cs
deleted file mode 100644
index 13d95ba..0000000
--- a/src/Application/FutureCreators/Queries/FutureCreatorListDto.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-using Hutopy.Domain.Entities;
-
-namespace Hutopy.Application.FutureCreators.Queries;
-
-public class FutureCreatorListDto
-{
- public Guid Id { get; init; }
-
- public required string FirstName { get; init; }
-
- public required string LastName { get; init; }
-
- private class Mapping : Profile
- {
- public Mapping()
- {
- CreateMap();
- }
- }
-}
diff --git a/src/Application/FutureCreators/Queries/GetFutureCreatorList.cs b/src/Application/FutureCreators/Queries/GetFutureCreatorList.cs
deleted file mode 100644
index 0281518..0000000
--- a/src/Application/FutureCreators/Queries/GetFutureCreatorList.cs
+++ /dev/null
@@ -1,28 +0,0 @@
-
-using Hutopy.Application.Common.Interfaces;
-using Hutopy.Application.Common.Mappings;
-using Hutopy.Application.Common.Models;
-using Hutopy.Application.Common.Security;
-
-namespace Hutopy.Application.FutureCreators.Queries;
-
-[Authorize(Roles = "Administrator")]
-public record GetFutureCreatorListQuery : IRequest>
-{
- public int PageNumber { get; init; } = 1;
- public int PageSize { get; init; } = 10;
-}
-
-public class GetFutureCreatorListQueryHandler(
- IApplicationDbContext context,
- IMapper mapper)
- : IRequestHandler>
-{
- public async Task> Handle(GetFutureCreatorListQuery request, CancellationToken cancellationToken)
- {
- return await context.FutureCreators
- .OrderBy(x => x.FirstName)
- .ProjectTo(mapper.ConfigurationProvider)
- .PaginatedListAsync(request.PageNumber, request.PageSize);
- }
-}
diff --git a/src/Application/GlobalUsings.cs b/src/Application/GlobalUsings.cs
deleted file mode 100644
index fa904ba..0000000
--- a/src/Application/GlobalUsings.cs
+++ /dev/null
@@ -1,6 +0,0 @@
-global using Ardalis.GuardClauses;
-global using AutoMapper;
-global using AutoMapper.QueryableExtensions;
-global using Microsoft.EntityFrameworkCore;
-global using FluentValidation;
-global using MediatR;
\ No newline at end of file
diff --git a/src/Application/Stripe/Commands/ConfirmStripeTransaction.cs b/src/Application/Stripe/Commands/ConfirmStripeTransaction.cs
deleted file mode 100644
index e287b45..0000000
--- a/src/Application/Stripe/Commands/ConfirmStripeTransaction.cs
+++ /dev/null
@@ -1,73 +0,0 @@
-using Hutopy.Application.Common.Interfaces;
-
-namespace Hutopy.Application.Stripe.Commands;
-public class ConfirmStripeTransactionCommand : IRequest
-{
- public string Id { get; set; }
- public string Object { get; set; }
- public int Created { get; set; }
- public Data Data { get; set; }
- public Request Request { get; set; }
-}
-
-public class Data
-{
- public Object Object { get; set; }
-}
-
-public class Object
-{
- public string Id { get; set; } = string.Empty;
- public int Amount { get; set; }
- public BillingDetails Billing_details { get; set; } = new();
- public string Calculated_statement_descriptor { get; set; } = string.Empty;
- public string Currency { get; set; } = string.Empty;
- public bool Paid { get; set; }
- public string Payment_intent { get; set; } = string.Empty;
- public string Payment_method { get; set; } = string.Empty;
- public string Receipt_url { get; set; } = string.Empty;
- public string Status { get; set; } = string.Empty;
- public string Failure_message { get; set; } = string.Empty;
-}
-
-public class BillingDetails
-{
- public string Email { get; set; } = string.Empty;
- public string Name { get; set; } = string.Empty;
- public string Phone { get; set; } = string.Empty;
-}
-
-public class Request
-{
- public string Id { get; set; } = string.Empty;
-}
-
-public class ConfirmStripeTransactionCommandHandler(
- IApplicationDbContext dbContext,
- IStripeService stripeService
- )
- : IRequestHandler
-{
- public async Task Handle(ConfirmStripeTransactionCommand request, CancellationToken cancellationToken)
- {
- var lastTransaction = await dbContext.UserTransactions.OrderBy(x => x.CreatedAt).LastAsync(cancellationToken);
- var stripeConfirmation = stripeService.ValidateTransaction(request);
-
- if (stripeConfirmation.Succeeded)
- {
- lastTransaction.IsConfirmed = true;
- }
- lastTransaction.Paid = request.Data.Object.Paid;
- lastTransaction.StripeChargeId = request.Data.Object.Id;
- lastTransaction.StripeEventId = request.Id;
- lastTransaction.StripeReceiptUrl = request.Data.Object.Receipt_url;
- lastTransaction.StripePaymentIntent = request.Data.Object.Payment_intent;
- lastTransaction.StripePaymentMethod = request.Data.Object.Payment_method;
- lastTransaction.StripeBillingDetailEmail = request.Data.Object.Billing_details.Email;
- lastTransaction.StripeBillingDetailName = request.Data.Object.Billing_details.Name;
-
- await dbContext.SaveChangesAsync(cancellationToken);
-
- return "";
- }
-}
diff --git a/src/Application/Stripe/Commands/CreateSessionCheckoutCommand.cs b/src/Application/Stripe/Commands/CreateSessionCheckoutCommand.cs
deleted file mode 100644
index 80249d3..0000000
--- a/src/Application/Stripe/Commands/CreateSessionCheckoutCommand.cs
+++ /dev/null
@@ -1,44 +0,0 @@
-using Hutopy.Application.Common.Interfaces;
-using Hutopy.Domain.Entities;
-
-namespace Hutopy.Application.Stripe.Commands;
-
-public record CreateSessionCheckoutCommand : IRequest
-{
- public required Guid CreatorId { get; init; }
- public required int Amount { get; init; }
- public string Currency { get; init; } = "CAD";
- public string TipMessage { get; init; } = string.Empty;
-}
-
-public class CreateSessionCheckoutCommandHandler(
- IApplicationDbContext dbContext,
- IStripeService stripeService
-)
- : IRequestHandler
-{
- public async Task Handle(CreateSessionCheckoutCommand request, CancellationToken cancellationToken)
- {
- var stripeSecret = await stripeService.CreateCheckoutSession(
- request.Amount,
- request.CreatorId.ToString(),
- request.Currency);
-
- // ReSharper disable once PossibleLossOfFraction
- decimal priceInDollars = (request.Amount / 100);
-
- var userTransaction = new UserTransaction
- {
- Currency = request.Currency,
- Amount = priceInDollars,
- TipMessage = request.TipMessage,
- ApplicationUserId = request.CreatorId
- };
-
- await dbContext.UserTransactions.AddAsync(userTransaction, cancellationToken);
-
- await dbContext.SaveChangesAsync(cancellationToken);
-
- return stripeSecret;
- }
-}
diff --git a/src/Application/Stripe/Queries/GetMyLastReceipt.cs b/src/Application/Stripe/Queries/GetMyLastReceipt.cs
deleted file mode 100644
index c4feb05..0000000
--- a/src/Application/Stripe/Queries/GetMyLastReceipt.cs
+++ /dev/null
@@ -1,27 +0,0 @@
-using Hutopy.Application.Common.Interfaces;
-
-namespace Hutopy.Application.Stripe.Queries;
-
-public record GetMyLastReceiptQuery : IRequest
-{
- public Guid CreatorId { get; set; }
- public string Email { get; set; } = string.Empty;
-};
-
-public class GetMyLastReceiptQueryHandler(
- IApplicationDbContext dbContext
-)
- : IRequestHandler
-{
- public async Task Handle(GetMyLastReceiptQuery request, CancellationToken cancellationToken)
- {
- var lastTransaction = await dbContext.UserTransactions.OrderBy(x => x.CreatedAt)
- .LastOrDefaultAsync(
- x => x.ApplicationUserId == request.CreatorId && x.StripeBillingDetailEmail == request.Email,
- cancellationToken);
-
- var receiptUrl = new MyLastReceiptDto { ReceiptUrl = lastTransaction?.StripeReceiptUrl ?? "", };
-
- return receiptUrl;
- }
-}
diff --git a/src/Application/Stripe/Queries/MyLastReceiptDto.cs b/src/Application/Stripe/Queries/MyLastReceiptDto.cs
deleted file mode 100644
index 5ce1645..0000000
--- a/src/Application/Stripe/Queries/MyLastReceiptDto.cs
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace Hutopy.Application.Stripe.Queries;
-
-public class MyLastReceiptDto
-{
- public string ReceiptUrl { get; set; }
-}
diff --git a/src/Application/Users/Commands/CreateUser.cs b/src/Application/Users/Commands/CreateUser.cs
deleted file mode 100644
index d84b69b..0000000
--- a/src/Application/Users/Commands/CreateUser.cs
+++ /dev/null
@@ -1,37 +0,0 @@
-using Hutopy.Application.Common.Interfaces;
-using Microsoft.AspNetCore.Http;
-
-namespace Hutopy.Application.Users.Commands;
-public record CreateUserCommand : IRequest
-{
- public required string FirstName { get; init; }
- public required string LastName { get; init; }
- public required string EmailAddress { get; init; }
- public required string UserName { get; init; }
- public required string Password { get; init; }
-}
-
-public class CreateUserCommandHandler : IRequestHandler
-{
- private readonly IApplicationDbContext _context;
- private readonly IIdentityService _identityService;
-
- public CreateUserCommandHandler(IApplicationDbContext context, IIdentityService identityService)
- {
- _context = context;
- _identityService = identityService;
- }
-
- public async Task Handle(CreateUserCommand request, CancellationToken cancellationToken)
- {
- await _identityService.CreateUserAsync(request.EmailAddress, request.UserName, request.FirstName, request.LastName, request.Password);
-
- var user = await _identityService.FindUserByEmailAsync(request.EmailAddress);
-
- if (user is null) throw new InvalidOperationException("This should never happen, we just created the user.");
-
- await _context.SaveChangesAsync(cancellationToken);
-
- return Results.Ok(user.Id);
- }
-}
diff --git a/src/Application/Users/Commands/Login.cs b/src/Application/Users/Commands/Login.cs
deleted file mode 100644
index 8f232b5..0000000
--- a/src/Application/Users/Commands/Login.cs
+++ /dev/null
@@ -1,27 +0,0 @@
-using Hutopy.Application.Common.Interfaces;
-
-namespace Hutopy.Application.Users.Commands;
-
-public record LoginCommand(
- string Email,
- string Password)
- : IRequest;
-
-public record LoginResponse(
- string AccessToken,
- string RefreshToken);
-
-public class LoginCommandHandler(
- IApplicationDbContext Context,
- IIdentityService identityService)
- : IRequestHandler
-{
- public async Task Handle(LoginCommand request, CancellationToken cancellationToken)
- {
- var accessToken = await identityService.LoginAsync(request.Email, request.Password);
-
- if (string.IsNullOrWhiteSpace(accessToken)) throw new InvalidOperationException("Invalid login credentials");
-
- return new LoginResponse(accessToken, string.Empty);
- }
-}
diff --git a/src/Application/Users/Commands/UpdateCurrentUserCommand.cs b/src/Application/Users/Commands/UpdateCurrentUserCommand.cs
deleted file mode 100644
index 4836544..0000000
--- a/src/Application/Users/Commands/UpdateCurrentUserCommand.cs
+++ /dev/null
@@ -1,50 +0,0 @@
-using System.ComponentModel.DataAnnotations.Schema;
-using Hutopy.Application.Common.Interfaces;
-using Hutopy.Application.Common.Models;
-using Microsoft.AspNetCore.Http;
-
-namespace Hutopy.Application.Users.Commands;
-
-public class UpdateCurrentUserCommand : IRequest
-{
- public required string? Alias { get; init; }
- public required string? FirstName { get; init; }
- public required string? LastName { get; init; }
- public required string? Occupation { get; init; }
- public required string? BirthDate { get; init; }
- public required string? Country { get; init; }
- public required string? City { get; init; }
- public required string? Address { get; init; }
-
- [NotMapped]
- private class Mapping : Profile
- {
- public Mapping()
- {
- CreateMap();
- }
- }
-}
-
-public class UpdateCurrentUserCommandHandler(
- IApplicationDbContext context,
- IIdentityService identityService,
- IMapper mapper)
- : IRequestHandler
-{
- public async Task Handle(UpdateCurrentUserCommand request, CancellationToken cancellationToken)
- {
- var identityUser = await identityService.GetCurrentUserAsync();
-
- if (identityUser?.Id is null) return Results.Problem("Current user not found.");
-
- var userModel = mapper.Map(request);
- userModel.Id = identityUser.Id;
-
- var result = await identityService.UpdateCurrentUserAsync(userModel);
-
- await context.SaveChangesAsync(cancellationToken);
-
- return result.Succeeded ? Results.Ok(result.GetValueOrDefault()) : Results.Problem(result.GetErrorsAsString());
- }
-}
diff --git a/src/Application/Users/Queries/GetUser/GetUserById.cs b/src/Application/Users/Queries/GetUser/GetUserById.cs
deleted file mode 100644
index 1128cfc..0000000
--- a/src/Application/Users/Queries/GetUser/GetUserById.cs
+++ /dev/null
@@ -1,23 +0,0 @@
-using Hutopy.Application.Common.Interfaces;
-
-namespace Hutopy.Application.Users.Queries.GetUser;
-
-public record GetUserByIdQuery : IRequest
-{
- public required string UserId { get; init; }
-}
-
-public class GetUserByIdHandler(
- IIdentityService identityService
- )
- : IRequestHandler
-{
- public async Task Handle(GetUserByIdQuery query, CancellationToken cancellationToken)
- {
- var user = await identityService.FindUserByIdAsync(query.UserId);
-
- if (user is null) throw new InvalidOperationException();
-
- return user.ToDto();
- }
-}
diff --git a/src/Application/Users/Queries/GetUser/GetUserByUserName.cs b/src/Application/Users/Queries/GetUser/GetUserByUserName.cs
deleted file mode 100644
index 131ac8b..0000000
--- a/src/Application/Users/Queries/GetUser/GetUserByUserName.cs
+++ /dev/null
@@ -1,23 +0,0 @@
-using Hutopy.Application.Common.Interfaces;
-
-namespace Hutopy.Application.Users.Queries.GetUser;
-
-public record GetUserByUserNameQuery : IRequest
-{
- public required string UserName { get; init; }
-};
-
-public class GetUserByUserNameQueryHandler(
- IIdentityService identityService
-)
- : IRequestHandler
-{
- public async Task Handle(GetUserByUserNameQuery query, CancellationToken cancellationToken)
- {
- var user = await identityService.GetUserByUserNameAsync(query.UserName);
-
- if (user is null) throw new InvalidOperationException();
-
- return user.ToDto();
- }
-}
diff --git a/src/Application/Users/Queries/GetUser/UserDto.cs b/src/Application/Users/Queries/GetUser/UserDto.cs
deleted file mode 100644
index c64bb21..0000000
--- a/src/Application/Users/Queries/GetUser/UserDto.cs
+++ /dev/null
@@ -1,24 +0,0 @@
-using Hutopy.Application.Common.Models;
-
-namespace Hutopy.Application.Users.Queries.GetUser;
-
-public class UserDto
-{
- public required Guid Id { get; init; }
- public required string UserName { get; init; }
- public string? FirstName { get; init; }
- public string? LastName { get; init; }
- public string? Occupation { get; init; }
-}
-
-public static class UserDtoExtensions
-{
- public static UserDto ToDto(this UserModel model) =>
- new()
- {
- Id = model.Id,
- FirstName = model.Firstname,
- LastName = model.Lastname,
- UserName = model.Username
- };
-}
diff --git a/src/Domain/Entities/FutureCreator.cs b/src/Domain/Entities/FutureCreator.cs
deleted file mode 100644
index b0ebde5..0000000
--- a/src/Domain/Entities/FutureCreator.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-namespace Hutopy.Domain.Entities;
-
-public class FutureCreator : BaseAuditableEntity
-{
- public required string FirstName { get; init; }
- public required string LastName { get; init; }
- public required string EmailAddress { get; init; }
- public required string PhoneNumber { get; init; }
- public required string SocialNetworkAccount { get; init; }
- public required string ReasonToJoin { get; init; }
-}
diff --git a/src/Domain/Entities/UserTransaction.cs b/src/Domain/Entities/UserTransaction.cs
deleted file mode 100644
index b7df7da..0000000
--- a/src/Domain/Entities/UserTransaction.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-namespace Hutopy.Domain.Entities;
-
-public class UserTransaction : BaseAuditableEntity
-{
- public decimal Amount { get; set; }
- public string Currency { get; set; } = "CAD";
- public string TipMessage { get; set; } = string.Empty;
-
- // Foreign key to ApplicationUser
- public required Guid ApplicationUserId { get; set; }
- public bool IsConfirmed { get; set; }
- public string StripeEventId { get; set; } = string.Empty;
- public string StripeChargeId { get; set; } = string.Empty;
- public string StripePaymentIntent { get; set; } = string.Empty;
- public string StripePaymentMethod { get; set; } = string.Empty;
- public string StripeReceiptUrl { get; set; } = string.Empty;
- public string StripeBillingDetailEmail { get; set; } = string.Empty;
- public string StripeBillingDetailName { get; set; } = string.Empty;
- public bool Paid { get; set; }
-}
diff --git a/src/Domain/GlobalUsings.cs b/src/Domain/GlobalUsings.cs
index e312629..d6161ca 100644
--- a/src/Domain/GlobalUsings.cs
+++ b/src/Domain/GlobalUsings.cs
@@ -1,5 +1,2 @@
global using Hutopy.Domain.Common;
-global using Hutopy.Domain.Entities;
-global using Hutopy.Domain.Enums;
global using Hutopy.Domain.Exceptions;
-global using Hutopy.Domain.ValueObjects;
\ No newline at end of file
diff --git a/src/Infrastructure/AzureBlob/AzureBlobStorage.cs b/src/Infrastructure/AzureBlob/AzureBlobStorage.cs
index fab5301..c2f3c72 100644
--- a/src/Infrastructure/AzureBlob/AzureBlobStorage.cs
+++ b/src/Infrastructure/AzureBlob/AzureBlobStorage.cs
@@ -1,13 +1,12 @@
using Azure;
using Azure.Storage.Blobs;
using Azure.Storage.Blobs.Models;
-using Hutopy.Application.Common.Interfaces;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
namespace Hutopy.Infrastructure.AzureBlob;
-public class AzureBlobStorage : IBlobStorage
+public class AzureBlobStorage
{
private const long MaxUploadSize = 10 * 1024 * 1024; // 10 MB in bytes
diff --git a/src/Infrastructure/AzureBlob/ContentTypes.cs b/src/Infrastructure/AzureBlob/ContentTypes.cs
index e7f2da3..8af77a1 100644
--- a/src/Infrastructure/AzureBlob/ContentTypes.cs
+++ b/src/Infrastructure/AzureBlob/ContentTypes.cs
@@ -1,24 +1,24 @@
-using System.Text;
-
-namespace Hutopy.Infrastructure.AzureBlob;
+namespace Hutopy.Infrastructure.AzureBlob;
public static class ContentTypes
{
- private static string ImagePng = "image/png";
- private static string ImageJpeg = "image/jpeg";
- private static string ImageJpg = "image/jpg";
- private static string TextHtml = "text/html";
-
- public static HashSet AllowedContentTypes = new HashSet { ImagePng, ImageJpeg, ImageJpg, TextHtml };
+ private const string ImagePng = "image/png";
+ private const string ImageJpeg = "image/jpeg";
+ private const string ImageJpg = "image/jpg";
- public static bool IsAllowed(string contentType, Stream fileStream)
+ private static readonly HashSet AllowedContentTypes = [ImagePng, ImageJpeg, ImageJpg];
+
+ public static bool IsAllowed(
+ string contentType,
+ Stream fileStream)
{
return IsValidFileType(fileStream) && AllowedContentTypes.Contains(contentType);
}
-
- private static bool IsValidFileType(Stream fileStream)
+
+ private static bool IsValidFileType(
+ Stream fileStream)
{
- byte[] buffer = new byte[512];
+ byte[] buffer = new byte[4];
fileStream.Read(buffer, 0, buffer.Length);
fileStream.Position = 0;
@@ -33,13 +33,6 @@ public static class ContentTypes
{
return true;
}
-
- // Check for HTML content by looking for "" or "" tags
- string content = Encoding.UTF8.GetString(buffer);
- if (content.Contains(""))
- {
- return true;
- }
return false;
}
diff --git a/src/Infrastructure/Data/ApplicationDbContext.cs b/src/Infrastructure/Data/ApplicationDbContext.cs
index a8a5d4f..ae691cb 100644
--- a/src/Infrastructure/Data/ApplicationDbContext.cs
+++ b/src/Infrastructure/Data/ApplicationDbContext.cs
@@ -1,24 +1,10 @@
-using System.Reflection;
-using Hutopy.Application.Common.Interfaces;
-using Hutopy.Domain.Entities;
-using Hutopy.Infrastructure.Identity;
+using Hutopy.Infrastructure.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
namespace Hutopy.Infrastructure.Data
{
- public class ApplicationDbContext(DbContextOptions options)
- : IdentityDbContext(options), IApplicationDbContext
- {
- public DbSet FutureCreators => Set();
- public DbSet UserTransactions => Set();
-
- protected override void OnModelCreating(ModelBuilder builder)
- {
- base.OnModelCreating(builder);
-
- // Apply configurations
- builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly());
- }
- }
+ public class ApplicationDbContext(
+ DbContextOptions options)
+ : IdentityDbContext(options);
}
diff --git a/src/Infrastructure/Data/Configurations/UserTransactionConfiguration.cs b/src/Infrastructure/Data/Configurations/UserTransactionConfiguration.cs
deleted file mode 100644
index f659e55..0000000
--- a/src/Infrastructure/Data/Configurations/UserTransactionConfiguration.cs
+++ /dev/null
@@ -1,21 +0,0 @@
-using Microsoft.EntityFrameworkCore;
-using Microsoft.EntityFrameworkCore.Metadata.Builders;
-using Hutopy.Domain.Entities;
-using Hutopy.Infrastructure.Identity;
-
-namespace Hutopy.Infrastructure.Data.Configurations
-{
- public class UserTransactionConfiguration : IEntityTypeConfiguration
- {
- public void Configure(EntityTypeBuilder builder)
- {
- // Relationship between ApplicationUser and UserTransaction
- builder.HasOne()
- .WithMany()
- .HasForeignKey(ut => ut.ApplicationUserId)
- .IsRequired();
-
- builder.Property(x => x.Amount).HasPrecision(18, 2);
- }
- }
-}
diff --git a/src/Infrastructure/Data/Interceptors/AuditableEntityInterceptor.cs b/src/Infrastructure/Data/Interceptors/AuditableEntityInterceptor.cs
deleted file mode 100644
index b1d7157..0000000
--- a/src/Infrastructure/Data/Interceptors/AuditableEntityInterceptor.cs
+++ /dev/null
@@ -1,55 +0,0 @@
-using Hutopy.Application.Common.Interfaces;
-using Hutopy.Domain.Common;
-using Microsoft.EntityFrameworkCore;
-using Microsoft.EntityFrameworkCore.ChangeTracking;
-using Microsoft.EntityFrameworkCore.Diagnostics;
-
-namespace Hutopy.Infrastructure.Data.Interceptors;
-
-public class AuditableEntityInterceptor(
- IUser user,
- TimeProvider dateTime) : SaveChangesInterceptor
-{
- public override InterceptionResult SavingChanges(DbContextEventData eventData, InterceptionResult result)
- {
- UpdateEntities(eventData.Context);
-
- return base.SavingChanges(eventData, result);
- }
-
- public override ValueTask> SavingChangesAsync(DbContextEventData eventData, InterceptionResult result, CancellationToken cancellationToken = default)
- {
- UpdateEntities(eventData.Context);
-
- return base.SavingChangesAsync(eventData, result, cancellationToken);
- }
-
- public void UpdateEntities(DbContext? context)
- {
- if (context == null) return;
-
- foreach (var entry in context.ChangeTracker.Entries())
- {
- if (entry.State is EntityState.Added or EntityState.Modified || entry.HasChangedOwnedEntities())
- {
- var utcNow = dateTime.GetUtcNow();
- if (entry.State == EntityState.Added)
- {
- entry.Entity.CreatedBy = user.Id;
- entry.Entity.CreatedAt = utcNow;
- }
- entry.Entity.LastModifiedBy = user.Id;
- entry.Entity.LastModifiedAt = utcNow;
- }
- }
- }
-}
-
-public static class Extensions
-{
- public static bool HasChangedOwnedEntities(this EntityEntry entry) =>
- entry.References.Any(r =>
- r.TargetEntry != null &&
- r.TargetEntry.Metadata.IsOwned() &&
- (r.TargetEntry.State == EntityState.Added || r.TargetEntry.State == EntityState.Modified));
-}
diff --git a/src/Infrastructure/Data/Interceptors/DispatchDomainEventsInterceptor.cs b/src/Infrastructure/Data/Interceptors/DispatchDomainEventsInterceptor.cs
deleted file mode 100644
index 3123ee7..0000000
--- a/src/Infrastructure/Data/Interceptors/DispatchDomainEventsInterceptor.cs
+++ /dev/null
@@ -1,45 +0,0 @@
-using Hutopy.Domain.Common;
-using MediatR;
-using Microsoft.EntityFrameworkCore;
-using Microsoft.EntityFrameworkCore.Diagnostics;
-
-namespace Hutopy.Infrastructure.Data.Interceptors;
-
-public class DispatchDomainEventsInterceptor(
- IPublisher mediator)
- : SaveChangesInterceptor
-{
- public override InterceptionResult SavingChanges(DbContextEventData eventData, InterceptionResult result)
- {
- DispatchDomainEvents(eventData.Context).GetAwaiter().GetResult();
-
- return base.SavingChanges(eventData, result);
-
- }
-
- public override async ValueTask> SavingChangesAsync(DbContextEventData eventData, InterceptionResult result, CancellationToken cancellationToken = default)
- {
- await DispatchDomainEvents(eventData.Context);
-
- return await base.SavingChangesAsync(eventData, result, cancellationToken);
- }
-
- public async Task DispatchDomainEvents(DbContext? context)
- {
- if (context == null) return;
-
- var entities = context.ChangeTracker
- .Entries()
- .Where(e => e.Entity.DomainEvents.Any())
- .Select(e => e.Entity);
-
- var domainEvents = entities
- .SelectMany(e => e.DomainEvents)
- .ToList();
-
- entities.ToList().ForEach(e => e.ClearDomainEvents());
-
- foreach (var domainEvent in domainEvents)
- await mediator.Publish(domainEvent);
- }
-}
diff --git a/src/Infrastructure/DependencyInjection.cs b/src/Infrastructure/DependencyInjection.cs
index f816daa..0a328f5 100644
--- a/src/Infrastructure/DependencyInjection.cs
+++ b/src/Infrastructure/DependencyInjection.cs
@@ -1,10 +1,7 @@
-using Hutopy.Application.Common.Interfaces;
-using Hutopy.Domain.Constants;
+using Hutopy.Domain.Constants;
using Hutopy.Infrastructure.AzureBlob;
using Hutopy.Infrastructure.Data;
-using Hutopy.Infrastructure.Data.Interceptors;
using Hutopy.Infrastructure.Identity;
-using Hutopy.Infrastructure.Stripe;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics;
@@ -23,17 +20,12 @@ public static class DependencyInjection
var connectionString = configuration.GetConnectionString("MssqlConnection")
?? throw new InvalidOperationException("Missing ConnectionStrings:MssqlConnection");
- services.AddScoped();
- services.AddScoped();
-
services.AddDbContext((sp, options) =>
{
options.AddInterceptors(sp.GetServices());
options.UseSqlServer(connectionString);
});
- services.AddScoped(provider => provider.GetRequiredService());
-
services.AddScoped();
services.AddAuthentication()
@@ -52,14 +44,11 @@ public static class DependencyInjection
// Singleton services
services.AddSingleton(TimeProvider.System);
- services.AddSingleton();
+ services.AddSingleton();
// Scoped services
- services.AddScoped();
+ services.AddScoped();
- // Transient services
- services.AddTransient();
-
services.AddAuthorization(options =>
options.AddPolicy(Policies.CanPurge, policy => policy.RequireRole(Roles.Administrator)));
diff --git a/src/Infrastructure/Identity/IdentityResultExtensions.cs b/src/Infrastructure/Identity/IdentityResultExtensions.cs
index 630c372..8ab92c1 100644
--- a/src/Infrastructure/Identity/IdentityResultExtensions.cs
+++ b/src/Infrastructure/Identity/IdentityResultExtensions.cs
@@ -1,4 +1,4 @@
-using Hutopy.Application.Common.Models;
+using Hutopy.Infrastructure.Identity.Models;
using Microsoft.AspNetCore.Identity;
namespace Hutopy.Infrastructure.Identity;
diff --git a/src/Infrastructure/Identity/IdentityService.cs b/src/Infrastructure/Identity/IdentityService.cs
index 6195a70..66ce6f8 100644
--- a/src/Infrastructure/Identity/IdentityService.cs
+++ b/src/Infrastructure/Identity/IdentityService.cs
@@ -1,74 +1,14 @@
-using Google.Apis.Oauth2.v2.Data;
using System.Security.Claims;
-using Hutopy.Application.Common.Interfaces;
-using Hutopy.Application.Common.Models;
-using Hutopy.Infrastructure.Utils;
-using Microsoft.AspNetCore.Authorization;
+using Hutopy.Infrastructure.Identity.Models;
using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.Identity;
-using Microsoft.Extensions.Options;
namespace Hutopy.Infrastructure.Identity;
public class IdentityService(
ApplicationUserManager userManager,
- SignInManager signInManager,
- IUserClaimsPrincipalFactory userClaimsPrincipalFactory,
- IAuthorizationService authorizationService,
- IHttpContextAccessor contextAccessor,
- IOptionsSnapshot jwtOptions
+ IHttpContextAccessor contextAccessor
)
- : IIdentityService
{
- public async Task GetUserNameAsync(Guid userId)
- {
- var user = await userManager.FindByIdAsync(userId.ToString());
-
- return user?.UserName;
- }
-
- public async Task GetUserByUserNameAsync(string userName)
- {
- var user = await userManager.FindByNameAsync(userName);
-
- if (user == null) return null;
-
- return new()
- {
- Id = user.Id,
- Username = user.UserName!,
- PhoneNumber = user.PhoneNumber,
- Email = user.Email,
- Alias = user.Alias,
- Firstname = user.Firstname,
- Lastname = user.Lastname,
- BirthDate = user.BirthDate,
- Address = user.Address,
- PortraitUrl = user.PortraitUrl
- };
- }
-
- public async Task> CreateUserAsync(Userinfo userInfo)
- {
- var applicationUser = new ApplicationUser
- {
- UserName = userInfo.Name,
- Email = userInfo.Email,
- Firstname = userInfo.GivenName,
- Lastname = userInfo.FamilyName
- };
-
- var password = Guid.NewGuid().ToString("N")[..32];
-
- var identityResult = await userManager.CreateAsync(applicationUser, password);
-
- var applicationResult = identityResult.ToApplicationResult();
-
- var result = new Result(applicationUser.Id, applicationResult.Succeeded, applicationResult.Errors);
-
- return result;
- }
-
public async Task> CreateUserAsync(string email, string userName, string firstName, string lastName,
string password)
{
@@ -89,35 +29,6 @@ public class IdentityService(
return result;
}
- public async Task> UpdateCurrentUserAsync(UserModel userModel)
- {
- var applicationUser = await userManager.FindByIdAsync(userModel.Id.ToString());
-
- if (applicationUser is null) return Result.Failure(Guid.Empty, new[] { "User not found." });
-
- applicationUser.Id = userModel.Id;
- applicationUser.Email = userModel.Email;
- applicationUser.PhoneNumber = userModel.PhoneNumber;
- applicationUser.Alias = userModel.Alias;
- applicationUser.Firstname = userModel.Firstname;
- applicationUser.Lastname = userModel.Lastname;
- applicationUser.BirthDate = userModel.BirthDate;
- applicationUser.Address = userModel.Address;
- applicationUser.PortraitUrl = userModel.PortraitUrl;
-
- var response = await userManager.UpdateAsync(applicationUser);
-
- var applicationResult = response.ToApplicationResult();
-
- var result = new Result(
- userModel.Id,
- applicationResult.Succeeded,
- applicationResult.Errors);
-
- return result;
- }
-
-
private static UserModel BuildModelFrom(ApplicationUser response)
{
var userModel = new UserModel
@@ -168,80 +79,6 @@ public class IdentityService(
return await FindUserByIdAsync(currentUserId);
}
- public async Task UpdateCurrentUserPortraitUrlAsync(string url)
- {
- var userModel = await GetCurrentUserAsync();
-
- var applicationUser = await userManager.FindByIdAsync(userModel.Id.ToString());
- if (applicationUser is null) return Result.Failure(["ApplicationUser not found."]);
-
- applicationUser.PortraitUrl = url;
-
- var response = await userManager.UpdateAsync(applicationUser);
-
- return response.ToApplicationResult();
- }
-
- public async Task IsInRoleAsync(Guid userId, string role)
- {
- var user = await userManager.FindByIdAsync(userId.ToString());
-
- return user != null && await userManager.IsInRoleAsync(user, role);
- }
-
- public async Task CurrentUserIsInRoleAsync(string role)
- {
- var currentUserModel = await GetCurrentUserAsync();
- var currentUser = await userManager.FindByIdAsync(currentUserModel.Id.ToString());
-
- return currentUser != null && await userManager.IsInRoleAsync(currentUser, role);
- }
-
- public async Task AuthorizeAsync(Guid userId, string policyName)
- {
- var user = await userManager.FindByIdAsync(userId.ToString());
-
- if (user == null)
- {
- return false;
- }
-
- var principal = await userClaimsPrincipalFactory.CreateAsync(user);
-
- var result = await authorizationService.AuthorizeAsync(principal, policyName);
-
- return result.Succeeded;
- }
-
- public async Task DeleteUserAsync(string userId)
- {
- var user = await userManager.FindByIdAsync(userId);
-
- return user != null ? await DeleteUserAsync(user) : Result.Success();
- }
-
- public async Task DeleteUserAsync(ApplicationUser user)
- {
- var result = await userManager.DeleteAsync(user);
-
- return result.ToApplicationResult();
- }
-
- public async Task AddRoleAsync(string userId, string role)
- {
- var hasAdminAccess = await CurrentUserIsInRoleAsync("Administrator");
-
- if (!hasAdminAccess) return Result.Failure(new[] { "Only administrator can assign new roles to a user." });
-
- var user = await userManager.FindByIdAsync(userId);
-
- if (user is null) return Result.Failure(new[] { "User not found." });
-
- var result = await userManager.AddToRoleAsync(user, role);
-
- return result.ToApplicationResult();
- }
-
public async Task> GetCurrentUserRolesAsync()
{
var currentUserModel = await GetCurrentUserAsync();
@@ -255,32 +92,4 @@ public class IdentityService(
return userRoles;
}
- public async Task LoginAsync(string userName, string password)
- {
- var result =
- await signInManager.PasswordSignInAsync(userName, password, isPersistent: false, lockoutOnFailure: false);
-
- if (!result.Succeeded)
- {
- return null;
- }
-
- var user = await GetUserByUserNameAsync(userName);
-
- if (user is null) throw new InvalidOperationException();
-
- var token = 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,
- alias: user.Alias,
- firstname: user.Firstname,
- lastname: user.Lastname,
- portraitUrl: user.PortraitUrl);
-
- return token;
- }
}
diff --git a/src/Application/Common/Models/Result.cs b/src/Infrastructure/Identity/Models/Result.cs
similarity index 95%
rename from src/Application/Common/Models/Result.cs
rename to src/Infrastructure/Identity/Models/Result.cs
index 8c30b4a..9d26a1d 100644
--- a/src/Application/Common/Models/Result.cs
+++ b/src/Infrastructure/Identity/Models/Result.cs
@@ -1,4 +1,4 @@
-namespace Hutopy.Application.Common.Models;
+namespace Hutopy.Infrastructure.Identity.Models;
public class Result(
bool succeeded,
diff --git a/src/Application/Common/Models/RoleModel.cs b/src/Infrastructure/Identity/Models/RoleModel.cs
similarity index 66%
rename from src/Application/Common/Models/RoleModel.cs
rename to src/Infrastructure/Identity/Models/RoleModel.cs
index 5c1c786..051a272 100644
--- a/src/Application/Common/Models/RoleModel.cs
+++ b/src/Infrastructure/Identity/Models/RoleModel.cs
@@ -1,4 +1,4 @@
-namespace Hutopy.Application.Common.Models;
+namespace Hutopy.Infrastructure.Identity.Models;
public class RoleModel
{
diff --git a/src/Application/Common/Models/UserModel.cs b/src/Infrastructure/Identity/Models/UserModel.cs
similarity index 90%
rename from src/Application/Common/Models/UserModel.cs
rename to src/Infrastructure/Identity/Models/UserModel.cs
index 2a28ec7..b43bc71 100644
--- a/src/Application/Common/Models/UserModel.cs
+++ b/src/Infrastructure/Identity/Models/UserModel.cs
@@ -1,4 +1,4 @@
-namespace Hutopy.Application.Common.Models;
+namespace Hutopy.Infrastructure.Identity.Models;
public class UserModel
{
diff --git a/src/Infrastructure/Identity/RoleService.cs b/src/Infrastructure/Identity/RoleService.cs
deleted file mode 100644
index ef8749a..0000000
--- a/src/Infrastructure/Identity/RoleService.cs
+++ /dev/null
@@ -1,49 +0,0 @@
-using Hutopy.Application.Common.Interfaces;
-using Hutopy.Application.Common.Models;
-using Microsoft.AspNetCore.Identity;
-
-namespace Hutopy.Infrastructure.Identity;
-
-public class RoleService(
- RoleManager roleManager
- )
- : IRoleService
-{
- public async Task CreateRoleAsync(string roleName)
- {
- var role = new ApplicationRole { Name = roleName, Id = Guid.NewGuid()};
- var result = await roleManager.CreateAsync(role);
-
- return result.ToApplicationResult();
- }
-
- public async Task DeleteRoleAsync(string roleName)
- {
- var role = new ApplicationRole { Name = roleName };
- var result = await roleManager.DeleteAsync(role);
-
- return result.ToApplicationResult();
- }
-
- public async Task FindRoleByIdAsync(string roleId)
- {
- var result = await roleManager.FindByIdAsync(roleId);
-
- if (result is null) return null;
-
- var roleModel = new RoleModel { Id = result.Id, Name = result.Name };
-
- return roleModel;
- }
-
- public async Task FindRoleByNameAsync(string roleName)
- {
- var result = await roleManager.FindByNameAsync(roleName);
-
- if (result is null) return null;
-
- var roleModel = new RoleModel { Id = result.Id, Name = result.Name };
-
- return roleModel;
- }
-}
diff --git a/src/Infrastructure/Stripe/StripeService.cs b/src/Infrastructure/Stripe/StripeService.cs
deleted file mode 100644
index 863db7c..0000000
--- a/src/Infrastructure/Stripe/StripeService.cs
+++ /dev/null
@@ -1,74 +0,0 @@
-using Stripe;
-using Stripe.Checkout;
-using Hutopy.Application.Common.Interfaces;
-using Microsoft.AspNetCore.Http;
-using Hutopy.Application.Common.Models;
-using Hutopy.Application.Stripe.Commands;
-using Microsoft.Extensions.Configuration;
-
-namespace Hutopy.Infrastructure.Stripe;
-
-public class StripeService : IStripeService
-{
- private readonly IHttpContextAccessor _httpContextAccessor;
-
- public StripeService(IHttpContextAccessor httpContextAccessor, IConfiguration configuration)
- {
- _httpContextAccessor = httpContextAccessor;
- var stripeKey = configuration["Stripe:apiKey"] ?? "";
- StripeConfiguration.ApiKey = stripeKey;
- }
-
- public async Task CreateCheckoutSession(int amount, string creatorId, string currency = "cad")
- {
- var options = new SessionCreateOptions
- {
- LineItems =
- [
- new SessionLineItemOptions
- {
- PriceData = new SessionLineItemPriceDataOptions
- {
- UnitAmount = amount,
- Currency = currency,
- ProductData = new SessionLineItemPriceDataProductDataOptions { Name = "Tip", },
- },
- Quantity = 1,
- }
-
- ],
- Mode = "payment",
- UiMode = "embedded",
- ReturnUrl = $"https://hutopy.ca/paymentcompleted?creatorId={creatorId}",
- InvoiceCreation = new SessionInvoiceCreationOptions(){ Enabled = true},
- ClientReferenceId = creatorId
- };
-
- var service = new SessionService();
- Session session = await service.CreateAsync(options);
-
- return session.ClientSecret;
- }
-
- public Result ValidateTransaction(ConfirmStripeTransactionCommand request)
- {
- try
- {
- if (request.Data.Object.Status is "succeeded")
- {
- return new Result(true, new List());
- }
-
- return new Result(false, new List());
- }
- catch (StripeException e)
- {
- Console.WriteLine("Error: {0}", e.Message);
- return new Result(false, new List{e.Message});
- }
- catch (Exception e)
- {
- return new Result(false, new List{e.Message});
- }
- }
-}
diff --git a/src/Web/Controllers/FacebookController.cs b/src/Web/Controllers/FacebookController.cs
index b8b52d9..bae7799 100644
--- a/src/Web/Controllers/FacebookController.cs
+++ b/src/Web/Controllers/FacebookController.cs
@@ -1,5 +1,5 @@
using System.Security.Claims;
-using Hutopy.Application.Common.Interfaces;
+using Hutopy.Infrastructure.Identity;
using Hutopy.Infrastructure.Utils;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
@@ -8,7 +8,9 @@ using Microsoft.AspNetCore.Mvc;
namespace Hutopy.Web.Controllers;
-public class FacebookController(IIdentityService identityService) : Controller
+public class FacebookController(
+ IdentityService identityService)
+ : Controller
{
[Microsoft.AspNetCore.Mvc.HttpGet("/api/facebook/sign-in")]
public async Task SignIn()
@@ -33,7 +35,7 @@ public class FacebookController(IIdentityService identityService) : Controller
var claimsIdentity = new ClaimsIdentity(
new List
{
- new(ClaimTypes.Name, name),
+ new(ClaimTypes.Name, name),
new(ClaimTypes.Email, email),
new(ClaimTypes.GivenName, givenName),
new(ClaimTypes.Surname, familyName)
diff --git a/src/Web/DependencyInjection.cs b/src/Web/DependencyInjection.cs
index 4161aad..1ce13a2 100644
--- a/src/Web/DependencyInjection.cs
+++ b/src/Web/DependencyInjection.cs
@@ -1,9 +1,6 @@
using System.Text;
using Azure.Identity;
-using Hutopy.Application.Common.Interfaces;
using Hutopy.Infrastructure.Data;
-using Hutopy.Web.Infrastructure;
-using Hutopy.Web.Services;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.Facebook;
using Microsoft.AspNetCore.Authentication.Google;
@@ -19,15 +16,11 @@ public static class DependencyInjection
{
services.AddDatabaseDeveloperPageExceptionFilter();
- services.AddScoped();
-
services.AddHttpContextAccessor();
services.AddHealthChecks()
.AddDbContextCheck();
- services.AddExceptionHandler();
-
services.AddRazorPages();
services.AddHttpClient();
diff --git a/src/Web/Endpoints/JoinUs.cs b/src/Web/Endpoints/JoinUs.cs
deleted file mode 100644
index 8353ba1..0000000
--- a/src/Web/Endpoints/JoinUs.cs
+++ /dev/null
@@ -1,26 +0,0 @@
-using Hutopy.Application.Common.Models;
-using Hutopy.Application.FutureCreators.Commands;
-using Hutopy.Application.FutureCreators.Queries;
-using Hutopy.Web.Infrastructure;
-
-namespace Hutopy.Web.Endpoints;
-
-public class JoinUs : EndpointGroupBase
-{
- public override void Map(WebApplication app)
- {
- app.MapGroup(this)
- .MapGet(GetFutureCreators)
- .MapPost(CreateFutureCreator);
- }
-
- private static Task CreateFutureCreator(ISender sender, CreateFutureCreatorCommand command)
- {
- return sender.Send(command);
- }
-
- private static Task> GetFutureCreators(ISender sender, [AsParameters] GetFutureCreatorListQuery query)
- {
- return sender.Send(query);
- }
-}
diff --git a/src/Web/Endpoints/Stripe.cs b/src/Web/Endpoints/Stripe.cs
deleted file mode 100644
index be76a0b..0000000
--- a/src/Web/Endpoints/Stripe.cs
+++ /dev/null
@@ -1,31 +0,0 @@
-using Hutopy.Application.Stripe.Commands;
-using Hutopy.Application.Stripe.Queries;
-using Hutopy.Web.Infrastructure;
-
-namespace Hutopy.Web.Endpoints;
-
-public class Stripe : EndpointGroupBase
-{
- public override void Map(WebApplication app)
- {
- app.MapGroup(this)
- .MapPost(ConfirmTransaction, "/confirmTransaction")
- .MapGet(GetMyLastReceipt, "/getMyLastReceipt")
- .MapPost(CreateSessionCheckout);
- }
-
- private static Task CreateSessionCheckout(ISender sender, CreateSessionCheckoutCommand command)
- {
- return sender.Send(command);
- }
-
- private async static Task ConfirmTransaction(ISender sender, ConfirmStripeTransactionCommand command)
- {
- return await sender.Send(command);
- }
-
- private static async Task GetMyLastReceipt(ISender sender, [AsParameters] GetMyLastReceiptQuery query)
- {
- return await sender.Send(query);
- }
-}
diff --git a/src/Web/Endpoints/UpdateMyUser.cs b/src/Web/Endpoints/UpdateMyUser.cs
deleted file mode 100644
index 7118e4e..0000000
--- a/src/Web/Endpoints/UpdateMyUser.cs
+++ /dev/null
@@ -1,19 +0,0 @@
-using Hutopy.Application.Users.Commands;
-using Hutopy.Web.Infrastructure;
-
-namespace Hutopy.Web.Endpoints;
-
-public class UpdateMyUser : EndpointGroupBase
-{
- public override void Map(WebApplication app)
- {
- app.MapGroup(this)
- .RequireAuthorization()
- .MapPatch("/profile", UpdateCurrentUser);
- }
-
- private static async Task UpdateCurrentUser(ISender sender, UpdateCurrentUserCommand command)
- {
- return await sender.Send(command);
- }
-}
diff --git a/src/Web/Endpoints/Users.cs b/src/Web/Endpoints/Users.cs
deleted file mode 100644
index 820aa37..0000000
--- a/src/Web/Endpoints/Users.cs
+++ /dev/null
@@ -1,39 +0,0 @@
-using Hutopy.Application.Users.Commands;
-using Hutopy.Application.Users.Queries.GetUser;
-using Hutopy.Web.Infrastructure;
-
-namespace Hutopy.Web.Endpoints;
-
-public class Users : EndpointGroupBase
-{
- public override void Map(WebApplication app)
- {
- app.MapGroup(this)
- .MapPost(CreateUser)
- .MapPost(Login, "/login")
- .MapGet(GetUserById, "/id")
- .MapGet(GetUserByUserName, "/user-name");
- }
-
- private static async Task CreateUser(ISender sender, CreateUserCommand command)
- {
- return await sender.Send(command);
- }
-
- private static async Task GetUserById(ISender sender,
- [AsParameters] GetUserByIdQuery query)
- {
- return await sender.Send(query);
- }
-
- private static async Task GetUserByUserName(ISender sender,
- [AsParameters] GetUserByUserNameQuery query)
- {
- return await sender.Send(query);
- }
-
- private static async Task Login(ISender sender, LoginCommand command)
- {
- return await sender.Send(command);
- }
-}
diff --git a/src/Web/Features/Contents/Data/ContentDbContext.cs b/src/Web/Features/Contents/Data/ContentDbContext.cs
index 11edff0..94b88ea 100644
--- a/src/Web/Features/Contents/Data/ContentDbContext.cs
+++ b/src/Web/Features/Contents/Data/ContentDbContext.cs
@@ -10,11 +10,11 @@ public class ContentDbContext(
public DbSet Contents => Set();
public DbSet Creators => Set();
- public DbSet Subscriptions => Set();
+ public DbSet Followers => Set();
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
- modelBuilder.HasDefaultSchema("Content");
+ modelBuilder.HasDefaultSchema(SchemaName);
modelBuilder
.Entity()
@@ -34,13 +34,13 @@ public class ContentDbContext(
.ToTable(nameof(ContentReaction).Pluralize());
modelBuilder
- .Entity()
+ .Entity()
.HasOne(c => c.Creator)
.WithMany()
.HasForeignKey(c => c.CreatorId);
modelBuilder
- .Entity()
+ .Entity()
.HasKey(s => new { s.CreatedBy, s.CreatorId });
modelBuilder
diff --git a/src/Web/Features/Contents/Data/Subscription.cs b/src/Web/Features/Contents/Data/Follower.cs
similarity index 89%
rename from src/Web/Features/Contents/Data/Subscription.cs
rename to src/Web/Features/Contents/Data/Follower.cs
index 4875fd9..c7368f1 100644
--- a/src/Web/Features/Contents/Data/Subscription.cs
+++ b/src/Web/Features/Contents/Data/Follower.cs
@@ -1,6 +1,6 @@
namespace Hutopy.Web.Features.Contents.Data;
-public class Subscription
+public class Follower
{
public Guid CreatedBy { get; init; }
public DateTimeOffset CreatedAt { get; init; }
diff --git a/src/Web/Features/Contents/Handlers/ChangeBanner.cs b/src/Web/Features/Contents/Handlers/ChangeBanner.cs
index c13aa95..950d1b6 100644
--- a/src/Web/Features/Contents/Handlers/ChangeBanner.cs
+++ b/src/Web/Features/Contents/Handlers/ChangeBanner.cs
@@ -1,5 +1,5 @@
using Hutopy.Application.AzureBlobStorage.Constants;
-using Hutopy.Application.Common.Interfaces;
+using Hutopy.Infrastructure.AzureBlob;
using Hutopy.Web.Features.Contents.Data;
namespace Hutopy.Web.Features.Contents.Handlers;
@@ -16,7 +16,7 @@ public record ChangeBannerResponse(
[PublicAPI]
public class ChangeBannerHandler(
ContentDbContext context,
- IBlobStorage blobStorage)
+ AzureBlobStorage blobStorage)
: Endpoint
{
public override void Configure()
diff --git a/src/Web/Features/Contents/Handlers/ChangeLogo.cs b/src/Web/Features/Contents/Handlers/ChangeLogo.cs
index 0caf367..5bbc6b9 100644
--- a/src/Web/Features/Contents/Handlers/ChangeLogo.cs
+++ b/src/Web/Features/Contents/Handlers/ChangeLogo.cs
@@ -1,5 +1,5 @@
using Hutopy.Application.AzureBlobStorage.Constants;
-using Hutopy.Application.Common.Interfaces;
+using Hutopy.Infrastructure.AzureBlob;
using Hutopy.Web.Features.Contents.Data;
namespace Hutopy.Web.Features.Contents.Handlers;
@@ -27,7 +27,7 @@ public sealed class ChangeLogoRequestValidator : Validator
[PublicAPI]
public class ChangeLogoHandler(
ContentDbContext context,
- IBlobStorage blobStorage)
+ AzureBlobStorage blobStorage)
: Endpoint
{
public override void Configure()
diff --git a/src/Web/Features/Contents/Handlers/CreateContent.cs b/src/Web/Features/Contents/Handlers/CreateContent.cs
index e98f7f1..46e9bb9 100644
--- a/src/Web/Features/Contents/Handlers/CreateContent.cs
+++ b/src/Web/Features/Contents/Handlers/CreateContent.cs
@@ -1,6 +1,6 @@
using System.Collections.Concurrent;
using Hutopy.Application.AzureBlobStorage.Constants;
-using Hutopy.Application.Common.Interfaces;
+using Hutopy.Infrastructure.AzureBlob;
using Hutopy.Web.Common;
using Hutopy.Web.Features.Contents.Data;
using Hutopy.Web.Features.Contents.Handlers.Models;
@@ -44,7 +44,7 @@ public sealed class PostContentRequestValidator : Validator
}
public sealed class PostContent(
- IBlobStorage blobStorage,
+ AzureBlobStorage blobStorage,
ContentDbContext context)
: Endpoint
{
diff --git a/src/Web/Features/Contents/Handlers/SubscribeToCreator.cs b/src/Web/Features/Contents/Handlers/FollowCreator.cs
similarity index 59%
rename from src/Web/Features/Contents/Handlers/SubscribeToCreator.cs
rename to src/Web/Features/Contents/Handlers/FollowCreator.cs
index 39352ff..3386046 100644
--- a/src/Web/Features/Contents/Handlers/SubscribeToCreator.cs
+++ b/src/Web/Features/Contents/Handlers/FollowCreator.cs
@@ -5,40 +5,40 @@ using Hutopy.Web.Features.Contents.Handlers.Models;
namespace Hutopy.Web.Features.Contents.Handlers;
[PublicAPI]
-public sealed class SubscribeToCreatorRequest
+public sealed class FollowCreatorRequest
{
public Guid CreatorId { get; set; }
}
[PublicAPI]
-public sealed class SubscribeToCreatorHandler(
+public sealed class FollowCreatorHandler(
ContentDbContext context)
- : Endpoint
+ : Endpoint
{
public override void Configure()
{
- Post("/api/creators/{CreatorId}/subscribe");
- Options((o => o.WithTags("Subscriptions")));
+ Post("/api/creators/{CreatorId}/follow");
+ Options((o => o.WithTags("creators")));
Description(x => x.Accepts("*/*"));
}
public override async Task HandleAsync(
- SubscribeToCreatorRequest req,
+ FollowCreatorRequest req,
CancellationToken ct)
{
- await context.Subscriptions.AddAsync(
- new() { CreatedBy = HttpContext.User.GetUserId(), CreatorId = req.CreatorId },
+ await context.Followers.AddAsync(
+ new Follower { CreatedBy = User.GetUserId(), CreatorId = req.CreatorId },
ct);
await context.SaveChangesAsync(ct);
var creator = await context
.Creators
- .Where(c => c.Id == req.CreatorId)
- .Select(c => new SubscriptionModel(
+ .Where(creator => creator.Id == req.CreatorId)
+ .Select(creator => new FollowModel(
req.CreatorId,
- c.Name,
- c.Images.Logo
+ creator.Name,
+ creator.Images.Logo
))
.FirstOrDefaultAsync(cancellationToken: ct);
diff --git a/src/Web/Features/Contents/Handlers/GetCreatorByAlias.cs b/src/Web/Features/Contents/Handlers/GetCreatorByAlias.cs
index 2a2b161..c53d2a6 100644
--- a/src/Web/Features/Contents/Handlers/GetCreatorByAlias.cs
+++ b/src/Web/Features/Contents/Handlers/GetCreatorByAlias.cs
@@ -50,7 +50,7 @@ public class GetCreatorByAliasHandler(
}
else
{
- var subscriberCount = await context.Subscriptions.CountAsync(
+ var followerCount = await context.Followers.CountAsync(
s => s.CreatorId == creator.Id,
cancellationToken: ct);
@@ -63,7 +63,7 @@ public class GetCreatorByAliasHandler(
creator.Socials,
creator.Colors,
creator.Images,
- subscriberCount);
+ followerCount);
await SendAsync(model, cancellation: ct);
}
diff --git a/src/Web/Features/Contents/Handlers/GetFollowedContents.cs b/src/Web/Features/Contents/Handlers/GetFollowedContents.cs
deleted file mode 100644
index 39de093..0000000
--- a/src/Web/Features/Contents/Handlers/GetFollowedContents.cs
+++ /dev/null
@@ -1,75 +0,0 @@
-using Hutopy.Web.Common;
-using Hutopy.Web.Extensions;
-using Hutopy.Web.Features.Contents.Data;
-using Hutopy.Web.Features.Contents.Handlers.Models;
-
-namespace Hutopy.Web.Features.Contents.Handlers;
-
-[PublicAPI]
-public sealed class GetFollowedContentsRequest
-{
- [BindFrom("page_size")] public int PageSize { get; set; } = 10;
- [BindFrom("last_id")] public Guid? LastId { get; set; }
-}
-
-[PublicAPI]
-public class GetFollowedContentsHandler(
- ContentDbContext context)
- : Endpoint>
-{
- public override void Configure()
- {
- Get("/api/contents/followed");
- Options(o => o.WithTags("Contents"));
- AllowAnonymous();
- }
-
- public override async Task HandleAsync(
- GetFollowedContentsRequest req,
- CancellationToken ct)
- {
-
- var userId = HttpContext.User.GetUserId();
-
- var userSubscriptionIds = await context
- .Subscriptions
- .Where(s => s.CreatedBy == userId)
- .Select(s => s.CreatorId)
- .ToListAsync(cancellationToken: ct);
-
- var query = context.Contents
- .Where(c => c.DeletedAt == null)
- .Where(x => userSubscriptionIds.Contains(x.CreatedBy));
- if (req.LastId.HasValue)
- {
- query = query.Where(c => c.Id > req.LastId.Value);
- }
-
- query = query.OrderByDescending(c => c.CreatedAt);
-
- var content = await query
- .Select(c => new ContentModel
- {
- Id = c.Id,
- CreatedBy = c.CreatedBy,
- CreatedByName = c.Creator!.Name,
- CreatedByPortraitUrl = c.Creator.Images.Logo,
- CreatedAt = c.CreatedAt,
- DeletedBy = c.DeletedBy,
- DeletedAt = c.DeletedAt,
- Title = c.Title,
- Description = c.Description,
- Urls = c.Urls,
- Reactions = c.Reactions.Select(x => new ReactionModel
- {
- Reaction = x.Reaction.FromEnum(),
- UserId = x.UserId,
- UserName = x.UserName
- }).ToList()
- })
- .Take(req.PageSize)
- .ToListAsync(ct);
-
- await SendAsync(content, cancellation: ct);
- }
-}
diff --git a/src/Web/Features/Contents/Handlers/GetSubscriptions.cs b/src/Web/Features/Contents/Handlers/GetFollowedCreators.cs
similarity index 73%
rename from src/Web/Features/Contents/Handlers/GetSubscriptions.cs
rename to src/Web/Features/Contents/Handlers/GetFollowedCreators.cs
index 02c320e..6ab7f52 100644
--- a/src/Web/Features/Contents/Handlers/GetSubscriptions.cs
+++ b/src/Web/Features/Contents/Handlers/GetFollowedCreators.cs
@@ -5,14 +5,14 @@ using Hutopy.Web.Features.Contents.Handlers.Models;
namespace Hutopy.Web.Features.Contents.Handlers;
[PublicAPI]
-public class GetSubscriptionsHandler(
+public class GetFollowedCreatorsHandler(
ContentDbContext context)
- : EndpointWithoutRequest>
+ : EndpointWithoutRequest>
{
public override void Configure()
{
- Get("/api/subscriptions");
- Options((o => o.WithTags("Subscriptions")));
+ Get("/api/creators/followed");
+ Options((o => o.WithTags("Creators")));
}
public override async Task HandleAsync(
@@ -21,9 +21,9 @@ public class GetSubscriptionsHandler(
var userId = HttpContext.User.GetUserId();
var subscriptions = await context
- .Subscriptions
+ .Followers
.Where(s => s.CreatedBy == userId)
- .Select(s => new SubscriptionModel(
+ .Select(s => new FollowModel(
s.CreatorId,
s.Creator!.Name,
s.Creator.Images.Logo))
diff --git a/src/Web/Features/Contents/Handlers/Models/CreatorModel.cs b/src/Web/Features/Contents/Handlers/Models/CreatorModel.cs
index 83b7bd6..00abe19 100644
--- a/src/Web/Features/Contents/Handlers/Models/CreatorModel.cs
+++ b/src/Web/Features/Contents/Handlers/Models/CreatorModel.cs
@@ -12,4 +12,4 @@ public record struct CreatorModel(
Socials Socials,
Colors Colors,
Images Images,
- int SubscriberCount);
+ int FollowerCount);
diff --git a/src/Web/Features/Contents/Handlers/Models/SubscriptionModel.cs b/src/Web/Features/Contents/Handlers/Models/FollowModel.cs
similarity index 81%
rename from src/Web/Features/Contents/Handlers/Models/SubscriptionModel.cs
rename to src/Web/Features/Contents/Handlers/Models/FollowModel.cs
index 7c748aa..7f722f1 100644
--- a/src/Web/Features/Contents/Handlers/Models/SubscriptionModel.cs
+++ b/src/Web/Features/Contents/Handlers/Models/FollowModel.cs
@@ -1,7 +1,7 @@
namespace Hutopy.Web.Features.Contents.Handlers.Models;
[PublicAPI]
-public record SubscriptionModel(
+public record FollowModel(
Guid CreatorId,
string CreatorName,
string? CreatorPortraitUrl);
diff --git a/src/Web/Features/Contents/Handlers/UnsubscribeFromCreator.cs b/src/Web/Features/Contents/Handlers/UnfollowCreator.cs
similarity index 75%
rename from src/Web/Features/Contents/Handlers/UnsubscribeFromCreator.cs
rename to src/Web/Features/Contents/Handlers/UnfollowCreator.cs
index 69ac37e..a4d9669 100644
--- a/src/Web/Features/Contents/Handlers/UnsubscribeFromCreator.cs
+++ b/src/Web/Features/Contents/Handlers/UnfollowCreator.cs
@@ -16,8 +16,8 @@ public class UnsubscribeFromCreatorHandler(
{
public override void Configure()
{
- Post("/api/creators/{CreatorId}/unsubscribe");
- Options((o => o.WithTags("Subscriptions")));
+ Post("/api/creators/{CreatorId}/unfollow");
+ Options((o => o.WithTags("Creators")));
Description(x => x.Accepts("*/*"));
}
@@ -25,14 +25,15 @@ public class UnsubscribeFromCreatorHandler(
UnsubscribeFromCreatorRequest req,
CancellationToken ct)
{
- var subscription = new Subscription { CreatorId = req.CreatorId, CreatedBy = HttpContext.User.GetUserId() };
-
- context.Subscriptions.Attach(subscription);
- context.Subscriptions.Remove(subscription);
-
try
{
+ var subscription = new Follower { CreatorId = req.CreatorId, CreatedBy = HttpContext.User.GetUserId() };
+
+ context.Followers.Attach(subscription);
+ context.Followers.Remove(subscription);
+
await context.SaveChangesAsync(ct);
+
await SendOkAsync(ct);
}
catch (Exception)
diff --git a/src/Web/Features/Contents/Migrations/20241011103653_FromSubscribersToFollowers.Designer.cs b/src/Web/Features/Contents/Migrations/20241011103653_FromSubscribersToFollowers.Designer.cs
new file mode 100644
index 0000000..4d4fd2b
--- /dev/null
+++ b/src/Web/Features/Contents/Migrations/20241011103653_FromSubscribersToFollowers.Designer.cs
@@ -0,0 +1,310 @@
+//
+using System;
+using Hutopy.Web.Features.Contents.Data;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+
+#nullable disable
+
+namespace Hutopy.Web.Features.Contents.Migrations
+{
+ [DbContext(typeof(ContentDbContext))]
+ [Migration("20241011103653_FromSubscribersToFollowers")]
+ partial class FromSubscribersToFollowers
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasDefaultSchema("Content")
+ .HasAnnotation("ProductVersion", "8.0.4")
+ .HasAnnotation("Relational:MaxIdentifierLength", 63);
+
+ NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
+
+ modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.Content", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("CreatedAt")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("timestamp with time zone")
+ .HasDefaultValueSql("CURRENT_TIMESTAMP");
+
+ b.Property("CreatedBy")
+ .HasColumnType("uuid");
+
+ b.Property("DeletedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("DeletedBy")
+ .HasColumnType("uuid");
+
+ b.Property("Description")
+ .IsRequired()
+ .HasMaxLength(2048)
+ .HasColumnType("character varying(2048)");
+
+ b.Property("Title")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("character varying(128)");
+
+ b.Property("Urls")
+ .HasColumnType("text[]");
+
+ b.HasKey("Id");
+
+ b.HasIndex("CreatedBy");
+
+ b.ToTable("Contents", "Content");
+ });
+
+ modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.Creator", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("CreatedBy")
+ .HasColumnType("uuid");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(255)
+ .HasColumnType("character varying(255)");
+
+ b.Property("Title")
+ .HasMaxLength(255)
+ .HasColumnType("character varying(255)");
+
+ b.HasKey("Id");
+
+ b.ToTable("Creators", "Content");
+ });
+
+ modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.Follower", b =>
+ {
+ b.Property("CreatedBy")
+ .HasColumnType("uuid");
+
+ b.Property("CreatorId")
+ .HasColumnType("uuid");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.HasKey("CreatedBy", "CreatorId");
+
+ b.HasIndex("CreatorId");
+
+ b.ToTable("Followers", "Content");
+ });
+
+ modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.Content", b =>
+ {
+ b.HasOne("Hutopy.Web.Features.Contents.Data.Creator", "Creator")
+ .WithMany()
+ .HasForeignKey("CreatedBy")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.OwnsMany("Hutopy.Web.Features.Contents.Data.ContentReaction", "Reactions", b1 =>
+ {
+ b1.Property("ContentId")
+ .HasColumnType("uuid");
+
+ b1.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b1.Property("Id"));
+
+ b1.Property("Reaction")
+ .HasColumnType("integer");
+
+ b1.Property("UserId")
+ .HasColumnType("uuid");
+
+ b1.Property("UserName")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("character varying(128)");
+
+ b1.HasKey("ContentId", "Id");
+
+ b1.ToTable("ContentReactions", "Content");
+
+ b1.WithOwner()
+ .HasForeignKey("ContentId");
+ });
+
+ b.Navigation("Creator");
+
+ b.Navigation("Reactions");
+ });
+
+ modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.Creator", b =>
+ {
+ b.OwnsOne("Hutopy.Web.Features.Contents.Data.Colors", "Colors", b1 =>
+ {
+ b1.Property("CreatorId")
+ .HasColumnType("uuid");
+
+ b1.Property("Background")
+ .IsRequired()
+ .HasMaxLength(9)
+ .HasColumnType("character varying(9)");
+
+ b1.Property("Error")
+ .IsRequired()
+ .HasMaxLength(9)
+ .HasColumnType("character varying(9)");
+
+ b1.Property("OnBackground")
+ .IsRequired()
+ .HasMaxLength(9)
+ .HasColumnType("character varying(9)");
+
+ b1.Property("OnError")
+ .IsRequired()
+ .HasMaxLength(9)
+ .HasColumnType("character varying(9)");
+
+ b1.Property("OnPrimary")
+ .IsRequired()
+ .HasMaxLength(9)
+ .HasColumnType("character varying(9)");
+
+ b1.Property("OnSecondary")
+ .IsRequired()
+ .HasMaxLength(9)
+ .HasColumnType("character varying(9)");
+
+ b1.Property("OnSurface")
+ .IsRequired()
+ .HasMaxLength(9)
+ .HasColumnType("character varying(9)");
+
+ b1.Property("Primary")
+ .IsRequired()
+ .HasMaxLength(9)
+ .HasColumnType("character varying(9)");
+
+ b1.Property("Secondary")
+ .IsRequired()
+ .HasMaxLength(9)
+ .HasColumnType("character varying(9)");
+
+ b1.Property("Surface")
+ .IsRequired()
+ .HasMaxLength(9)
+ .HasColumnType("character varying(9)");
+
+ b1.HasKey("CreatorId");
+
+ b1.ToTable("Colors", "Content");
+
+ b1.WithOwner()
+ .HasForeignKey("CreatorId");
+ });
+
+ b.OwnsOne("Hutopy.Web.Features.Contents.Data.Images", "Images", b1 =>
+ {
+ b1.Property("CreatorId")
+ .HasColumnType("uuid");
+
+ b1.Property("Banner")
+ .HasMaxLength(255)
+ .HasColumnType("character varying(255)");
+
+ b1.Property("Logo")
+ .HasMaxLength(255)
+ .HasColumnType("character varying(255)");
+
+ b1.HasKey("CreatorId");
+
+ b1.ToTable("Images", "Content");
+
+ b1.WithOwner()
+ .HasForeignKey("CreatorId");
+ });
+
+ b.OwnsOne("Hutopy.Web.Features.Contents.Data.Socials", "Socials", b1 =>
+ {
+ b1.Property("CreatorId")
+ .HasColumnType("uuid");
+
+ b1.Property("FacebookUrl")
+ .HasMaxLength(255)
+ .HasColumnType("character varying(255)");
+
+ b1.Property("InstagramUrl")
+ .HasMaxLength(255)
+ .HasColumnType("character varying(255)");
+
+ b1.Property("LinkedInUrl")
+ .HasMaxLength(255)
+ .HasColumnType("character varying(255)");
+
+ b1.Property("RedditUrl")
+ .HasMaxLength(255)
+ .HasColumnType("character varying(255)");
+
+ b1.Property("TikTokUrl")
+ .HasMaxLength(255)
+ .HasColumnType("character varying(255)");
+
+ b1.Property("WebsiteUrl")
+ .HasMaxLength(255)
+ .HasColumnType("character varying(255)");
+
+ b1.Property("XUrl")
+ .HasMaxLength(255)
+ .HasColumnType("character varying(255)");
+
+ b1.Property("YoutubeUrl")
+ .HasMaxLength(255)
+ .HasColumnType("character varying(255)");
+
+ b1.HasKey("CreatorId");
+
+ b1.ToTable("Socials", "Content");
+
+ b1.WithOwner()
+ .HasForeignKey("CreatorId");
+ });
+
+ b.Navigation("Colors")
+ .IsRequired();
+
+ b.Navigation("Images")
+ .IsRequired();
+
+ b.Navigation("Socials")
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.Follower", b =>
+ {
+ b.HasOne("Hutopy.Web.Features.Contents.Data.Creator", "Creator")
+ .WithMany()
+ .HasForeignKey("CreatorId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Creator");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/src/Web/Features/Contents/Migrations/20241011103653_FromSubscribersToFollowers.cs b/src/Web/Features/Contents/Migrations/20241011103653_FromSubscribersToFollowers.cs
new file mode 100644
index 0000000..8f41082
--- /dev/null
+++ b/src/Web/Features/Contents/Migrations/20241011103653_FromSubscribersToFollowers.cs
@@ -0,0 +1,81 @@
+using System;
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace Hutopy.Web.Features.Contents.Migrations
+{
+ ///
+ public partial class FromSubscribersToFollowers : Migration
+ {
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropTable(
+ name: "Subscriptions",
+ schema: "Content");
+
+ migrationBuilder.CreateTable(
+ name: "Followers",
+ schema: "Content",
+ columns: table => new
+ {
+ CreatedBy = table.Column(type: "uuid", nullable: false),
+ CreatorId = table.Column(type: "uuid", nullable: false),
+ CreatedAt = table.Column(type: "timestamp with time zone", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_Followers", x => new { x.CreatedBy, x.CreatorId });
+ table.ForeignKey(
+ name: "FK_Followers_Creators_CreatorId",
+ column: x => x.CreatorId,
+ principalSchema: "Content",
+ principalTable: "Creators",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateIndex(
+ name: "IX_Followers_CreatorId",
+ schema: "Content",
+ table: "Followers",
+ column: "CreatorId");
+ }
+
+ ///
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropTable(
+ name: "Followers",
+ schema: "Content");
+
+ migrationBuilder.CreateTable(
+ name: "Subscriptions",
+ schema: "Content",
+ columns: table => new
+ {
+ CreatedBy = table.Column(type: "uuid", nullable: false),
+ CreatorId = table.Column(type: "uuid", nullable: false),
+ CreatedAt = table.Column(type: "timestamp with time zone", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_Subscriptions", x => new { x.CreatedBy, x.CreatorId });
+ table.ForeignKey(
+ name: "FK_Subscriptions_Creators_CreatorId",
+ column: x => x.CreatorId,
+ principalSchema: "Content",
+ principalTable: "Creators",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateIndex(
+ name: "IX_Subscriptions_CreatorId",
+ schema: "Content",
+ table: "Subscriptions",
+ column: "CreatorId");
+ }
+ }
+}
diff --git a/src/Web/Features/Contents/Migrations/ContentDbContextModelSnapshot.cs b/src/Web/Features/Contents/Migrations/ContentDbContextModelSnapshot.cs
index c38d421..2f630c6 100644
--- a/src/Web/Features/Contents/Migrations/ContentDbContextModelSnapshot.cs
+++ b/src/Web/Features/Contents/Migrations/ContentDbContextModelSnapshot.cs
@@ -92,7 +92,7 @@ namespace Hutopy.Web.Features.Contents.Migrations
b.ToTable("Creators", "Content");
});
- modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.Subscription", b =>
+ modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.Follower", b =>
{
b.Property("CreatedBy")
.HasColumnType("uuid");
@@ -107,7 +107,7 @@ namespace Hutopy.Web.Features.Contents.Migrations
b.HasIndex("CreatorId");
- b.ToTable("Subscriptions", "Content");
+ b.ToTable("Followers", "Content");
});
modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.Content", b =>
@@ -294,7 +294,7 @@ namespace Hutopy.Web.Features.Contents.Migrations
.IsRequired();
});
- modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.Subscription", b =>
+ modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.Follower", b =>
{
b.HasOne("Hutopy.Web.Features.Contents.Data.Creator", "Creator")
.WithMany()
diff --git a/src/Web/Features/Memberships/Data/Creator.cs b/src/Web/Features/Memberships/Data/Creator.cs
new file mode 100644
index 0000000..72ebd31
--- /dev/null
+++ b/src/Web/Features/Memberships/Data/Creator.cs
@@ -0,0 +1,8 @@
+namespace Hutopy.Web.Features.Memberships.Data;
+
+public class Creator
+{
+ public Guid Id { get; set; }
+ public string Name { get; set; }
+ public string StripeAccountId { get; set; }
+}
diff --git a/src/Web/Features/Memberships/Data/MembershipDbContext.cs b/src/Web/Features/Memberships/Data/MembershipDbContext.cs
new file mode 100644
index 0000000..f0dcd47
--- /dev/null
+++ b/src/Web/Features/Memberships/Data/MembershipDbContext.cs
@@ -0,0 +1,58 @@
+namespace Hutopy.Web.Features.Memberships.Data;
+
+public sealed class MembershipDbContext(
+ DbContextOptions