Skip to content

Commit

Permalink
Added support for stripe backend
Browse files Browse the repository at this point in the history
  • Loading branch information
DavidLazarescu committed Jan 10, 2024
1 parent e8daf0d commit 6da7c27
Show file tree
Hide file tree
Showing 27 changed files with 674 additions and 30 deletions.
20 changes: 20 additions & 0 deletions src/Application/Common/DTOs/Product/ProductForUpdateDto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System.Collections.ObjectModel;

namespace Application.Common.DTOs.Product;

public class ProductForUpdateDto
{
public string Id { get; set; }

public string Name { get; set; }

public string Description { get; set; }

public int Price { get; set; }

public long BookStorageLimit { get; set; }

public int AiRequestLimit { get; set; }

public ICollection<string> Features { get; set; } = new Collection<string>();
}
25 changes: 25 additions & 0 deletions src/Application/Common/DTOs/Product/ProductInDto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System.Collections.ObjectModel;
using System.ComponentModel.DataAnnotations;

namespace Application.Common.DTOs.Product;

public class ProductInDto
{
[Required]
public string Id { get; set; }

[Required]
public string Name { get; set; }

[Required]
public string Description { get; set; }

[Required]
public long BookStorageLimit { get; set; }

[Required]
public int AiRequestLimit { get; set; }

[Required]
public ICollection<string> Features { get; set; } = new Collection<string>();
}
18 changes: 18 additions & 0 deletions src/Application/Common/DTOs/Product/ProductOutDto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System.Collections.ObjectModel;

namespace Application.Common.DTOs.Product;

public class ProductOutDto
{
public string Id { get; set; }

public bool Active { get; set; }

public string Name { get; set; }

public string Description { get; set; }

public int Price { get; set; }

public ICollection<string> Features { get; set; } = new Collection<string>();
}
4 changes: 4 additions & 0 deletions src/Application/Common/DTOs/Users/UserOutDto.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ public class UserOutDto
public string Email { get; set; }

public string Role { get; set; }

public string ProductId { get; set; }

public string CustomerId { get; set; }

public DateTime AccountCreation { get; set; }

Expand Down
20 changes: 20 additions & 0 deletions src/Application/Common/Mappings/ProductAutoMapperProfile.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using Application.Common.DTOs.Highlights;
using Application.Common.DTOs.Product;
using AutoMapper;
using Domain.Entities;

namespace Application.Common.Mappings;

public class ProductAutoMapperProfile : Profile
{
public ProductAutoMapperProfile()
{
CreateMap<ProductInDto, Product>()
.ForMember(dest => dest.ProductId, temp => temp.MapFrom(src => src.Id))
.ForMember(dest => dest.Features, temp => temp.Ignore())
.ForMember(dest => dest.Price, temp => temp.Ignore());
CreateMap<Product, ProductOutDto>()
.ForMember(dest => dest.Id, temp => temp.MapFrom(src => src.ProductId.ToString()))
.ForMember(dest => dest.Features, temp => temp.MapFrom(src => src.Features.Select(feature => feature.Name)));
}
}
12 changes: 7 additions & 5 deletions src/Application/Common/Mappings/UserAutoMapperProfile.cs
Original file line number Diff line number Diff line change
@@ -1,22 +1,24 @@
using Application.Common.DTOs.Users;
using Application.Interfaces.Repositories;
using AutoMapper;
using Domain.Entities;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.DependencyInjection;

namespace Application.Common.Mappings;

public class UserAutoMapperProfile : Profile
{
public UserAutoMapperProfile()
{
CreateMap<User, UserOutDto>();
CreateMap<User, UserOutDto>()
.ForMember(dest => dest.Role, temp => temp.Ignore());

CreateMap<RegisterDto, User>()
.ForMember(dest => dest.UserName, temp => temp.MapFrom(src => src.Email))
.ForMember(dest => dest.AccountCreation,
temp => temp.MapFrom(src => DateTime.UtcNow))
temp => temp.MapFrom(src => DateTime.UtcNow))
.ForMember(dest => dest.PasswordHash, temp => temp.Ignore())
.ForMember(dest => dest.Role, temp => temp.MapFrom(src => "Basic")); // Default role
.ForMember(dest => dest.ProductId, temp => temp.Ignore());

CreateMap<User, UserForUpdateDto>()
.ReverseMap();
}
Expand Down
12 changes: 12 additions & 0 deletions src/Application/Interfaces/Repositories/IProductRepository.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using Domain.Entities;

namespace Application.Interfaces.Repositories;

public interface IProductRepository
{
public Task<int> SaveChangesAsync();
public void CreateProduct(Product product);
public IQueryable<Product> GetAll();
public Task<Product> GetByIdAsync(string id);
public void DeleteProduct(Product product);
}
12 changes: 12 additions & 0 deletions src/Application/Interfaces/Services/IProductService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using Application.Common.DTOs.Product;

namespace Application.Interfaces.Services;

public interface IProductService
{
public Task<IEnumerable<ProductOutDto>> GetAllProductsAsync();
public Task CreateProductAsync(ProductInDto productInDto);
public Task UpdateProductAsync(ProductForUpdateDto productInDto);
public Task DeleteProductAsync(string id);
public Task AddPriceToProductAsync(string id, int price);
}
3 changes: 3 additions & 0 deletions src/Application/Interfaces/Services/IUserService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,7 @@ public Task PatchUserAsync(string email,
public Task ChangePasswordWithTokenAsync(string email, string token,
string newPassword);
public Task ForgotPassword(string email);
public Task AddCustomerIdToUser(string email, string customerId);
public Task AddTierToUser(string email, string productId);
public Task ResetUserToFreeTier(string email);
}
3 changes: 0 additions & 3 deletions src/Application/Managers/AuthenticationManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,6 @@ public AuthenticationManager(IConfiguration configuration,
public async Task<bool> CreateUserAsync(User user, string password)
{
var result = await _userManager.CreateAsync(user, password);
if (result.Succeeded)
await _userManager.AddToRoleAsync(user, user.Role);

return result.Succeeded;
}

Expand Down
13 changes: 12 additions & 1 deletion src/Application/Services/AuthenticationService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using Application.Common.DTOs.Users;
using Application.Common.Exceptions;
using Application.Interfaces.Managers;
using Application.Interfaces.Repositories;
using Application.Interfaces.Services;
using Application.Interfaces.Utility;
using AutoMapper;
Expand All @@ -17,19 +18,22 @@ public class AuthenticationService : IAuthenticationService
private readonly IEmailSender _emailSender;
private readonly IHttpClientFactory _httpClientFactory;
private readonly IConfiguration _configuration;
private readonly IProductRepository _productRepository;


public AuthenticationService(IMapper mapper,
IAuthenticationManager authenticationManager,
IEmailSender emailSender,
IHttpClientFactory httpClientFactory,
IConfiguration configuration)
IConfiguration configuration,
IProductRepository productRepository)
{
_mapper = mapper;
_authenticationManager = authenticationManager;
_emailSender = emailSender;
_httpClientFactory = httpClientFactory;
_configuration = configuration;
_productRepository = productRepository;
}


Expand Down Expand Up @@ -62,6 +66,13 @@ public async Task RegisterUserAsync(RegisterDto registerDto)
}

var user = _mapper.Map<User>(registerDto);

// Assign the free product by default
var freeProduct = _productRepository.GetAll().SingleOrDefault(p => p.Price == 0.0);
if(freeProduct == null)
throw new CommonErrorException(500, "No free product found", 0);

user.ProductId = freeProduct.ProductId;

var success =
await _authenticationManager.CreateUserAsync(user, registerDto.Password);
Expand Down
156 changes: 156 additions & 0 deletions src/Application/Services/ProductService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
using Application.Common.DTOs.Product;
using Application.Common.Exceptions;
using Application.Interfaces.Repositories;
using Application.Interfaces.Services;
using AutoMapper;
using Domain.Entities;
using Microsoft.EntityFrameworkCore;

namespace Application.Services;

public class ProductService(IMapper mapper, IProductRepository productRepository) : IProductService
{
private IMapper Mapper { get; } = mapper;
private IProductRepository ProductRepository { get; } = productRepository;

Check warning on line 14 in src/Application/Services/ProductService.cs

View workflow job for this annotation

GitHub Actions / test

Parameter 'IProductRepository productRepository' is captured into the state of the enclosing type and its value is also used to initialize a field, property, or event.

Check warning on line 14 in src/Application/Services/ProductService.cs

View workflow job for this annotation

GitHub Actions / deploy

Parameter 'IProductRepository productRepository' is captured into the state of the enclosing type and its value is also used to initialize a field, property, or event.

public async Task<IEnumerable<ProductOutDto>> GetAllProductsAsync()
{
var products = await productRepository.GetAll().ToListAsync();
return products.Select(product => Mapper.Map<ProductOutDto>(product));
}

public async Task CreateProductAsync(ProductInDto productInDto)
{
var product = Mapper.Map<Product>(productInDto);
foreach(var feature in productInDto.Features)
{
product.Features.Add(new ProductFeature
{
Name = feature
});
}
productRepository.CreateProduct(product);

await productRepository.SaveChangesAsync();
}

public async Task UpdateProductAsync(ProductForUpdateDto productUpdateDto)
{
var product = await productRepository.GetByIdAsync(productUpdateDto.Id);

// The update gets sent at the same time as the product is created, we need to wait for the product to be created
int tries = 0;
while(product == null && tries < 3)
{
await Task.Delay(200);
product = await productRepository.GetByIdAsync(productUpdateDto.Id);
tries++;
}

if (product == null)
{
const string message = "No product with this id exists";
throw new CommonErrorException(404, message, 0);
}

bool hasChanged = false;
var dtoProperties = productUpdateDto.GetType().GetProperties();
foreach (var dtoProperty in dtoProperties)
{
// Manually handle certain properties
switch (dtoProperty.Name)
{
case "Id":
continue; // Can't modify the GUID
case "Features":
{
// Remove all existing features
product.Features.Clear();

// Add all new features
foreach (var feature in productUpdateDto.Features)
{
product.Features.Add(new ProductFeature
{
Name = feature
});
}

hasChanged = true;
continue;
}
}

var productProperty = product.GetType().GetProperty(dtoProperty.Name);
var productValue = productProperty.GetValue(product);
var dtoValue = dtoProperty.GetValue(productUpdateDto);
if (productValue == dtoValue)
continue;

// Handle this after the check if the values are the same
if (dtoProperty.Name == "Price")
{
if (productUpdateDto.Price == 0)
continue;

product.Price = productUpdateDto.Price;
}

// Update any other property via reflection
var value = dtoProperty.GetValue(productUpdateDto);
SetPropertyOnProduct(product, dtoProperty.Name, value);
hasChanged = true;
}

if(hasChanged)
await ProductRepository.SaveChangesAsync();
}

private void SetPropertyOnProduct(Product product, string property, object value)
{
var bookProperty = product.GetType().GetProperty(property);
if (bookProperty == null)
{
var message = "Product has no property called: " + property;
throw new CommonErrorException(400, message, 0);
}

bookProperty.SetValue(product, value);
}

public async Task DeleteProductAsync(string id)
{
var product = await productRepository.GetByIdAsync(id);
if (product == null)
{
const string message = "No product with this id exists";
throw new CommonErrorException(404, message, 0);
}

productRepository.DeleteProduct(product);
await productRepository.SaveChangesAsync();
}

public async Task AddPriceToProductAsync(string id, int price)
{
var product = await productRepository.GetByIdAsync(id);

// The price gets sent at the same time as the product is created, we need to wait for the product to be created
int tries = 0;
while(product == null && tries < 3)
{
await Task.Delay(300);
product = await productRepository.GetByIdAsync(id);
tries++;
}

if (product == null)
{
const string message = "No product with this id exists";
throw new CommonErrorException(404, message, 0);
}

product.Price = price;
await productRepository.SaveChangesAsync();
}
}
Loading

0 comments on commit 6da7c27

Please sign in to comment.