From b63d53f109ddbf431309b2818ebf6e166bd0102d Mon Sep 17 00:00:00 2001 From: Dominic Villemure Date: Mon, 22 Apr 2024 15:55:49 -0400 Subject: [PATCH] #27 added userTransactions --- .../Interfaces/IApplicationDbContext.cs | 1 + .../Commands/CreateSessionCheckoutCommand.cs | 24 +- .../Users/Queries/GetCurrentUser.cs | 37 ++ src/Application/Users/Queries/UserDto.cs | 12 + .../Users/Queries/UserTransactionDto.cs | 20 + src/Domain/Entities/UserTransaction.cs | 11 + src/Domain/Interfaces/IUserService.cs | 1 + .../Data/ApplicationDbContext.cs | 9 + src/Infrastructure/Infrastructure.csproj | 3 - ...0422173330_AddUserTransactions.Designer.cs | 383 ++++++++++++++++++ .../20240422173330_AddUserTransactions.cs | 52 +++ .../ApplicationDbContextModelSnapshot.cs | 49 +++ src/Infrastructure/Services/UserService.cs | 26 +- src/Infrastructure/Stripe/StripeService.cs | 12 +- src/Web/Endpoints/GetMyUser.cs | 17 + src/Web/Endpoints/Users.cs | 2 +- src/Web/wwwroot/api/specification.json | 65 ++- 17 files changed, 696 insertions(+), 28 deletions(-) create mode 100644 src/Application/Users/Queries/GetCurrentUser.cs create mode 100644 src/Application/Users/Queries/UserDto.cs create mode 100644 src/Application/Users/Queries/UserTransactionDto.cs create mode 100644 src/Domain/Entities/UserTransaction.cs create mode 100644 src/Infrastructure/Migrations/20240422173330_AddUserTransactions.Designer.cs create mode 100644 src/Infrastructure/Migrations/20240422173330_AddUserTransactions.cs create mode 100644 src/Web/Endpoints/GetMyUser.cs diff --git a/src/Application/Common/Interfaces/IApplicationDbContext.cs b/src/Application/Common/Interfaces/IApplicationDbContext.cs index 3615321..75eabb6 100644 --- a/src/Application/Common/Interfaces/IApplicationDbContext.cs +++ b/src/Application/Common/Interfaces/IApplicationDbContext.cs @@ -5,5 +5,6 @@ namespace Hutopy.Application.Common.Interfaces; public interface IApplicationDbContext { DbSet FutureCreators { get; } + DbSet UserTransactions { get; } Task SaveChangesAsync(CancellationToken cancellationToken); } diff --git a/src/Application/Stripe/Commands/CreateSessionCheckoutCommand.cs b/src/Application/Stripe/Commands/CreateSessionCheckoutCommand.cs index 878a2d1..e627bf1 100644 --- a/src/Application/Stripe/Commands/CreateSessionCheckoutCommand.cs +++ b/src/Application/Stripe/Commands/CreateSessionCheckoutCommand.cs @@ -1,25 +1,31 @@ using Hutopy.Application.Common.Interfaces; - +using Hutopy.Domain.Entities; namespace Hutopy.Application.Stripe.Commands; -public abstract record CreateSessionCheckoutCommand : IRequest +public record CreateSessionCheckoutCommand : IRequest { + public required string CreatorId { get; init; } public required int Price { get; init; } - - public string Currency { get; init; } = "cad"; + public string Currency { get; init; } = "CAD"; + public string TipMessage { get; init; } = string.Empty; } - public class CreateSessionCheckoutCommandHandler( - IApplicationDbContext context, - IStripeService stripeService) + IApplicationDbContext dbContext, + IStripeService stripeService + ) : IRequestHandler { - private readonly IApplicationDbContext _context = context; - public async Task Handle(CreateSessionCheckoutCommand request, CancellationToken cancellationToken) { var stripeSecret = await stripeService.CreateCheckoutSession(request.Price, request.Currency); + + var userTransaction = new UserTransaction + { + Currency = request.Currency, Amount = request.Price, TipMessage = request.TipMessage, ApplicationUserId = request.CreatorId + }; + + await dbContext.UserTransactions.AddAsync(userTransaction, cancellationToken); return stripeSecret; } diff --git a/src/Application/Users/Queries/GetCurrentUser.cs b/src/Application/Users/Queries/GetCurrentUser.cs new file mode 100644 index 0000000..a087cf8 --- /dev/null +++ b/src/Application/Users/Queries/GetCurrentUser.cs @@ -0,0 +1,37 @@ +using Hutopy.Application.Common.Interfaces; +using Hutopy.Domain.Interfaces; + +namespace Hutopy.Application.Users.Queries; + +public record GetCurrentUserQuery : IRequest; + +public class GetCurrentUserQueryHandler( + IApplicationDbContext context, + IMapper mapper, + IUserService userService + ) + : IRequestHandler +{ + public async Task Handle(GetCurrentUserQuery request, CancellationToken cancellationToken) + { + var identityUser = await userService.GetCurrentUserAsync(); + var currentUserId = new Guid(identityUser?.Id ?? ""); + + var transactions = await context.UserTransactions + .Where(x => x.Id == currentUserId) + .OrderBy(x => x.LastModified) + .ProjectTo(mapper.ConfigurationProvider) + .ToListAsync(cancellationToken); + + + var user = new UserDto() + { + Id = currentUserId, + FirstName = identityUser?.FirstName ?? "", + LastName = identityUser?.LastName ?? "", + UserTransactions = transactions + }; + + return user; + } +} diff --git a/src/Application/Users/Queries/UserDto.cs b/src/Application/Users/Queries/UserDto.cs new file mode 100644 index 0000000..da62e26 --- /dev/null +++ b/src/Application/Users/Queries/UserDto.cs @@ -0,0 +1,12 @@ +namespace Hutopy.Application.Users.Queries; + +public class UserDto +{ + public Guid Id { get; init; } + + public required string FirstName { get; init; } + + public required string LastName { get; init; } + + public List UserTransactions { get; init; } = []; +} diff --git a/src/Application/Users/Queries/UserTransactionDto.cs b/src/Application/Users/Queries/UserTransactionDto.cs new file mode 100644 index 0000000..e6103c7 --- /dev/null +++ b/src/Application/Users/Queries/UserTransactionDto.cs @@ -0,0 +1,20 @@ +using Hutopy.Domain.Entities; + +namespace Hutopy.Application.Users.Queries; + +public class UserTransactionDto +{ + public required decimal Amount { get; init; } + + public string Currency { get; init; } = "cad"; + + public string TipMessage { get; init; } = string.Empty; + + private class Mapping : Profile + { + public Mapping() + { + CreateMap(); + } + } +} diff --git a/src/Domain/Entities/UserTransaction.cs b/src/Domain/Entities/UserTransaction.cs new file mode 100644 index 0000000..4617c46 --- /dev/null +++ b/src/Domain/Entities/UserTransaction.cs @@ -0,0 +1,11 @@ +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 string ApplicationUserId { get; set; } = string.Empty; +} diff --git a/src/Domain/Interfaces/IUserService.cs b/src/Domain/Interfaces/IUserService.cs index c3bd5ac..efe46bb 100644 --- a/src/Domain/Interfaces/IUserService.cs +++ b/src/Domain/Interfaces/IUserService.cs @@ -7,6 +7,7 @@ public interface IUserService Task CreateUserAsync(string email, string userName, string firstName, string lastName, string password); Task FindUserByIdAsync(string id); + Task GetCurrentUserAsync(); Task FindUserByEmailAsync(string id); } diff --git a/src/Infrastructure/Data/ApplicationDbContext.cs b/src/Infrastructure/Data/ApplicationDbContext.cs index 1dd4d94..8ae973e 100644 --- a/src/Infrastructure/Data/ApplicationDbContext.cs +++ b/src/Infrastructure/Data/ApplicationDbContext.cs @@ -12,10 +12,19 @@ public class ApplicationDbContext( : IdentityDbContext(options), IApplicationDbContext { public DbSet FutureCreators => Set(); + public DbSet UserTransactions => Set(); protected override void OnModelCreating(ModelBuilder builder) { base.OnModelCreating(builder); + + // Relationship between ApplicationUser and UserTransaction + builder.Entity() + .HasOne() + .WithMany() + .HasForeignKey(ut => ut.ApplicationUserId) + .IsRequired(); + builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); } } diff --git a/src/Infrastructure/Infrastructure.csproj b/src/Infrastructure/Infrastructure.csproj index 2a665da..2958bd6 100644 --- a/src/Infrastructure/Infrastructure.csproj +++ b/src/Infrastructure/Infrastructure.csproj @@ -19,7 +19,4 @@ - - - diff --git a/src/Infrastructure/Migrations/20240422173330_AddUserTransactions.Designer.cs b/src/Infrastructure/Migrations/20240422173330_AddUserTransactions.Designer.cs new file mode 100644 index 0000000..cc634ad --- /dev/null +++ b/src/Infrastructure/Migrations/20240422173330_AddUserTransactions.Designer.cs @@ -0,0 +1,383 @@ +// +using System; +using Hutopy.Infrastructure.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Hutopy.Infrastructure.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20240422173330_AddUserTransactions")] + partial class AddUserTransactions + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.3") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Hutopy.Domain.Entities.FutureCreator", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Created") + .HasColumnType("datetimeoffset"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("EmailAddress") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("FirstName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("LastModified") + .HasColumnType("datetimeoffset"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("LastName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ReasonToJoin") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("SocialNetworkAccount") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("FutureCreators"); + }); + + modelBuilder.Entity("Hutopy.Domain.Entities.UserTransaction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Amount") + .HasColumnType("decimal(18,2)"); + + b.Property("ApplicationUserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("Created") + .HasColumnType("datetimeoffset"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Currency") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("LastModified") + .HasColumnType("datetimeoffset"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("TipMessage") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationUserId"); + + b.ToTable("UserTransactions"); + }); + + modelBuilder.Entity("Hutopy.Infrastructure.Identity.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("FirstName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("LastName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("Hutopy.Domain.Entities.UserTransaction", b => + { + b.HasOne("Hutopy.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("ApplicationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Hutopy.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Hutopy.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Hutopy.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Hutopy.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Infrastructure/Migrations/20240422173330_AddUserTransactions.cs b/src/Infrastructure/Migrations/20240422173330_AddUserTransactions.cs new file mode 100644 index 0000000..f60e3fc --- /dev/null +++ b/src/Infrastructure/Migrations/20240422173330_AddUserTransactions.cs @@ -0,0 +1,52 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Hutopy.Infrastructure.Migrations +{ + /// + public partial class AddUserTransactions : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "UserTransactions", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + Amount = table.Column(type: "decimal(18,2)", nullable: false), + Currency = table.Column(type: "nvarchar(max)", nullable: false), + TipMessage = table.Column(type: "nvarchar(max)", nullable: false), + ApplicationUserId = table.Column(type: "nvarchar(450)", nullable: false), + Created = table.Column(type: "datetimeoffset", nullable: false), + CreatedBy = table.Column(type: "nvarchar(max)", nullable: true), + LastModified = table.Column(type: "datetimeoffset", nullable: false), + LastModifiedBy = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_UserTransactions", x => x.Id); + table.ForeignKey( + name: "FK_UserTransactions_AspNetUsers_ApplicationUserId", + column: x => x.ApplicationUserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_UserTransactions_ApplicationUserId", + table: "UserTransactions", + column: "ApplicationUserId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "UserTransactions"); + } + } +} diff --git a/src/Infrastructure/Migrations/ApplicationDbContextModelSnapshot.cs b/src/Infrastructure/Migrations/ApplicationDbContextModelSnapshot.cs index 33d2e9d..157cea1 100644 --- a/src/Infrastructure/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/src/Infrastructure/Migrations/ApplicationDbContextModelSnapshot.cs @@ -69,6 +69,46 @@ namespace Hutopy.Infrastructure.Migrations b.ToTable("FutureCreators"); }); + modelBuilder.Entity("Hutopy.Domain.Entities.UserTransaction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Amount") + .HasColumnType("decimal(18,2)"); + + b.Property("ApplicationUserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("Created") + .HasColumnType("datetimeoffset"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Currency") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("LastModified") + .HasColumnType("datetimeoffset"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("TipMessage") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationUserId"); + + b.ToTable("UserTransactions"); + }); + modelBuilder.Entity("Hutopy.Infrastructure.Identity.ApplicationUser", b => { b.Property("Id") @@ -275,6 +315,15 @@ namespace Hutopy.Infrastructure.Migrations b.ToTable("AspNetUserTokens", (string)null); }); + modelBuilder.Entity("Hutopy.Domain.Entities.UserTransaction", b => + { + b.HasOne("Hutopy.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("ApplicationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => { b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) diff --git a/src/Infrastructure/Services/UserService.cs b/src/Infrastructure/Services/UserService.cs index 98c0c7c..7fae66c 100644 --- a/src/Infrastructure/Services/UserService.cs +++ b/src/Infrastructure/Services/UserService.cs @@ -1,15 +1,14 @@ -using Hutopy.Domain.Interfaces; +using System.Security.Claims; +using Hutopy.Domain.Interfaces; using Hutopy.Domain.Models; using Hutopy.Infrastructure.Identity; -using Microsoft.AspNetCore.Http.HttpResults; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Identity; namespace Hutopy.Infrastructure.Services; -public class UserService(UserManager userManager) : IUserService +public class UserService(UserManager userManager, IHttpContextAccessor httpContextAccessor) : IUserService { - private readonly UserManager _userManager = userManager; - public async Task CreateUserAsync(string email, string userName, string firstName, string lastName, string password) { var applicationUser = new ApplicationUser @@ -21,7 +20,7 @@ public class UserService(UserManager userManager) : IUserServic }; //todo: Need to handle errors better for the user. - var response = await _userManager.CreateAsync(applicationUser, password); + var response = await userManager.CreateAsync(applicationUser, password); if (response.Errors.Any()) { @@ -31,7 +30,7 @@ public class UserService(UserManager userManager) : IUserServic public async Task FindUserByIdAsync(string id) { - var response = await _userManager.FindByIdAsync(id); + var response = await userManager.FindByIdAsync(id); if (response == null) return null; @@ -46,10 +45,21 @@ public class UserService(UserManager userManager) : IUserServic return userModel; } + + public async Task GetCurrentUserAsync() + { + var userId = httpContextAccessor.HttpContext?.User.FindFirstValue(ClaimTypes.NameIdentifier); + if (string.IsNullOrEmpty(userId)) + { + return null; + } + + return await FindUserByIdAsync(userId); + } public async Task FindUserByEmailAsync(string email) { - var response = await _userManager.FindByEmailAsync(email); + var response = await userManager.FindByEmailAsync(email); if (response == null) return null; diff --git a/src/Infrastructure/Stripe/StripeService.cs b/src/Infrastructure/Stripe/StripeService.cs index f54fda4..154c541 100644 --- a/src/Infrastructure/Stripe/StripeService.cs +++ b/src/Infrastructure/Stripe/StripeService.cs @@ -1,16 +1,16 @@ using Stripe; using Stripe.Checkout; using Hutopy.Application.Common.Interfaces; +using Microsoft.Extensions.Configuration; namespace Hutopy.Infrastructure.Stripe; public class StripeService : IStripeService { - public StripeService() - { - // I removed the key to push. Will need to be in config. - StripeConfiguration.ApiKey = ""; - } + public StripeService(IConfiguration configuration) + { + StripeConfiguration.ApiKey = configuration["STRIPE_API_KEY"]; + } public async Task CreateCheckoutSession(int price, string currency = "cad") { @@ -32,7 +32,7 @@ public class StripeService : IStripeService ], Mode = "payment", UiMode = "embedded", - ReturnUrl = $"http://localhost:5173/creatorfolio", + ReturnUrl = $"https://zealous-bay-08204590f.5.azurestaticapps.net/paymentcompleted", }; var service = new SessionService(); diff --git a/src/Web/Endpoints/GetMyUser.cs b/src/Web/Endpoints/GetMyUser.cs new file mode 100644 index 0000000..5fe1c79 --- /dev/null +++ b/src/Web/Endpoints/GetMyUser.cs @@ -0,0 +1,17 @@ +using Hutopy.Application.Users.Queries; + +namespace Hutopy.Web.Endpoints; + +public class GetMyUser : EndpointGroupBase +{ + public override void Map(WebApplication app) + { + app.MapGroup(this) + .MapGet(GetCurrentUser); + } + + private static async Task GetCurrentUser(ISender sender, [AsParameters] GetCurrentUserQuery query) + { + return await sender.Send(query); + } +} diff --git a/src/Web/Endpoints/Users.cs b/src/Web/Endpoints/Users.cs index 38ccb1e..0492fb5 100644 --- a/src/Web/Endpoints/Users.cs +++ b/src/Web/Endpoints/Users.cs @@ -13,7 +13,7 @@ public class Users : EndpointGroupBase .MapIdentityApi(); } - public async Task CreateUser(ISender sender, CreateUserCommand command, IUserService userService) + private static async Task CreateUser(ISender sender, CreateUserCommand command, IUserService userService) { await userService.CreateUserAsync(command.EmailAddress, command.UserName, command.FirstName, command.LastName, command.Password); return await sender.Send(command); diff --git a/src/Web/wwwroot/api/specification.json b/src/Web/wwwroot/api/specification.json index 944608b..5e5b6e3 100644 --- a/src/Web/wwwroot/api/specification.json +++ b/src/Web/wwwroot/api/specification.json @@ -6,6 +6,26 @@ "version": "1.0.0" }, "paths": { + "/api/GetMyUser": { + "get": { + "tags": [ + "GetMyUser" + ], + "operationId": "GetCurrentUser", + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserDto" + } + } + } + } + } + } + }, "/api/JoinUs": { "get": { "tags": [ @@ -559,6 +579,44 @@ }, "components": { "schemas": { + "UserDto": { + "type": "object", + "additionalProperties": false, + "properties": { + "id": { + "type": "string", + "format": "guid" + }, + "firstName": { + "type": "string" + }, + "lastName": { + "type": "string" + }, + "userTransactions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/UserTransactionDto" + } + } + } + }, + "UserTransactionDto": { + "type": "object", + "additionalProperties": false, + "properties": { + "amount": { + "type": "number", + "format": "decimal" + }, + "currency": { + "type": "string" + }, + "tipMessage": { + "type": "string" + } + } + }, "PaginatedListOfFutureCreatorListDto": { "type": "object", "additionalProperties": false, @@ -631,15 +689,20 @@ }, "CreateSessionCheckoutCommand": { "type": "object", - "x-abstract": true, "additionalProperties": false, "properties": { + "creatorId": { + "type": "string" + }, "price": { "type": "integer", "format": "int32" }, "currency": { "type": "string" + }, + "tipMessage": { + "type": "string" } } },