Merge branch 'master' into feature/oauth

This commit is contained in:
Kamigen
2024-04-28 19:19:28 -04:00
23 changed files with 1185 additions and 53 deletions

View File

@@ -5,5 +5,6 @@ namespace Hutopy.Application.Common.Interfaces;
public interface IApplicationDbContext public interface IApplicationDbContext
{ {
DbSet<FutureCreator> FutureCreators { get; } DbSet<FutureCreator> FutureCreators { get; }
DbSet<UserTransaction> UserTransactions { get; }
Task<int> SaveChangesAsync(CancellationToken cancellationToken); Task<int> SaveChangesAsync(CancellationToken cancellationToken);
} }

View File

@@ -3,5 +3,5 @@ namespace Hutopy.Application.Common.Interfaces;
public interface IStripeService public interface IStripeService
{ {
public Task<string> CreateCheckoutSession(int price, string currency); public Task<string> CreateCheckoutSession(int amount, string currency);
} }

View File

@@ -0,0 +1,24 @@
using Hutopy.Application.Common.Interfaces;
namespace Hutopy.Application.Stripe.Commands;
public record ConfirmStripeTransactionCommand : IRequest<string>
{
public required Guid UserTransactionId { get; init; }
public required bool IsConfirmed { get; init; }
}
public class ConfirmStripeTransactionCommandHandler(
IApplicationDbContext dbContext
)
: IRequestHandler<ConfirmStripeTransactionCommand, string>
{
public async Task<string> Handle(ConfirmStripeTransactionCommand request, CancellationToken cancellationToken)
{
var transaction = await dbContext.UserTransactions.FirstOrDefaultAsync(x => x.Id == request.UserTransactionId, cancellationToken);
if (transaction is null) return "";
transaction.IsConfirmed = request.IsConfirmed;
dbContext.UserTransactions.Update(transaction);
return transaction.Id.ToString();
}
}

View File

@@ -1,25 +1,38 @@
using Hutopy.Application.Common.Interfaces; using Hutopy.Application.Common.Interfaces;
using Hutopy.Domain.Entities;
namespace Hutopy.Application.Stripe.Commands; namespace Hutopy.Application.Stripe.Commands;
public abstract record CreateSessionCheckoutCommand : IRequest<string> public record CreateSessionCheckoutCommand : IRequest<string>
{ {
public required int Price { get; init; } public required string CreatorId { get; init; }
public required int Amount { get; init; }
public string Currency { get; init; } = "cad"; public string Currency { get; init; } = "CAD";
public string TipMessage { get; init; } = string.Empty;
} }
public class CreateSessionCheckoutCommandHandler( public class CreateSessionCheckoutCommandHandler(
IApplicationDbContext context, IApplicationDbContext dbContext,
IStripeService stripeService) IStripeService stripeService
)
: IRequestHandler<CreateSessionCheckoutCommand, string> : IRequestHandler<CreateSessionCheckoutCommand, string>
{ {
private readonly IApplicationDbContext _context = context;
public async Task<string> Handle(CreateSessionCheckoutCommand request, CancellationToken cancellationToken) public async Task<string> Handle(CreateSessionCheckoutCommand request, CancellationToken cancellationToken)
{ {
var stripeSecret = await stripeService.CreateCheckoutSession(request.Price, request.Currency); var stripeSecret = await stripeService.CreateCheckoutSession(request.Amount, request.Currency);
// ReSharper disable once PossibleLossOfFraction
decimal priceInDollars = (request.Amount / 100);
//todo: Need to add this transaction after the confirmation from stripe in the frontEnd ( redirect, re-call backend )
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; return stripeSecret;
} }

View File

@@ -0,0 +1,37 @@
using Hutopy.Application.Common.Interfaces;
using Hutopy.Domain.Interfaces;
namespace Hutopy.Application.Users.Queries;
public record GetCurrentUserQuery : IRequest<UserDto>;
public class GetCurrentUserQueryHandler(
IApplicationDbContext context,
IMapper mapper,
IUserService userService
)
: IRequestHandler<GetCurrentUserQuery, UserDto>
{
public async Task<UserDto> 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<UserTransactionDto>(mapper.ConfigurationProvider)
.ToListAsync(cancellationToken);
var user = new UserDto()
{
Id = currentUserId,
FirstName = identityUser?.FirstName ?? "",
LastName = identityUser?.LastName ?? "",
UserTransactions = transactions
};
return user;
}
}

View File

@@ -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<UserTransactionDto> UserTransactions { get; init; } = [];
}

View File

@@ -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<UserTransaction, UserTransactionDto>();
}
}
}

View File

@@ -0,0 +1,12 @@
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;
public bool IsConfirmed { get; set; } = false;
}

View File

@@ -10,6 +10,7 @@ public interface IUserService
Task CreateUserAsync(Userinfo userInfo); Task CreateUserAsync(Userinfo userInfo);
Task<UserModel?> FindUserByIdAsync(string id); Task<UserModel?> FindUserByIdAsync(string id);
Task<UserModel?> GetCurrentUserAsync();
Task<UserModel?> FindUserByEmailAsync(string id); Task<UserModel?> FindUserByEmailAsync(string id);
} }

View File

@@ -12,10 +12,21 @@ public class ApplicationDbContext(
: IdentityDbContext<ApplicationUser>(options), IApplicationDbContext : IdentityDbContext<ApplicationUser>(options), IApplicationDbContext
{ {
public DbSet<FutureCreator> FutureCreators => Set<FutureCreator>(); public DbSet<FutureCreator> FutureCreators => Set<FutureCreator>();
public DbSet<UserTransaction> UserTransactions => Set<UserTransaction>();
protected override void OnModelCreating(ModelBuilder builder) protected override void OnModelCreating(ModelBuilder builder)
{ {
base.OnModelCreating(builder); base.OnModelCreating(builder);
// Relationship between ApplicationUser and UserTransaction
builder.Entity<UserTransaction>()
.HasOne<ApplicationUser>()
.WithMany()
.HasForeignKey(ut => ut.ApplicationUserId)
.IsRequired();
builder.Entity<UserTransaction>().Property(x => x.Amount).HasPrecision(18, 2);
builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly());
} }
} }

View File

@@ -26,7 +26,7 @@ public static class DependencyInjection
var dbPassword = configuration["DB_PASSWORD"] ?? ""; var dbPassword = configuration["DB_PASSWORD"] ?? "";
var dbHost = configuration["DB_HOST"] ?? "localhost"; var dbHost = configuration["DB_HOST"] ?? "localhost";
if (dbHost == "localhost" && dbPassword != string.Empty) if (dbPassword != string.Empty)
{ {
connectionString = connectionString.Replace("{DB_PASSWORD}", dbPassword); connectionString = connectionString.Replace("{DB_PASSWORD}", dbPassword);
connectionString = connectionString.Replace("{DB_HOST}", dbHost); connectionString = connectionString.Replace("{DB_HOST}", dbHost);

View File

@@ -19,7 +19,4 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Application\Application.csproj" /> <ProjectReference Include="..\Application\Application.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Folder Include="Data\Migrations\" />
</ItemGroup>
</Project> </Project>

View File

@@ -0,0 +1,383 @@
// <auto-generated />
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
{
/// <inheritdoc />
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<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<DateTimeOffset>("Created")
.HasColumnType("datetimeoffset");
b.Property<string>("CreatedBy")
.HasColumnType("nvarchar(max)");
b.Property<string>("EmailAddress")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("FirstName")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<DateTimeOffset>("LastModified")
.HasColumnType("datetimeoffset");
b.Property<string>("LastModifiedBy")
.HasColumnType("nvarchar(max)");
b.Property<string>("LastName")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("PhoneNumber")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("ReasonToJoin")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("SocialNetworkAccount")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.ToTable("FutureCreators");
});
modelBuilder.Entity("Hutopy.Domain.Entities.UserTransaction", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<decimal>("Amount")
.HasColumnType("decimal(18,2)");
b.Property<string>("ApplicationUserId")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.Property<DateTimeOffset>("Created")
.HasColumnType("datetimeoffset");
b.Property<string>("CreatedBy")
.HasColumnType("nvarchar(max)");
b.Property<string>("Currency")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<DateTimeOffset>("LastModified")
.HasColumnType("datetimeoffset");
b.Property<string>("LastModifiedBy")
.HasColumnType("nvarchar(max)");
b.Property<string>("TipMessage")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.HasIndex("ApplicationUserId");
b.ToTable("UserTransactions");
});
modelBuilder.Entity("Hutopy.Infrastructure.Identity.ApplicationUser", b =>
{
b.Property<string>("Id")
.HasColumnType("nvarchar(450)");
b.Property<int>("AccessFailedCount")
.HasColumnType("int");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("nvarchar(max)");
b.Property<string>("Email")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<bool>("EmailConfirmed")
.HasColumnType("bit");
b.Property<string>("FirstName")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("LastName")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<bool>("LockoutEnabled")
.HasColumnType("bit");
b.Property<DateTimeOffset?>("LockoutEnd")
.HasColumnType("datetimeoffset");
b.Property<string>("NormalizedEmail")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("NormalizedUserName")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("PasswordHash")
.HasColumnType("nvarchar(max)");
b.Property<string>("PhoneNumber")
.HasColumnType("nvarchar(max)");
b.Property<bool>("PhoneNumberConfirmed")
.HasColumnType("bit");
b.Property<string>("SecurityStamp")
.HasColumnType("nvarchar(max)");
b.Property<bool>("TwoFactorEnabled")
.HasColumnType("bit");
b.Property<string>("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<string>("Id")
.HasColumnType("nvarchar(450)");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("nvarchar(max)");
b.Property<string>("Name")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("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<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("ClaimType")
.HasColumnType("nvarchar(max)");
b.Property<string>("ClaimValue")
.HasColumnType("nvarchar(max)");
b.Property<string>("RoleId")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("ClaimType")
.HasColumnType("nvarchar(max)");
b.Property<string>("ClaimValue")
.HasColumnType("nvarchar(max)");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.Property<string>("LoginProvider")
.HasColumnType("nvarchar(450)");
b.Property<string>("ProviderKey")
.HasColumnType("nvarchar(450)");
b.Property<string>("ProviderDisplayName")
.HasColumnType("nvarchar(max)");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("nvarchar(450)");
b.Property<string>("RoleId")
.HasColumnType("nvarchar(450)");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("nvarchar(450)");
b.Property<string>("LoginProvider")
.HasColumnType("nvarchar(450)");
b.Property<string>("Name")
.HasColumnType("nvarchar(450)");
b.Property<string>("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<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.HasOne("Hutopy.Infrastructure.Identity.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.HasOne("Hutopy.Infrastructure.Identity.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", 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<string>", b =>
{
b.HasOne("Hutopy.Infrastructure.Identity.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -0,0 +1,52 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Hutopy.Infrastructure.Migrations
{
/// <inheritdoc />
public partial class AddUserTransactions : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "UserTransactions",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
Amount = table.Column<decimal>(type: "decimal(18,2)", nullable: false),
Currency = table.Column<string>(type: "nvarchar(max)", nullable: false),
TipMessage = table.Column<string>(type: "nvarchar(max)", nullable: false),
ApplicationUserId = table.Column<string>(type: "nvarchar(450)", nullable: false),
Created = table.Column<DateTimeOffset>(type: "datetimeoffset", nullable: false),
CreatedBy = table.Column<string>(type: "nvarchar(max)", nullable: true),
LastModified = table.Column<DateTimeOffset>(type: "datetimeoffset", nullable: false),
LastModifiedBy = table.Column<string>(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");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "UserTransactions");
}
}
}

View File

@@ -0,0 +1,387 @@
// <auto-generated />
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("20240425020920_AddIsConfirmedToUserTransaction")]
partial class AddIsConfirmedToUserTransaction
{
/// <inheritdoc />
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<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<DateTimeOffset>("Created")
.HasColumnType("datetimeoffset");
b.Property<string>("CreatedBy")
.HasColumnType("nvarchar(max)");
b.Property<string>("EmailAddress")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("FirstName")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<DateTimeOffset>("LastModified")
.HasColumnType("datetimeoffset");
b.Property<string>("LastModifiedBy")
.HasColumnType("nvarchar(max)");
b.Property<string>("LastName")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("PhoneNumber")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("ReasonToJoin")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("SocialNetworkAccount")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.ToTable("FutureCreators");
});
modelBuilder.Entity("Hutopy.Domain.Entities.UserTransaction", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<decimal>("Amount")
.HasPrecision(18, 2)
.HasColumnType("decimal(18,2)");
b.Property<string>("ApplicationUserId")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.Property<DateTimeOffset>("Created")
.HasColumnType("datetimeoffset");
b.Property<string>("CreatedBy")
.HasColumnType("nvarchar(max)");
b.Property<string>("Currency")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<bool>("IsConfirmed")
.HasColumnType("bit");
b.Property<DateTimeOffset>("LastModified")
.HasColumnType("datetimeoffset");
b.Property<string>("LastModifiedBy")
.HasColumnType("nvarchar(max)");
b.Property<string>("TipMessage")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.HasIndex("ApplicationUserId");
b.ToTable("UserTransactions");
});
modelBuilder.Entity("Hutopy.Infrastructure.Identity.ApplicationUser", b =>
{
b.Property<string>("Id")
.HasColumnType("nvarchar(450)");
b.Property<int>("AccessFailedCount")
.HasColumnType("int");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("nvarchar(max)");
b.Property<string>("Email")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<bool>("EmailConfirmed")
.HasColumnType("bit");
b.Property<string>("FirstName")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("LastName")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<bool>("LockoutEnabled")
.HasColumnType("bit");
b.Property<DateTimeOffset?>("LockoutEnd")
.HasColumnType("datetimeoffset");
b.Property<string>("NormalizedEmail")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("NormalizedUserName")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("PasswordHash")
.HasColumnType("nvarchar(max)");
b.Property<string>("PhoneNumber")
.HasColumnType("nvarchar(max)");
b.Property<bool>("PhoneNumberConfirmed")
.HasColumnType("bit");
b.Property<string>("SecurityStamp")
.HasColumnType("nvarchar(max)");
b.Property<bool>("TwoFactorEnabled")
.HasColumnType("bit");
b.Property<string>("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<string>("Id")
.HasColumnType("nvarchar(450)");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("nvarchar(max)");
b.Property<string>("Name")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("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<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("ClaimType")
.HasColumnType("nvarchar(max)");
b.Property<string>("ClaimValue")
.HasColumnType("nvarchar(max)");
b.Property<string>("RoleId")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("ClaimType")
.HasColumnType("nvarchar(max)");
b.Property<string>("ClaimValue")
.HasColumnType("nvarchar(max)");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.Property<string>("LoginProvider")
.HasColumnType("nvarchar(450)");
b.Property<string>("ProviderKey")
.HasColumnType("nvarchar(450)");
b.Property<string>("ProviderDisplayName")
.HasColumnType("nvarchar(max)");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("nvarchar(450)");
b.Property<string>("RoleId")
.HasColumnType("nvarchar(450)");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("nvarchar(450)");
b.Property<string>("LoginProvider")
.HasColumnType("nvarchar(450)");
b.Property<string>("Name")
.HasColumnType("nvarchar(450)");
b.Property<string>("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<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.HasOne("Hutopy.Infrastructure.Identity.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.HasOne("Hutopy.Infrastructure.Identity.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", 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<string>", b =>
{
b.HasOne("Hutopy.Infrastructure.Identity.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -0,0 +1,29 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Hutopy.Infrastructure.Migrations
{
/// <inheritdoc />
public partial class AddIsConfirmedToUserTransaction : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "IsConfirmed",
table: "UserTransactions",
type: "bit",
nullable: false,
defaultValue: false);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "IsConfirmed",
table: "UserTransactions");
}
}
}

View File

@@ -69,6 +69,50 @@ namespace Hutopy.Infrastructure.Migrations
b.ToTable("FutureCreators"); b.ToTable("FutureCreators");
}); });
modelBuilder.Entity("Hutopy.Domain.Entities.UserTransaction", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<decimal>("Amount")
.HasPrecision(18, 2)
.HasColumnType("decimal(18,2)");
b.Property<string>("ApplicationUserId")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.Property<DateTimeOffset>("Created")
.HasColumnType("datetimeoffset");
b.Property<string>("CreatedBy")
.HasColumnType("nvarchar(max)");
b.Property<string>("Currency")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<bool>("IsConfirmed")
.HasColumnType("bit");
b.Property<DateTimeOffset>("LastModified")
.HasColumnType("datetimeoffset");
b.Property<string>("LastModifiedBy")
.HasColumnType("nvarchar(max)");
b.Property<string>("TipMessage")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.HasIndex("ApplicationUserId");
b.ToTable("UserTransactions");
});
modelBuilder.Entity("Hutopy.Infrastructure.Identity.ApplicationUser", b => modelBuilder.Entity("Hutopy.Infrastructure.Identity.ApplicationUser", b =>
{ {
b.Property<string>("Id") b.Property<string>("Id")
@@ -275,6 +319,15 @@ namespace Hutopy.Infrastructure.Migrations
b.ToTable("AspNetUserTokens", (string)null); 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<string>", b => modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{ {
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)

View File

@@ -1,5 +1,4 @@
using System.Security.Cryptography; using System.Text;
using System.Text;
using Google.Apis.Oauth2.v2.Data; using Google.Apis.Oauth2.v2.Data;
using Hutopy.Domain.Interfaces; using Hutopy.Domain.Interfaces;
using Hutopy.Domain.Models; using Hutopy.Domain.Models;
@@ -21,10 +20,11 @@ public class UserService(UserManager<ApplicationUser> userManager) : IUserServic
FirstName = firstName, FirstName = firstName,
LastName = lastName LastName = lastName
}; };
//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.Succeeded) if (response.Errors.Any())
{ {
throw new Exception("Failed to create user", new AggregateException(response.Errors.Select(e => new Exception(e.Description)))); throw new Exception("Failed to create user", new AggregateException(response.Errors.Select(e => new Exception(e.Description))));
} }
@@ -52,6 +52,18 @@ public class UserService(UserManager<ApplicationUser> userManager) : IUserServic
return userModel; return userModel;
} }
public async Task<UserModel?> GetCurrentUserAsync()
{
// todo: Get the id of the user doing the request.
var userId = "";
if (string.IsNullOrEmpty(userId))
{
return null;
}
return await FindUserByIdAsync(userId);
}
public async Task<UserModel?> FindUserByEmailAsync(string email) public async Task<UserModel?> FindUserByEmailAsync(string email)
{ {

View File

@@ -6,13 +6,12 @@ namespace Hutopy.Infrastructure.Stripe;
public class StripeService : IStripeService public class StripeService : IStripeService
{ {
public StripeService() public StripeService()
{ {
// I removed the key to push. Will need to be in config. StripeConfiguration.ApiKey = "";
StripeConfiguration.ApiKey = ""; }
}
public async Task<string> CreateCheckoutSession(int price, string currency = "cad") public async Task<string> CreateCheckoutSession(int amount, string currency = "cad")
{ {
var options = new SessionCreateOptions var options = new SessionCreateOptions
{ {
@@ -22,7 +21,7 @@ public class StripeService : IStripeService
{ {
PriceData = new SessionLineItemPriceDataOptions PriceData = new SessionLineItemPriceDataOptions
{ {
UnitAmount = price, UnitAmount = amount,
Currency = currency, Currency = currency,
ProductData = new SessionLineItemPriceDataProductDataOptions { Name = "Tip", }, ProductData = new SessionLineItemPriceDataProductDataOptions { Name = "Tip", },
}, },
@@ -32,7 +31,7 @@ public class StripeService : IStripeService
], ],
Mode = "payment", Mode = "payment",
UiMode = "embedded", UiMode = "embedded",
ReturnUrl = $"http://localhost:5173/creatorfolio", ReturnUrl = "https://zealous-bay-08204590f.5.azurestaticapps.net/paymentcompleted",
}; };
var service = new SessionService(); var service = new SessionService();

View File

@@ -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<UserDto> GetCurrentUser(ISender sender, [AsParameters] GetCurrentUserQuery query)
{
return await sender.Send(query);
}
}

View File

@@ -7,6 +7,7 @@ public class Stripe : EndpointGroupBase
public override void Map(WebApplication app) public override void Map(WebApplication app)
{ {
app.MapGroup(this) app.MapGroup(this)
.MapPost(ConfirmTransaction, "/confirmTransaction")
.MapPost(CreateSessionCheckout); .MapPost(CreateSessionCheckout);
} }
@@ -14,4 +15,9 @@ public class Stripe : EndpointGroupBase
{ {
return sender.Send(command); return sender.Send(command);
} }
private static Task<string> ConfirmTransaction(ISender sender, ConfirmStripeTransactionCommand command)
{
return sender.Send(command);
}
} }

View File

@@ -13,7 +13,7 @@ public class Users : EndpointGroupBase
.MapIdentityApi<ApplicationUser>(); .MapIdentityApi<ApplicationUser>();
} }
public async Task<Guid> CreateUser(ISender sender, CreateUserCommand command, IUserService userService) private static async Task<Guid> CreateUser(ISender sender, CreateUserCommand command, IUserService userService)
{ {
await userService.CreateUserAsync(command.EmailAddress, command.UserName, command.FirstName, command.LastName, command.Password); await userService.CreateUserAsync(command.EmailAddress, command.UserName, command.FirstName, command.LastName, command.Password);
return await sender.Send(command); return await sender.Send(command);

View File

@@ -6,32 +6,19 @@
"version": "1.0.0" "version": "1.0.0"
}, },
"paths": { "paths": {
"/api/Google": { "/api/GetMyUser": {
"post": { "get": {
"tags": [ "tags": [
"Google" "GetMyUser"
], ],
"operationId": "CreateGoogleUser", "operationId": "GetCurrentUser",
"requestBody": {
"x-name": "command",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateGoogleUserCommand"
}
}
},
"required": true,
"x-position": 1
},
"responses": { "responses": {
"200": { "200": {
"description": "", "description": "",
"content": { "content": {
"application/json": { "application/json": {
"schema": { "schema": {
"type": "string", "$ref": "#/components/schemas/UserDto"
"format": "guid"
} }
} }
} }
@@ -112,6 +99,38 @@
} }
} }
}, },
"/api/Stripe/confirmTransaction": {
"post": {
"tags": [
"Stripe"
],
"operationId": "ConfirmTransaction",
"requestBody": {
"x-name": "command",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ConfirmStripeTransactionCommand"
}
}
},
"required": true,
"x-position": 1
},
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"type": "string"
}
}
}
}
}
}
},
"/api/Stripe": { "/api/Stripe": {
"post": { "post": {
"tags": [ "tags": [
@@ -592,11 +611,40 @@
}, },
"components": { "components": {
"schemas": { "schemas": {
"CreateGoogleUserCommand": { "UserDto": {
"type": "object", "type": "object",
"additionalProperties": false, "additionalProperties": false,
"properties": { "properties": {
"accessToken": { "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" "type": "string"
} }
} }
@@ -671,17 +719,35 @@
} }
} }
}, },
"CreateSessionCheckoutCommand": { "ConfirmStripeTransactionCommand": {
"type": "object", "type": "object",
"x-abstract": true,
"additionalProperties": false, "additionalProperties": false,
"properties": { "properties": {
"price": { "userTransactionId": {
"type": "string",
"format": "guid"
},
"isConfirmed": {
"type": "boolean"
}
}
},
"CreateSessionCheckoutCommand": {
"type": "object",
"additionalProperties": false,
"properties": {
"creatorId": {
"type": "string"
},
"amount": {
"type": "integer", "type": "integer",
"format": "int32" "format": "int32"
}, },
"currency": { "currency": {
"type": "string" "type": "string"
},
"tipMessage": {
"type": "string"
} }
} }
}, },