diff --git a/Topers.Api/Controllers/AccountController.cs b/Topers.Api/Controllers/AccountController.cs new file mode 100644 index 0000000..05f48a4 --- /dev/null +++ b/Topers.Api/Controllers/AccountController.cs @@ -0,0 +1,40 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Options; +using Topers.Core.Abstractions; +using Topers.Core.Dtos; +using Topers.Infrastructure.Features; + + +namespace Topers.Api.Controllers; + +[ApiController] +[Route("api/account")] +public class AccountController( + IUsersService userService, + IOptions options) : ControllerBase +{ + private readonly IUsersService _userService = userService; + private readonly CookiesOptions _cookieOptions = options.Value; + + [HttpPost("register")] + public async Task Register( + [FromBody] RegisterUserRequestDto request, + CancellationToken cancellationToken) + { + await _userService.Register(request.Username, request.Email, request.Password, cancellationToken); + + return Results.Ok(); + } + + [HttpPost("login")] + public async Task Login( + [FromBody] LoginUserRequestDto request, + CancellationToken cancellationToken) + { + var token = await _userService.Login(request.Username, request.Password, cancellationToken); + + HttpContext.Response.Cookies.Append(_cookieOptions.Name, token); + + return Results.Ok(token); + } +} \ No newline at end of file diff --git a/Topers.Api/Controllers/AddressesController.cs b/Topers.Api/Controllers/AddressesController.cs new file mode 100644 index 0000000..d896ee8 --- /dev/null +++ b/Topers.Api/Controllers/AddressesController.cs @@ -0,0 +1,48 @@ +using Microsoft.AspNetCore.Mvc; +using Swashbuckle.AspNetCore.Annotations; +using Topers.Core.Abstractions; +using Topers.Core.Dtos; +using Topers.Core.Models; +using Topers.Core.Validators; + +namespace Topers.Api.Controllers; + +[ApiController] +[Route("api/addresses")] +public class AddressesController(IAddressesService addressesService) : ControllerBase +{ + private readonly IAddressesService _addressService = addressesService; + + [HttpPost("{customerId:guid}")] + [SwaggerResponse(200, Description = "Returns the new address data of the customer.", Type = typeof(AddressResponseDto))] + [SwaggerResponse(400, Description = "There are some errors in the model.")] + public async Task> AddAddressToCustomer( + [FromRoute] Guid customerId, + [FromBody] AddressRequestDto address, + CancellationToken cancellationToken) + { + var newAddressValidator = new AddressDtoValidator(); + + var newAddressValidatorResult = newAddressValidator.Validate(address); + + if (!newAddressValidatorResult.IsValid) + { + return BadRequest(newAddressValidatorResult.Errors); + } + + var newAddress = new Address + ( + Guid.Empty, + customerId, + address.Street, + address.City, + address.State, + address.PostalCode, + address.Country + ); + + var addressEntity = await _addressService.AddAddressToCustomerAsync(newAddress, cancellationToken); + + return Ok(addressEntity); + } +} \ No newline at end of file diff --git a/Topers.Api/Controllers/CartsController.cs b/Topers.Api/Controllers/CartsController.cs new file mode 100644 index 0000000..0f94621 --- /dev/null +++ b/Topers.Api/Controllers/CartsController.cs @@ -0,0 +1,112 @@ +using Microsoft.AspNetCore.Mvc; +using Swashbuckle.AspNetCore.Annotations; +using Topers.Core.Abstractions; +using Topers.Core.Dtos; +using Topers.Core.Models; + +namespace Topers.Api.Controllers +{ + [ApiController] + [Route("api/cart")] + public class CartsController(ICartsService cartService) : ControllerBase + { + private readonly ICartsService _cartService = cartService; + + [HttpGet("{cartId:guid}")] + [SwaggerResponse( + 200, + Description = "Returns a cart by identifier.", + Type = typeof(CartResponseDto) + )] + [SwaggerResponse(400, Description = "Cart not found.")] + public async Task> GetCartById( + [FromRoute] Guid cartId, + CancellationToken cancellationToken + ) + { + var cart = await _cartService.GetCartById(cartId, cancellationToken); + + if (cart == null) + { + return NotFound(); + } + + return Ok(cart); + } + + [HttpGet("{customerId:guid}")] + [SwaggerResponse( + 200, + Description = "Returns a cart by customer identifier.", + Type = typeof(CartResponseDto) + )] + [SwaggerResponse(400, Description = "Cart not found.")] + public async Task> GetCartByCustomerId( + [FromRoute] Guid customerId, + CancellationToken cancellationToken + ) + { + var cart = await _cartService.GetCartByCustomerId(customerId, cancellationToken); + + if (cart == null) + { + return NotFound(); + } + + return Ok(cart); + } + + [HttpPost("create")] + [SwaggerResponse(200, Description = "Create a new cart.")] + [SwaggerResponse(400, Description = "There are some errors in the model.")] + public async Task> CreateCart( + [FromBody] CartRequestDto cart, + CancellationToken cancellationToken + ) + { + var newCart = new Cart + ( + Guid.Empty, + cart.CustomerId, + DateTime.UtcNow, + DateTime.UtcNow + ); + + var newCartEntity = await _cartService.CreateCartAsync(newCart, cancellationToken); + + return Ok(newCartEntity); + } + + [HttpPost("{cartId:guid}/addGood")] + [SwaggerResponse(200, Description = "Add good to a customer cart.")] + [SwaggerResponse(400, Description = "There are some errors in the model.")] + public async Task> AddProductToCart( + [FromRoute] Guid cartId, + [FromBody] AddProductRequestDto cartDetail, + CancellationToken cancellationToken + ) + { + var newGoodDetail = new CartItems( + Guid.Empty, + cartId, + cartDetail.GoodScopeId, + cartDetail.GoodQuantity, + default + ); + + var newGoodScope = new GoodScope( + Guid.Empty, + cartDetail.GoodScopeId, + cartDetail.GoodLitre + ); + + var newGoodDetailIdentifier = await _cartService.AddGoodToCartAsync( + newGoodDetail, + newGoodScope, + cancellationToken + ); + + return Ok(newGoodDetailIdentifier); + } + } +} diff --git a/Topers.Api/Controllers/CategoriesController.cs b/Topers.Api/Controllers/CategoriesController.cs new file mode 100644 index 0000000..8fd8abb --- /dev/null +++ b/Topers.Api/Controllers/CategoriesController.cs @@ -0,0 +1,141 @@ +namespace Topers.Api.Controllers; + +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Swashbuckle.AspNetCore.Annotations; +using Topers.Core.Abstractions; +using Topers.Core.Dtos; +using Topers.Core.Models; +using Topers.Core.Validators; + +[ApiController] +[Route("api/categories")] +public class CategoriesController : ControllerBase +{ + private readonly ICategoriesService _categoryService; + + public CategoriesController(ICategoriesService categoryService) + { + _categoryService = categoryService; + } + + [HttpGet] + [SwaggerResponse(200, Description = "Returns a category list.", Type = typeof(IEnumerable))] + [SwaggerResponse(400, Description = "Categories not found.")] + public async Task>> GetCategories(CancellationToken cancellationToken) + { + var categories = await _categoryService.GetAllCategoriesAsync(cancellationToken); + + if (categories == null) + { + return BadRequest(); + } + + return Ok(categories); + } + + [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] + [HttpGet("{categoryId:guid}")] + [SwaggerResponse(200, Description = "Returns a category.", Type = typeof(CategoryResponseDto))] + [SwaggerResponse(400, Description = "Category not found.")] + public async Task> GetCategory( + [FromRoute] Guid categoryId, + CancellationToken cancellationToken) + { + var category = await _categoryService.GetCategoryByIdAsync(categoryId, cancellationToken); + + if (category == null) + { + return BadRequest(); + } + + return Ok(category); + } + + [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] + [HttpGet("{categoryId:guid}/goods")] + [SwaggerResponse(200, Description = "Returns a category goods.", Type = typeof(IEnumerable))] + [SwaggerResponse(400, Description = "Goods not found.")] + public async Task>> GetCategoryGoods( + [FromRoute] Guid categoryId, + CancellationToken cancellationToken) + { + var goods = await _categoryService.GetGoodsByCategoryIdAsync(categoryId, cancellationToken); + + if (goods == null) + { + return BadRequest(); + } + + return Ok(goods); + } + + [HttpPost("create")] + [SwaggerResponse(200, Description = "Create a new category.")] + [SwaggerResponse(400, Description = "There are some errors in the model.")] + public async Task> CreateCategory( + [FromBody] CategoryRequestDto category, + CancellationToken cancellationToken) + { + var categoryValidator = new CategoryDtoValidator(); + + var categoryValidatorResult = categoryValidator.Validate(category); + + if (!categoryValidatorResult.IsValid) + { + return BadRequest(categoryValidatorResult.Errors); + } + + var newCategory = new Category + ( + Guid.Empty, + category.Name, + category.Description + ); + + var newCategoryEntity = await _categoryService.CreateCategoryAsync(newCategory, cancellationToken); + + return Ok(newCategoryEntity); + } + + [HttpPut("{categoryId:guid}")] + [SwaggerResponse(200, Description = "Update an existing category.")] + [SwaggerResponse(400, Description = "There are some errors in the model.")] + public async Task> UpdateCategory( + [FromRoute] Guid categoryId, + [FromBody] CategoryRequestDto category, + CancellationToken cancellationToken) + { + var categoryValidator = new CategoryDtoValidator(); + + var categoryValidatorResult = categoryValidator.Validate(category); + + if (!categoryValidatorResult.IsValid) + { + return BadRequest(categoryValidatorResult.Errors); + } + + var existCategory = new Category + ( + categoryId, + category.Name, + category.Description + ); + + var updatedCategory = await _categoryService.UpdateCategoryAsync(existCategory, cancellationToken); + + return Ok(updatedCategory); + } + + [HttpDelete("{categoryId:guid}")] + [SwaggerResponse(200, Description = "Delete category.")] + public async Task> DeleteCategory( + [FromRoute] Guid categoryId, + CancellationToken cancellationToken) + { + await _categoryService.DeleteCategoryAsync(categoryId, cancellationToken); + + return Ok(categoryId); + } +} \ No newline at end of file diff --git a/Topers.Api/Controllers/CustomersController.cs b/Topers.Api/Controllers/CustomersController.cs new file mode 100644 index 0000000..35b2cbd --- /dev/null +++ b/Topers.Api/Controllers/CustomersController.cs @@ -0,0 +1,77 @@ +namespace Topers.Api.Contollers; + +using Microsoft.AspNetCore.Mvc; +using Swashbuckle.AspNetCore.Annotations; +using Topers.Core.Abstractions; +using Topers.Core.Dtos; +using Topers.Core.Models; +using Topers.Core.Validators; + +[ApiController] +[Route("api/customers")] +public class CustomersController(ICustomersService customerService) : ControllerBase +{ + private readonly ICustomersService _customerService = customerService; + + [HttpGet] + [SwaggerResponse(200, Description = "Returns a customer list.", Type = typeof(IEnumerable))] + [SwaggerResponse(400, Description = "Customers not found.")] + public async Task>> GetCustomers(CancellationToken cancellationToken) + { + var customers = await _customerService.GetAllCustomersAsync(cancellationToken); + + if (customers == null) + { + return BadRequest(); + } + + return Ok(customers); + } + + [HttpGet("{customerId:guid}")] + [SwaggerResponse(200, Description = "Returns a customer.", Type = typeof(CustomerResponseDto))] + [SwaggerResponse(400, Description = "Customer not found.")] + public async Task> GetCustomerById( + [FromRoute] Guid customerId, + CancellationToken cancellationToken) + { + var customer = await _customerService.GetCustomerByIdAsync(customerId, cancellationToken); + + if (customer == null) + { + return BadRequest(); + } + + return Ok(customer); + } + + [HttpPost("create")] + [SwaggerResponse(200, Description = "Create a new customer.")] + [SwaggerResponse(400, Description = "There are some errors in the model.")] + public async Task> CreateCustomer( + [FromBody] CustomerRequestDto customer, + CancellationToken cancellationToken) + { + var newCustomerValidator = new CustomerDtoValidator(); + + var newCustomerValidatorResult = newCustomerValidator.Validate(customer); + + if (!newCustomerValidatorResult.IsValid) + { + return BadRequest(newCustomerValidatorResult.Errors); + } + + var newCustomer = new Customer + ( + Guid.Empty, + null, + customer.Name, + customer.Email, + customer.Phone + ); + + var newCustomerEntity = await _customerService.CreateCustomerAsync(newCustomer, cancellationToken); + + return Ok(newCustomerEntity); + } +} diff --git a/Topers.Api/Controllers/GoodsController.cs b/Topers.Api/Controllers/GoodsController.cs new file mode 100644 index 0000000..5bd4de9 --- /dev/null +++ b/Topers.Api/Controllers/GoodsController.cs @@ -0,0 +1,151 @@ +namespace Topers.Api.Contollers; + +using Microsoft.AspNetCore.Mvc; +using Swashbuckle.AspNetCore.Annotations; +using Topers.Core.Abstractions; +using Topers.Core.Dtos; +using Topers.Core.Models; +using Topers.Core.Validators; + +[ApiController] +[Route("api/goods")] +public class GoodsController( + IGoodsService goodService, + IFileService fileService) : ControllerBase +{ + private readonly IGoodsService _goodService = goodService; + private readonly IFileService _fileService = fileService; + + [HttpGet] + [SwaggerResponse(200, Description = "Returns a good list.", Type = typeof(IEnumerable))] + [SwaggerResponse(400, Description = "Goods not found.")] + public async Task>> GetGoods() + { + var goods = await _goodService.GetAllGoodsAsync(); + + if (goods == null) + { + return BadRequest(); + } + + return Ok(goods); + } + + [HttpGet("{goodId:guid}")] + [SwaggerResponse(200, Description = "Returns a good.", Type = typeof(GoodResponseDto))] + [SwaggerResponse(400, Description = "Good not found.")] + public async Task> GetGood([FromRoute] Guid goodId) + { + var good = await _goodService.GetGoodByIdAsync(goodId); + + if (good == null) + { + return BadRequest(); + } + + return Ok(good); + } + + [HttpPost("create")] + [SwaggerResponse(200, Description = "Create a new good.")] + [SwaggerResponse(400, Description = "There are some errors in the model.")] + public async Task> CreateGood( + [FromBody] GoodRequestDto good, + CancellationToken cancellationToken) + { + var newGoodValidator = new GoodDtoValidator(); + + var newGoodValidatorResult = newGoodValidator.Validate(good); + + if (!newGoodValidatorResult.IsValid) + { + return BadRequest(newGoodValidatorResult.Errors); + } + + var newGood = new Good + ( + Guid.Empty, + good.CategoryId, + good.Name, + good.Description + ); + + var newGoodEntity = await _goodService.CreateGoodAsync(newGood, cancellationToken); + + return Ok(newGoodEntity); + } + + [HttpPut("{goodId:guid}")] + [SwaggerResponse(200, Description = "Update an existing good.")] + [SwaggerResponse(400, Description = "There are some errors in the model.")] + public async Task> UpdateGood( + [FromRoute] Guid goodId, + [FromBody] GoodRequestDto good, + CancellationToken cancellationToken) + { + var goodValidator = new GoodDtoValidator(); + + var goodValidatorResult = goodValidator.Validate(good); + + if (!goodValidatorResult.IsValid) + { + return BadRequest(goodValidatorResult.Errors); + } + + var existGood = new Good + ( + goodId, + good.CategoryId, + good.Name, + good.Description + ); + + var updatedGood = await _goodService.UpdateGoodAsync(existGood, cancellationToken); + + return Ok(updatedGood); + } + + [HttpDelete("{goodId:guid}")] + [SwaggerResponse(200, Description = "Delete good.")] + public async Task> DeleteGood( + [FromRoute] Guid goodId, + CancellationToken cancellationToken) + { + await _goodService.DeleteGoodAsync(goodId, cancellationToken); + + return Ok(goodId); + } + + [HttpPost("{goodId:guid}/addScope")] + public async Task> AddGoodScope( + [FromRoute] Guid goodId, + [FromForm] GoodScopeRequestDto scope, + CancellationToken cancellationToken) + { + if (scope.ImageFile == null) + { + return BadRequest("Good image not found!"); + } + + var fileResult = _fileService.SaveImage(scope.ImageFile); + + if (fileResult.Item1 == 1) + { + scope = scope with { ImageName = fileResult.Item2 }; + } + + var scopeModel = new GoodScope( + Guid.Empty, + goodId, + scope.Litre, + scope.Price, + scope.ImageName + ); + + var isUpdated = await _goodService.IsGoodScopeExistsAsync(goodId, scopeModel.Litre, cancellationToken); + + var newScopeIdentifier = (!isUpdated) ? await _goodService.AddGoodScopeAsync(scopeModel, cancellationToken) : await _goodService.UpdateGoodScopeAsync(scopeModel, cancellationToken); + + return Ok(newScopeIdentifier); + } +} \ No newline at end of file diff --git a/Topers.Api/Controllers/OrdersController.cs b/Topers.Api/Controllers/OrdersController.cs new file mode 100644 index 0000000..47b8620 --- /dev/null +++ b/Topers.Api/Controllers/OrdersController.cs @@ -0,0 +1,126 @@ +using Microsoft.AspNetCore.Mvc; +using Swashbuckle.AspNetCore.Annotations; +using Topers.Core.Abstractions; +using Topers.Core.Dtos; +using Topers.Core.Models; + +namespace Topers.Api.Controllers +{ + [ApiController] + [Route("api/orders")] + public class OrdersController(IOrdersService orderService) : ControllerBase + { + private readonly IOrdersService _ordersService = orderService; + + [HttpGet] + [SwaggerResponse( + 200, + Description = "Returns an orders list.", + Type = typeof(IEnumerable) + )] + [SwaggerResponse(400, Description = "Orders not found.")] + public async Task>> GetOrders( + CancellationToken cancellationToken + ) + { + var orders = await _ordersService.GetAllOrdersAsync(cancellationToken); + + if (orders == null) + { + return NotFound(); + } + + return Ok(orders); + } + + [HttpGet("{orderId:guid}")] + [SwaggerResponse( + 200, + Description = "Returns an orders list.", + Type = typeof(OrderResponseDto) + )] + [SwaggerResponse(400, Description = "Orders not found.")] + public async Task> GetOrderById( + [FromRoute] Guid orderId, + CancellationToken cancellationToken + ) + { + var order = await _ordersService.GetOrderByIdAsync(orderId, cancellationToken); + + if (order == null) + { + return NotFound(); + } + + return Ok(order); + } + + [HttpPost("create")] + [SwaggerResponse(200, Description = "Create a new order.")] + [SwaggerResponse(400, Description = "There are some errors in the model.")] + public async Task> CreateGood( + [FromBody] OrderRequestDto order, + CancellationToken cancellationToken + ) + { + var newOrder = new Order(Guid.Empty, order.Date, order.CustomerId, 0); + + var newOrderEntity = await _ordersService.CreateOrderAsync(newOrder, cancellationToken); + + return Ok(newOrderEntity); + } + + [HttpPut("{orderId:guid}")] + [SwaggerResponse(200, Description = "Update an existing order.")] + [SwaggerResponse(400, Description = "There are some errors in the model.")] + public async Task> UpdateOrder( + [FromRoute] Guid orderId, + [FromBody] UpdateOrderRequestDto order, + CancellationToken cancellationToken + ) + { + var existOrder = new Order( + orderId, + DateTime.UtcNow, + order.CustomerId, + Decimal.MinValue + ); + + var updatedOrder = await _ordersService.UpdateOrderAsync(existOrder, cancellationToken); + + return Ok(updatedOrder); + } + + [HttpPost("{orderId:guid}/addGood")] + [SwaggerResponse(200, Description = "Add good to an existing order.")] + [SwaggerResponse(400, Description = "There are some errors in the model.")] + public async Task> AddProductToOrder( + [FromRoute] Guid orderId, + [FromBody] AddProductRequestDto orderDetail, + CancellationToken cancellationToken + ) + { + var newGoodDetail = new OrderDetails( + Guid.Empty, + orderId, + orderDetail.GoodScopeId, + orderDetail.GoodQuantity, + default + ); + + var newGoodScope = new GoodScope( + Guid.Empty, + orderDetail.GoodScopeId, + orderDetail.GoodLitre + ); + + var newGoodDetailIdentifier = await _ordersService.AddGoodToOrderAsync( + newGoodDetail, + newGoodScope, + cancellationToken + ); + + return Ok(newGoodDetailIdentifier); + } + } +} diff --git a/Topers.Api/Extensions/AuthenticationExtensions.cs b/Topers.Api/Extensions/AuthenticationExtensions.cs new file mode 100644 index 0000000..5bfa46c --- /dev/null +++ b/Topers.Api/Extensions/AuthenticationExtensions.cs @@ -0,0 +1,44 @@ +namespace Topers.Api.Extensions; + +using Topers.Infrastructure.Features; +using Microsoft.IdentityModel.Tokens; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using System.Text; + +public static class AuthenticationExtensions +{ + public static IServiceCollection AddCustomAuthentication(this IServiceCollection services, IConfiguration configuration) + { + var jwtOptionsSection = configuration.GetSection(nameof(JwtOptions)); + var cookieOptionsSection = configuration.GetSection(nameof(CookiesOptions)); + var cookieName = cookieOptionsSection.GetValue("Name")!; + + services.AddAuthentication(options => + { + options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; + options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; + }).AddJwtBearer(options => + { + options.TokenValidationParameters = new TokenValidationParameters + { + ValidateIssuer = false, + ValidateAudience = false, + ValidateLifetime = true, + ValidateIssuerSigningKey = true, + IssuerSigningKey = new SymmetricSecurityKey( + Encoding.UTF8.GetBytes(jwtOptionsSection.GetValue("SecretKey")!)) + }; + + options.Events = new JwtBearerEvents + { + OnMessageReceived = context => + { + context.Token = context.Request.Cookies[cookieName]; + return Task.CompletedTask; + } + }; + }); + + return services; + } +}; \ No newline at end of file diff --git a/Topers.Api/Extensions/MiddlewareExtensions.cs b/Topers.Api/Extensions/MiddlewareExtensions.cs new file mode 100644 index 0000000..98cdae0 --- /dev/null +++ b/Topers.Api/Extensions/MiddlewareExtensions.cs @@ -0,0 +1,56 @@ +namespace Topers.Api.Extensions; + +using System.Globalization; +using Microsoft.AspNetCore.Localization; +using Microsoft.Extensions.FileProviders; +using Microsoft.AspNetCore.CookiePolicy; +using Topers.Api.Middleware; + +public static class MiddlewareExtensions +{ + public static IApplicationBuilder UseCustomMiddlewares(this IApplicationBuilder application, IWebHostEnvironment environment) + { + var defaultCulture = new CultureInfo("en-US"); + var localizationOptions = new RequestLocalizationOptions + { + DefaultRequestCulture = new RequestCulture(defaultCulture), + SupportedCultures = new[] { defaultCulture }, + SupportedUICultures = new[] { defaultCulture } + }; + + if (environment.IsDevelopment()) + { + application.UseSwagger(); + application.UseSwaggerUI(options => + { + options.SwaggerEndpoint("/swagger/v1/swagger.json", "v1"); + options.DocumentTitle = "Topers API"; + options.RoutePrefix = string.Empty; + }); + } + + application.UseHttpsRedirection(); + application.UseRequestLocalization(localizationOptions); + application.UseStaticFiles(new StaticFileOptions + { + FileProvider = new PhysicalFileProvider( + Path.Combine(environment.ContentRootPath, "Uploads") + ), + RequestPath = "/Resources" + }); + application.UseCookiePolicy(new CookiePolicyOptions + { + MinimumSameSitePolicy = SameSiteMode.Strict, + HttpOnly = HttpOnlyPolicy.Always, + Secure = CookieSecurePolicy.Always + }); + application.UseCors(options => options.WithOrigins("http://localhost:5173") + .AllowAnyHeader() + .AllowAnyMethod()); + application.UseAuthentication(); + application.UseAuthorization(); + application.UseMiddleware(); + + return application; + } +}; \ No newline at end of file diff --git a/Topers.Api/Extensions/ServiceExtensions.cs b/Topers.Api/Extensions/ServiceExtensions.cs new file mode 100644 index 0000000..0997672 --- /dev/null +++ b/Topers.Api/Extensions/ServiceExtensions.cs @@ -0,0 +1,46 @@ +namespace Topers.Api.Extensions; + +using Topers.Infrastructure.Features; +using Topers.DataAccess.Postgres; +using Microsoft.EntityFrameworkCore; +using Topers.Core.Abstractions; +using Topers.DataAccess.Postgres.Repositories; +using Topers.Infrastructure.Services; + +public static class ServiceExtensions +{ + public static IServiceCollection AddCustomServices(this IServiceCollection services, IConfiguration configuration) + { + services.Configure(configuration.GetSection(nameof(JwtOptions))); + services.Configure(configuration.GetSection(nameof(CookiesOptions))); + + services.AddDbContext(options => + { + options.UseNpgsql(configuration.GetConnectionString("TopersDbContext")); + }); + + services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies()); + + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + + return services; + } +}; \ No newline at end of file diff --git a/Topers.Api/Mapping/MappingProfile.cs b/Topers.Api/Mapping/MappingProfile.cs new file mode 100644 index 0000000..54d85bf --- /dev/null +++ b/Topers.Api/Mapping/MappingProfile.cs @@ -0,0 +1,147 @@ +using AutoMapper; +using Topers.Core.Dtos; +using Topers.Core.Models; +using Topers.DataAccess.Postgres.Entities; + +namespace Topers.Api.Mapping; + +public class MappingProfile : Profile +{ + public MappingProfile() + { + CreateMap(); + + CreateMap(); + CreateMap(); + + CreateMap() + .ForMember( + dest => dest.Address, + opt => + opt.MapFrom(src => + src.Address != null + ? new AddressResponseDto( + src.Address.Id, + src.Id, + src.Address.Street, + src.Address.City, + src.Address.State, + src.Address.PostalCode, + src.Address.Country + ) + : null + ) + ); + + CreateMap() + .ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.Id)) + .ForMember(dest => dest.CartId, opt => opt.MapFrom(src => src.CartId)) + .ForMember(dest => dest.GoodId, opt => opt.MapFrom(src => src.GoodId)) + .ForMember(dest => dest.Quantity, opt => opt.MapFrom(src => src.Quantity)) + .ForMember(dest => dest.Price, opt => opt.MapFrom(src => src.Price)); + + CreateMap() + .ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.Id)) + .ForMember(dest => dest.CustomerId, opt => opt.MapFrom(src => src.CustomerId)) + .ForMember(dest => dest.CreatedDate, opt => opt.MapFrom(src => src.CreatedDate)) + .ForMember(dest => dest.UpdatedDate, opt => opt.MapFrom(src => src.UpdatedDate)); + + CreateMap() + .ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.Id)) + .ForMember(dest => dest.CustomerId, opt => opt.MapFrom(src => src.CustomerId)) + .ForMember(dest => dest.CreatedDate, opt => opt.MapFrom(src => src.CreatedDate)) + .ForMember(dest => dest.UpdatedDate, opt => opt.MapFrom(src => src.UpdatedDate)) + .ForMember(dest => dest.CartDetails, opt => opt.MapFrom(src => src.CartDetails)); + + CreateMap() + .ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.Id)); + + CreateMap() + .ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.Id)) + .ForMember(dest => dest.Date, opt => opt.MapFrom(src => src.Date)) + .ForMember(dest => dest.CustomerId, opt => opt.MapFrom(src => src.CustomerId)) + .ForMember(dest => dest.TotalPrice, opt => opt.MapFrom(src => src.TotalPrice)) + .ForMember(dest => dest.OrderDetails, opt => opt.MapFrom(src => src.OrderDetails)); + + CreateMap() + .ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.Id)) + .ForMember(dest => dest.OrderId, opt => opt.MapFrom(src => src.OrderId)) + .ForMember(dest => dest.GoodId, opt => opt.MapFrom(src => src.GoodId)) + .ForMember(dest => dest.Quantity, opt => opt.MapFrom(src => src.Quantity)) + .ForMember(dest => dest.Price, opt => opt.MapFrom(src => src.Price)); + + CreateMap() + .ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.Id)) + .ForMember(dest => dest.Date, opt => opt.MapFrom(src => src.Date)) + .ForMember(dest => dest.CustomerId, opt => opt.MapFrom(src => src.CustomerId)) + .ForMember(dest => dest.TotalPrice, opt => opt.MapFrom(src => src.TotalPrice)) + .ForMember(dest => dest.OrderDetails, opt => opt.MapFrom(src => src.OrderDetails)); + + CreateMap() + .ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.Id)); + + CreateMap(); + + CreateMap(); + CreateMap(); + + CreateMap(); + CreateMap(); + + CreateMap() + .ForMember( + dest => dest.Scopes, + opt => + opt.MapFrom(src => + src.Scopes != null ? src.Scopes : new List() + ) + ); + CreateMap() + .ForMember( + dest => dest.Scopes, + opt => opt.MapFrom(src => src.Scopes != null ? src.Scopes : new List()) + ); + + CreateMap() + .ForMember(dest => dest.ImageName, opt => opt.MapFrom(src => src.ImageName)); + + CreateMap() + .ForMember( + dest => dest.Scopes, + opt => opt.MapFrom(src => src.Scopes != null ? src.Scopes : new List()) + ); + + CreateMap() + .ForMember( + dest => dest.Scopes, + opt => + opt.MapFrom(src => + src.Scopes != null + ? src + .Scopes.Select(scope => new GoodScopeResponseDto( + scope.Id, + scope.GoodId, + scope.Litre, + scope.Price, + scope.ImageName + )) + .ToList() + : new List() + ) + ); + + CreateMap(); + CreateMap(); + + CreateMap() + .ForMember(dest => dest.Address, opt => opt.MapFrom(src => src.Address)); + + CreateMap() + .ForMember(dest => dest.Address, opt => opt.MapFrom(src => src.Address)); + + CreateMap(); + + CreateMap(); + CreateMap(); + } +} diff --git a/Topers.Api/Middleware/TaskCancellationHandlingMiddleware.cs b/Topers.Api/Middleware/TaskCancellationHandlingMiddleware.cs new file mode 100644 index 0000000..62c144b --- /dev/null +++ b/Topers.Api/Middleware/TaskCancellationHandlingMiddleware.cs @@ -0,0 +1,25 @@ +namespace Topers.Api.Middleware; + +public class TaskCancellationHandlingMiddleware +{ + private readonly RequestDelegate _next; + private readonly ILogger _logger; + + public TaskCancellationHandlingMiddleware(RequestDelegate next, ILogger logger) + { + _next = next; + _logger = logger; + } + + public async Task Invoke(HttpContext context) + { + try + { + await _next(context); + } + catch (Exception _) when (_ is OperationCanceledException or TaskCanceledException) + { + _logger.LogInformation("Task has been cancelled!"); + } + } +} \ No newline at end of file diff --git a/Topers.Api/Program.cs b/Topers.Api/Program.cs index 42af6c7..4766625 100644 --- a/Topers.Api/Program.cs +++ b/Topers.Api/Program.cs @@ -1,22 +1,21 @@ +using Topers.Api.Extensions; + var builder = WebApplication.CreateBuilder(args); { - builder.Services.AddEndpointsApiExplorer(); - builder.Services.AddSwaggerGen(); + var configuration = builder.Configuration; + var services = builder.Services; + + services.AddEndpointsApiExplorer(); + services.AddSwaggerGen(); + services.AddControllers(); + services.AddCustomServices(configuration); + services.AddCustomAuthentication(configuration); + services.AddAuthorization(); }; var app = builder.Build(); { - if (app.Environment.IsDevelopment()) - { - app.UseSwagger(); - app.UseSwaggerUI(options => - { - options.SwaggerEndpoint("/swagger/v1/swagger.json", "v1"); - options.DocumentTitle = "Topers API"; - options.RoutePrefix = string.Empty; - }); - } - - app.UseHttpsRedirection(); + app.UseCustomMiddlewares(app.Environment); + app.MapControllers(); app.Run(); } \ No newline at end of file diff --git a/Topers.Api/Topers.Api.csproj b/Topers.Api/Topers.Api.csproj index fc545ba..64030b2 100644 --- a/Topers.Api/Topers.Api.csproj +++ b/Topers.Api/Topers.Api.csproj @@ -7,8 +7,26 @@ + + + + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + diff --git a/Topers.Api/Topers.Api.http b/Topers.Api/Topers.Api.http deleted file mode 100644 index df31883..0000000 --- a/Topers.Api/Topers.Api.http +++ /dev/null @@ -1,6 +0,0 @@ -@Topers.Api_HostAddress = http://localhost:5264 - -GET {{Topers.Api_HostAddress}}/weatherforecast/ -Accept: application/json - -### diff --git a/Topers.Api/appsettings.json b/Topers.Api/appsettings.json index 4d56694..fe666cd 100644 --- a/Topers.Api/appsettings.json +++ b/Topers.Api/appsettings.json @@ -5,5 +5,15 @@ "Microsoft.AspNetCore": "Warning" } }, - "AllowedHosts": "*" + "ConnectionStrings": { + "TopersDbContext": "Host=localhost; Database=topers; Username=postgres; Password=root" + }, + "AllowedHosts": "*", + "JwtOptions": { + "SecretKey": "secretkeysecretkeysecretkeysecretkeysecretkeysecretkeysecretkeysecretkey", + "ExpiresHours": "12" + }, + "CookiesOptions": { + "Name": "topers-app" + } } diff --git a/Topers.Client/index.html b/Topers.Client/index.html index 4f698af..86dc266 100644 --- a/Topers.Client/index.html +++ b/Topers.Client/index.html @@ -2,12 +2,12 @@ - - + + Topers
- + diff --git a/Topers.Client/package-lock.json b/Topers.Client/package-lock.json index c6873fb..86097a0 100644 --- a/Topers.Client/package-lock.json +++ b/Topers.Client/package-lock.json @@ -8,8 +8,15 @@ "name": "topers-client", "version": "0.0.0", "dependencies": { + "@chakra-ui/icons": "^2.1.1", + "@chakra-ui/react": "^2.8.2", + "@emotion/react": "^11.11.4", + "@emotion/styled": "^11.11.5", + "axios": "^1.7.2", + "framer-motion": "^11.3.4", "react": "^18.3.1", - "react-dom": "^18.3.1" + "react-dom": "^18.3.1", + "react-icons": "^5.2.1" }, "devDependencies": { "@types/react": "^18.3.3", @@ -19,6 +26,7 @@ "eslint-plugin-react": "^7.34.2", "eslint-plugin-react-hooks": "^4.6.2", "eslint-plugin-react-refresh": "^0.4.7", + "typescript": "^5.5.3", "vite": "^5.3.1" } }, @@ -40,7 +48,6 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", - "dev": true, "license": "MIT", "dependencies": { "@babel/highlight": "^7.24.7", @@ -95,7 +102,6 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.7.tgz", "integrity": "sha512-oipXieGC3i45Y1A41t4tAqpnEZWgB/lC6Ehh6+rOviR5XWpTtMmLN+fGjz9vOiNRt0p6RtO6DtD0pdU3vpqdSA==", - "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.24.7", @@ -128,7 +134,6 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.7.tgz", "integrity": "sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==", - "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.24.7" @@ -141,7 +146,6 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.24.7.tgz", "integrity": "sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA==", - "dev": true, "license": "MIT", "dependencies": { "@babel/template": "^7.24.7", @@ -155,7 +159,6 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.7.tgz", "integrity": "sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ==", - "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.24.7" @@ -168,7 +171,6 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz", "integrity": "sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==", - "dev": true, "license": "MIT", "dependencies": { "@babel/traverse": "^7.24.7", @@ -226,172 +228,1600 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz", "integrity": "sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.7.tgz", + "integrity": "sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", + "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.7.tgz", + "integrity": "sha512-yy1/KvjhV/ZCL+SM7hBrvnZJ3ZuT9OuZgIJAGpPEToANvc3iM6iDvBnRjtElWibHU6n8/LPR/EjX9EtIEYO3pw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.7.tgz", + "integrity": "sha512-NlmJJtvcw72yRJRcnCmGvSi+3jDEg8qFu3z0AFoymmzLx5ERVWyzd9kVXr7Th9/8yIJi2Zc6av4Tqz3wFs8QWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.24.7", + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", + "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.24.7", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.7.tgz", + "integrity": "sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw==", + "license": "MIT", + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.24.7.tgz", + "integrity": "sha512-fOPQYbGSgH0HUp4UJO4sMBFjY6DuWq+2i8rixyUMb3CdGixs/gccURvYOAhajBdKDoGajFr3mUq5rH3phtkGzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.24.7.tgz", + "integrity": "sha512-J2z+MWzZHVOemyLweMqngXrgGC42jQ//R0KdxqkIz/OrbVIIlhFI3WigZ5fO+nwFvBlncr4MGapd8vTyc7RPNQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.24.7" + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.8.tgz", + "integrity": "sha512-5F7SDGs1T72ZczbRwbGO9lQi0NLjQxzl6i4lJxLxfW9U5UluCSyEJeniWvnhl3/euNiqQVbo8zruhsDfid0esA==", + "license": "MIT", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.7.tgz", + "integrity": "sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.24.7", + "@babel/parser": "^7.24.7", + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.7.tgz", + "integrity": "sha512-yb65Ed5S/QAcewNPh0nZczy9JdYXkkAbIsEo+P7BE7yO3txAY30Y/oPa3QkQ5It3xVG2kpKMg9MsdxZaO31uKA==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.24.7", + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-function-name": "^7.24.7", + "@babel/helper-hoist-variables": "^7.24.7", + "@babel/helper-split-export-declaration": "^7.24.7", + "@babel/parser": "^7.24.7", + "@babel/types": "^7.24.7", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.7.tgz", + "integrity": "sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.24.7", + "@babel/helper-validator-identifier": "^7.24.7", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@chakra-ui/accordion": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/accordion/-/accordion-2.3.1.tgz", + "integrity": "sha512-FSXRm8iClFyU+gVaXisOSEw0/4Q+qZbFRiuhIAkVU6Boj0FxAMrlo9a8AV5TuF77rgaHytCdHk0Ng+cyUijrag==", + "license": "MIT", + "dependencies": { + "@chakra-ui/descendant": "3.1.0", + "@chakra-ui/icon": "3.2.0", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-use-controllable-state": "2.1.0", + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5", + "@chakra-ui/transition": "2.1.0" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "framer-motion": ">=4.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/alert": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/alert/-/alert-2.2.2.tgz", + "integrity": "sha512-jHg4LYMRNOJH830ViLuicjb3F+v6iriE/2G5T+Sd0Hna04nukNJ1MxUmBPE+vI22me2dIflfelu2v9wdB6Pojw==", + "license": "MIT", + "dependencies": { + "@chakra-ui/icon": "3.2.0", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5", + "@chakra-ui/spinner": "2.1.0" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/anatomy": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/anatomy/-/anatomy-2.2.2.tgz", + "integrity": "sha512-MV6D4VLRIHr4PkW4zMyqfrNS1mPlCTiCXwvYGtDFQYr+xHFfonhAuf9WjsSc0nyp2m0OdkSLnzmVKkZFLo25Tg==", + "license": "MIT" + }, + "node_modules/@chakra-ui/avatar": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/avatar/-/avatar-2.3.0.tgz", + "integrity": "sha512-8gKSyLfygnaotbJbDMHDiJoF38OHXUYVme4gGxZ1fLnQEdPVEaIWfH+NndIjOM0z8S+YEFnT9KyGMUtvPrBk3g==", + "license": "MIT", + "dependencies": { + "@chakra-ui/image": "2.1.0", + "@chakra-ui/react-children-utils": "2.0.6", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/breadcrumb": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/breadcrumb/-/breadcrumb-2.2.0.tgz", + "integrity": "sha512-4cWCG24flYBxjruRi4RJREWTGF74L/KzI2CognAW/d/zWR0CjiScuJhf37Am3LFbCySP6WSoyBOtTIoTA4yLEA==", + "license": "MIT", + "dependencies": { + "@chakra-ui/react-children-utils": "2.0.6", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/breakpoint-utils": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@chakra-ui/breakpoint-utils/-/breakpoint-utils-2.0.8.tgz", + "integrity": "sha512-Pq32MlEX9fwb5j5xx8s18zJMARNHlQZH2VH1RZgfgRDpp7DcEgtRW5AInfN5CfqdHLO1dGxA7I3MqEuL5JnIsA==", + "license": "MIT", + "dependencies": { + "@chakra-ui/shared-utils": "2.0.5" + } + }, + "node_modules/@chakra-ui/button": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/button/-/button-2.1.0.tgz", + "integrity": "sha512-95CplwlRKmmUXkdEp/21VkEWgnwcx2TOBG6NfYlsuLBDHSLlo5FKIiE2oSi4zXc4TLcopGcWPNcm/NDaSC5pvA==", + "license": "MIT", + "dependencies": { + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5", + "@chakra-ui/spinner": "2.1.0" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/card": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/card/-/card-2.2.0.tgz", + "integrity": "sha512-xUB/k5MURj4CtPAhdSoXZidUbm8j3hci9vnc+eZJVDqhDOShNlD6QeniQNRPRys4lWAQLCbFcrwL29C8naDi6g==", + "license": "MIT", + "dependencies": { + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/checkbox": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/checkbox/-/checkbox-2.3.2.tgz", + "integrity": "sha512-85g38JIXMEv6M+AcyIGLh7igNtfpAN6KGQFYxY9tBj0eWvWk4NKQxvqqyVta0bSAyIl1rixNIIezNpNWk2iO4g==", + "license": "MIT", + "dependencies": { + "@chakra-ui/form-control": "2.2.0", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-types": "2.0.7", + "@chakra-ui/react-use-callback-ref": "2.1.0", + "@chakra-ui/react-use-controllable-state": "2.1.0", + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/react-use-safe-layout-effect": "2.1.0", + "@chakra-ui/react-use-update-effect": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5", + "@chakra-ui/visually-hidden": "2.2.0", + "@zag-js/focus-visible": "0.16.0" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/clickable": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/clickable/-/clickable-2.1.0.tgz", + "integrity": "sha512-flRA/ClPUGPYabu+/GLREZVZr9j2uyyazCAUHAdrTUEdDYCr31SVGhgh7dgKdtq23bOvAQJpIJjw/0Bs0WvbXw==", + "license": "MIT", + "dependencies": { + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/close-button": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/close-button/-/close-button-2.1.1.tgz", + "integrity": "sha512-gnpENKOanKexswSVpVz7ojZEALl2x5qjLYNqSQGbxz+aP9sOXPfUS56ebyBrre7T7exuWGiFeRwnM0oVeGPaiw==", + "license": "MIT", + "dependencies": { + "@chakra-ui/icon": "3.2.0" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/color-mode": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/color-mode/-/color-mode-2.2.0.tgz", + "integrity": "sha512-niTEA8PALtMWRI9wJ4LL0CSBDo8NBfLNp4GD6/0hstcm3IlbBHTVKxN6HwSaoNYfphDQLxCjT4yG+0BJA5tFpg==", + "license": "MIT", + "dependencies": { + "@chakra-ui/react-use-safe-layout-effect": "2.1.0" + }, + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/control-box": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/control-box/-/control-box-2.1.0.tgz", + "integrity": "sha512-gVrRDyXFdMd8E7rulL0SKeoljkLQiPITFnsyMO8EFHNZ+AHt5wK4LIguYVEq88APqAGZGfHFWXr79RYrNiE3Mg==", + "license": "MIT", + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/counter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/counter/-/counter-2.1.0.tgz", + "integrity": "sha512-s6hZAEcWT5zzjNz2JIWUBzRubo9la/oof1W7EKZVVfPYHERnl5e16FmBC79Yfq8p09LQ+aqFKm/etYoJMMgghw==", + "license": "MIT", + "dependencies": { + "@chakra-ui/number-utils": "2.0.7", + "@chakra-ui/react-use-callback-ref": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/css-reset": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/css-reset/-/css-reset-2.3.0.tgz", + "integrity": "sha512-cQwwBy5O0jzvl0K7PLTLgp8ijqLPKyuEMiDXwYzl95seD3AoeuoCLyzZcJtVqaUZ573PiBdAbY/IlZcwDOItWg==", + "license": "MIT", + "peerDependencies": { + "@emotion/react": ">=10.0.35", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/descendant": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/descendant/-/descendant-3.1.0.tgz", + "integrity": "sha512-VxCIAir08g5w27klLyi7PVo8BxhW4tgU/lxQyujkmi4zx7hT9ZdrcQLAted/dAa+aSIZ14S1oV0Q9lGjsAdxUQ==", + "license": "MIT", + "dependencies": { + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-use-merge-refs": "2.1.0" + }, + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/dom-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/dom-utils/-/dom-utils-2.1.0.tgz", + "integrity": "sha512-ZmF2qRa1QZ0CMLU8M1zCfmw29DmPNtfjR9iTo74U5FPr3i1aoAh7fbJ4qAlZ197Xw9eAW28tvzQuoVWeL5C7fQ==", + "license": "MIT" + }, + "node_modules/@chakra-ui/editable": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/editable/-/editable-3.1.0.tgz", + "integrity": "sha512-j2JLrUL9wgg4YA6jLlbU88370eCRyor7DZQD9lzpY95tSOXpTljeg3uF9eOmDnCs6fxp3zDWIfkgMm/ExhcGTg==", + "license": "MIT", + "dependencies": { + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-types": "2.0.7", + "@chakra-ui/react-use-callback-ref": "2.1.0", + "@chakra-ui/react-use-controllable-state": "2.1.0", + "@chakra-ui/react-use-focus-on-pointer-down": "2.1.0", + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/react-use-safe-layout-effect": "2.1.0", + "@chakra-ui/react-use-update-effect": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/event-utils": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@chakra-ui/event-utils/-/event-utils-2.0.8.tgz", + "integrity": "sha512-IGM/yGUHS+8TOQrZGpAKOJl/xGBrmRYJrmbHfUE7zrG3PpQyXvbLDP1M+RggkCFVgHlJi2wpYIf0QtQlU0XZfw==", + "license": "MIT" + }, + "node_modules/@chakra-ui/focus-lock": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/focus-lock/-/focus-lock-2.1.0.tgz", + "integrity": "sha512-EmGx4PhWGjm4dpjRqM4Aa+rCWBxP+Rq8Uc/nAVnD4YVqkEhBkrPTpui2lnjsuxqNaZ24fIAZ10cF1hlpemte/w==", + "license": "MIT", + "dependencies": { + "@chakra-ui/dom-utils": "2.1.0", + "react-focus-lock": "^2.9.4" + }, + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/form-control": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/form-control/-/form-control-2.2.0.tgz", + "integrity": "sha512-wehLC1t4fafCVJ2RvJQT2jyqsAwX7KymmiGqBu7nQoQz8ApTkGABWpo/QwDh3F/dBLrouHDoOvGmYTqft3Mirw==", + "license": "MIT", + "dependencies": { + "@chakra-ui/icon": "3.2.0", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-types": "2.0.7", + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/hooks": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/hooks/-/hooks-2.2.1.tgz", + "integrity": "sha512-RQbTnzl6b1tBjbDPf9zGRo9rf/pQMholsOudTxjy4i9GfTfz6kgp5ValGjQm2z7ng6Z31N1cnjZ1AlSzQ//ZfQ==", + "license": "MIT", + "dependencies": { + "@chakra-ui/react-utils": "2.0.12", + "@chakra-ui/utils": "2.0.15", + "compute-scroll-into-view": "3.0.3", + "copy-to-clipboard": "3.3.3" + }, + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/icon": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/icon/-/icon-3.2.0.tgz", + "integrity": "sha512-xxjGLvlX2Ys4H0iHrI16t74rG9EBcpFvJ3Y3B7KMQTrnW34Kf7Da/UC8J67Gtx85mTHW020ml85SVPKORWNNKQ==", + "license": "MIT", + "dependencies": { + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/icons": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/icons/-/icons-2.1.1.tgz", + "integrity": "sha512-3p30hdo4LlRZTT5CwoAJq3G9fHI0wDc0pBaMHj4SUn0yomO+RcDRlzhdXqdr5cVnzax44sqXJVnf3oQG0eI+4g==", + "license": "MIT", + "dependencies": { + "@chakra-ui/icon": "3.2.0" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/image": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/image/-/image-2.1.0.tgz", + "integrity": "sha512-bskumBYKLiLMySIWDGcz0+D9Th0jPvmX6xnRMs4o92tT3Od/bW26lahmV2a2Op2ItXeCmRMY+XxJH5Gy1i46VA==", + "license": "MIT", + "dependencies": { + "@chakra-ui/react-use-safe-layout-effect": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/input": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/input/-/input-2.1.2.tgz", + "integrity": "sha512-GiBbb3EqAA8Ph43yGa6Mc+kUPjh4Spmxp1Pkelr8qtudpc3p2PJOOebLpd90mcqw8UePPa+l6YhhPtp6o0irhw==", + "license": "MIT", + "dependencies": { + "@chakra-ui/form-control": "2.2.0", + "@chakra-ui/object-utils": "2.1.0", + "@chakra-ui/react-children-utils": "2.0.6", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/layout": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/layout/-/layout-2.3.1.tgz", + "integrity": "sha512-nXuZ6WRbq0WdgnRgLw+QuxWAHuhDtVX8ElWqcTK+cSMFg/52eVP47czYBE5F35YhnoW2XBwfNoNgZ7+e8Z01Rg==", + "license": "MIT", + "dependencies": { + "@chakra-ui/breakpoint-utils": "2.0.8", + "@chakra-ui/icon": "3.2.0", + "@chakra-ui/object-utils": "2.1.0", + "@chakra-ui/react-children-utils": "2.0.6", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/lazy-utils": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@chakra-ui/lazy-utils/-/lazy-utils-2.0.5.tgz", + "integrity": "sha512-UULqw7FBvcckQk2n3iPO56TMJvDsNv0FKZI6PlUNJVaGsPbsYxK/8IQ60vZgaTVPtVcjY6BE+y6zg8u9HOqpyg==", + "license": "MIT" + }, + "node_modules/@chakra-ui/live-region": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/live-region/-/live-region-2.1.0.tgz", + "integrity": "sha512-ZOxFXwtaLIsXjqnszYYrVuswBhnIHHP+XIgK1vC6DePKtyK590Wg+0J0slDwThUAd4MSSIUa/nNX84x1GMphWw==", + "license": "MIT", + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/media-query": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/media-query/-/media-query-3.3.0.tgz", + "integrity": "sha512-IsTGgFLoICVoPRp9ykOgqmdMotJG0CnPsKvGQeSFOB/dZfIujdVb14TYxDU4+MURXry1MhJ7LzZhv+Ml7cr8/g==", + "license": "MIT", + "dependencies": { + "@chakra-ui/breakpoint-utils": "2.0.8", + "@chakra-ui/react-env": "3.1.0", + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/menu": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/menu/-/menu-2.2.1.tgz", + "integrity": "sha512-lJS7XEObzJxsOwWQh7yfG4H8FzFPRP5hVPN/CL+JzytEINCSBvsCDHrYPQGp7jzpCi8vnTqQQGQe0f8dwnXd2g==", + "license": "MIT", + "dependencies": { + "@chakra-ui/clickable": "2.1.0", + "@chakra-ui/descendant": "3.1.0", + "@chakra-ui/lazy-utils": "2.0.5", + "@chakra-ui/popper": "3.1.0", + "@chakra-ui/react-children-utils": "2.0.6", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-use-animation-state": "2.1.0", + "@chakra-ui/react-use-controllable-state": "2.1.0", + "@chakra-ui/react-use-disclosure": "2.1.0", + "@chakra-ui/react-use-focus-effect": "2.1.0", + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/react-use-outside-click": "2.2.0", + "@chakra-ui/react-use-update-effect": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5", + "@chakra-ui/transition": "2.1.0" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "framer-motion": ">=4.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/modal": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/modal/-/modal-2.3.1.tgz", + "integrity": "sha512-TQv1ZaiJMZN+rR9DK0snx/OPwmtaGH1HbZtlYt4W4s6CzyK541fxLRTjIXfEzIGpvNW+b6VFuFjbcR78p4DEoQ==", + "license": "MIT", + "dependencies": { + "@chakra-ui/close-button": "2.1.1", + "@chakra-ui/focus-lock": "2.1.0", + "@chakra-ui/portal": "2.1.0", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-types": "2.0.7", + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5", + "@chakra-ui/transition": "2.1.0", + "aria-hidden": "^1.2.3", + "react-remove-scroll": "^2.5.6" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "framer-motion": ">=4.0.0", + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@chakra-ui/number-input": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/number-input/-/number-input-2.1.2.tgz", + "integrity": "sha512-pfOdX02sqUN0qC2ysuvgVDiws7xZ20XDIlcNhva55Jgm095xjm8eVdIBfNm3SFbSUNxyXvLTW/YQanX74tKmuA==", + "license": "MIT", + "dependencies": { + "@chakra-ui/counter": "2.1.0", + "@chakra-ui/form-control": "2.2.0", + "@chakra-ui/icon": "3.2.0", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-types": "2.0.7", + "@chakra-ui/react-use-callback-ref": "2.1.0", + "@chakra-ui/react-use-event-listener": "2.1.0", + "@chakra-ui/react-use-interval": "2.1.0", + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/react-use-safe-layout-effect": "2.1.0", + "@chakra-ui/react-use-update-effect": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/number-utils": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@chakra-ui/number-utils/-/number-utils-2.0.7.tgz", + "integrity": "sha512-yOGxBjXNvLTBvQyhMDqGU0Oj26s91mbAlqKHiuw737AXHt0aPllOthVUqQMeaYLwLCjGMg0jtI7JReRzyi94Dg==", + "license": "MIT" + }, + "node_modules/@chakra-ui/object-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/object-utils/-/object-utils-2.1.0.tgz", + "integrity": "sha512-tgIZOgLHaoti5PYGPTwK3t/cqtcycW0owaiOXoZOcpwwX/vlVb+H1jFsQyWiiwQVPt9RkoSLtxzXamx+aHH+bQ==", + "license": "MIT" + }, + "node_modules/@chakra-ui/pin-input": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/pin-input/-/pin-input-2.1.0.tgz", + "integrity": "sha512-x4vBqLStDxJFMt+jdAHHS8jbh294O53CPQJoL4g228P513rHylV/uPscYUHrVJXRxsHfRztQO9k45jjTYaPRMw==", + "license": "MIT", + "dependencies": { + "@chakra-ui/descendant": "3.1.0", + "@chakra-ui/react-children-utils": "2.0.6", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-use-controllable-state": "2.1.0", + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/popover": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/popover/-/popover-2.2.1.tgz", + "integrity": "sha512-K+2ai2dD0ljvJnlrzesCDT9mNzLifE3noGKZ3QwLqd/K34Ym1W/0aL1ERSynrcG78NKoXS54SdEzkhCZ4Gn/Zg==", + "license": "MIT", + "dependencies": { + "@chakra-ui/close-button": "2.1.1", + "@chakra-ui/lazy-utils": "2.0.5", + "@chakra-ui/popper": "3.1.0", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-types": "2.0.7", + "@chakra-ui/react-use-animation-state": "2.1.0", + "@chakra-ui/react-use-disclosure": "2.1.0", + "@chakra-ui/react-use-focus-effect": "2.1.0", + "@chakra-ui/react-use-focus-on-pointer-down": "2.1.0", + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "framer-motion": ">=4.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/popper": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/popper/-/popper-3.1.0.tgz", + "integrity": "sha512-ciDdpdYbeFG7og6/6J8lkTFxsSvwTdMLFkpVylAF6VNC22jssiWfquj2eyD4rJnzkRFPvIWJq8hvbfhsm+AjSg==", + "license": "MIT", + "dependencies": { + "@chakra-ui/react-types": "2.0.7", + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@popperjs/core": "^2.9.3" + }, + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/portal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/portal/-/portal-2.1.0.tgz", + "integrity": "sha512-9q9KWf6SArEcIq1gGofNcFPSWEyl+MfJjEUg/un1SMlQjaROOh3zYr+6JAwvcORiX7tyHosnmWC3d3wI2aPSQg==", + "license": "MIT", + "dependencies": { + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-use-safe-layout-effect": "2.1.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@chakra-ui/progress": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/progress/-/progress-2.2.0.tgz", + "integrity": "sha512-qUXuKbuhN60EzDD9mHR7B67D7p/ZqNS2Aze4Pbl1qGGZfulPW0PY8Rof32qDtttDQBkzQIzFGE8d9QpAemToIQ==", + "license": "MIT", + "dependencies": { + "@chakra-ui/react-context": "2.1.0" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/provider": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/provider/-/provider-2.4.2.tgz", + "integrity": "sha512-w0Tef5ZCJK1mlJorcSjItCSbyvVuqpvyWdxZiVQmE6fvSJR83wZof42ux0+sfWD+I7rHSfj+f9nzhNaEWClysw==", + "license": "MIT", + "dependencies": { + "@chakra-ui/css-reset": "2.3.0", + "@chakra-ui/portal": "2.1.0", + "@chakra-ui/react-env": "3.1.0", + "@chakra-ui/system": "2.6.2", + "@chakra-ui/utils": "2.0.15" + }, + "peerDependencies": { + "@emotion/react": "^11.0.0", + "@emotion/styled": "^11.0.0", + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@chakra-ui/radio": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/radio/-/radio-2.1.2.tgz", + "integrity": "sha512-n10M46wJrMGbonaghvSRnZ9ToTv/q76Szz284gv4QUWvyljQACcGrXIONUnQ3BIwbOfkRqSk7Xl/JgZtVfll+w==", + "license": "MIT", + "dependencies": { + "@chakra-ui/form-control": "2.2.0", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-types": "2.0.7", + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5", + "@zag-js/focus-visible": "0.16.0" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/react": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/react/-/react-2.8.2.tgz", + "integrity": "sha512-Hn0moyxxyCDKuR9ywYpqgX8dvjqwu9ArwpIb9wHNYjnODETjLwazgNIliCVBRcJvysGRiV51U2/JtJVrpeCjUQ==", + "license": "MIT", + "dependencies": { + "@chakra-ui/accordion": "2.3.1", + "@chakra-ui/alert": "2.2.2", + "@chakra-ui/avatar": "2.3.0", + "@chakra-ui/breadcrumb": "2.2.0", + "@chakra-ui/button": "2.1.0", + "@chakra-ui/card": "2.2.0", + "@chakra-ui/checkbox": "2.3.2", + "@chakra-ui/close-button": "2.1.1", + "@chakra-ui/control-box": "2.1.0", + "@chakra-ui/counter": "2.1.0", + "@chakra-ui/css-reset": "2.3.0", + "@chakra-ui/editable": "3.1.0", + "@chakra-ui/focus-lock": "2.1.0", + "@chakra-ui/form-control": "2.2.0", + "@chakra-ui/hooks": "2.2.1", + "@chakra-ui/icon": "3.2.0", + "@chakra-ui/image": "2.1.0", + "@chakra-ui/input": "2.1.2", + "@chakra-ui/layout": "2.3.1", + "@chakra-ui/live-region": "2.1.0", + "@chakra-ui/media-query": "3.3.0", + "@chakra-ui/menu": "2.2.1", + "@chakra-ui/modal": "2.3.1", + "@chakra-ui/number-input": "2.1.2", + "@chakra-ui/pin-input": "2.1.0", + "@chakra-ui/popover": "2.2.1", + "@chakra-ui/popper": "3.1.0", + "@chakra-ui/portal": "2.1.0", + "@chakra-ui/progress": "2.2.0", + "@chakra-ui/provider": "2.4.2", + "@chakra-ui/radio": "2.1.2", + "@chakra-ui/react-env": "3.1.0", + "@chakra-ui/select": "2.1.2", + "@chakra-ui/skeleton": "2.1.0", + "@chakra-ui/skip-nav": "2.1.0", + "@chakra-ui/slider": "2.1.0", + "@chakra-ui/spinner": "2.1.0", + "@chakra-ui/stat": "2.1.1", + "@chakra-ui/stepper": "2.3.1", + "@chakra-ui/styled-system": "2.9.2", + "@chakra-ui/switch": "2.1.2", + "@chakra-ui/system": "2.6.2", + "@chakra-ui/table": "2.1.0", + "@chakra-ui/tabs": "3.0.0", + "@chakra-ui/tag": "3.1.1", + "@chakra-ui/textarea": "2.1.2", + "@chakra-ui/theme": "3.3.1", + "@chakra-ui/theme-utils": "2.0.21", + "@chakra-ui/toast": "7.0.2", + "@chakra-ui/tooltip": "2.3.1", + "@chakra-ui/transition": "2.1.0", + "@chakra-ui/utils": "2.0.15", + "@chakra-ui/visually-hidden": "2.2.0" + }, + "peerDependencies": { + "@emotion/react": "^11.0.0", + "@emotion/styled": "^11.0.0", + "framer-motion": ">=4.0.0", + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@chakra-ui/react-children-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-children-utils/-/react-children-utils-2.0.6.tgz", + "integrity": "sha512-QVR2RC7QsOsbWwEnq9YduhpqSFnZGvjjGREV8ygKi8ADhXh93C8azLECCUVgRJF2Wc+So1fgxmjLcbZfY2VmBA==", + "license": "MIT", + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/react-context": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-context/-/react-context-2.1.0.tgz", + "integrity": "sha512-iahyStvzQ4AOwKwdPReLGfDesGG+vWJfEsn0X/NoGph/SkN+HXtv2sCfYFFR9k7bb+Kvc6YfpLlSuLvKMHi2+w==", + "license": "MIT", + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/react-env": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-env/-/react-env-3.1.0.tgz", + "integrity": "sha512-Vr96GV2LNBth3+IKzr/rq1IcnkXv+MLmwjQH6C8BRtn3sNskgDFD5vLkVXcEhagzZMCh8FR3V/bzZPojBOyNhw==", + "license": "MIT", + "dependencies": { + "@chakra-ui/react-use-safe-layout-effect": "2.1.0" + }, + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/react-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-types/-/react-types-2.0.7.tgz", + "integrity": "sha512-12zv2qIZ8EHwiytggtGvo4iLT0APris7T0qaAWqzpUGS0cdUtR8W+V1BJ5Ocq+7tA6dzQ/7+w5hmXih61TuhWQ==", + "license": "MIT", + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/react-use-animation-state": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-animation-state/-/react-use-animation-state-2.1.0.tgz", + "integrity": "sha512-CFZkQU3gmDBwhqy0vC1ryf90BVHxVN8cTLpSyCpdmExUEtSEInSCGMydj2fvn7QXsz/za8JNdO2xxgJwxpLMtg==", + "license": "MIT", + "dependencies": { + "@chakra-ui/dom-utils": "2.1.0", + "@chakra-ui/react-use-event-listener": "2.1.0" + }, + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/react-use-callback-ref": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-callback-ref/-/react-use-callback-ref-2.1.0.tgz", + "integrity": "sha512-efnJrBtGDa4YaxDzDE90EnKD3Vkh5a1t3w7PhnRQmsphLy3g2UieasoKTlT2Hn118TwDjIv5ZjHJW6HbzXA9wQ==", + "license": "MIT", + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/react-use-controllable-state": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-controllable-state/-/react-use-controllable-state-2.1.0.tgz", + "integrity": "sha512-QR/8fKNokxZUs4PfxjXuwl0fj/d71WPrmLJvEpCTkHjnzu7LnYvzoe2wB867IdooQJL0G1zBxl0Dq+6W1P3jpg==", + "license": "MIT", + "dependencies": { + "@chakra-ui/react-use-callback-ref": "2.1.0" + }, + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/react-use-disclosure": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-disclosure/-/react-use-disclosure-2.1.0.tgz", + "integrity": "sha512-Ax4pmxA9LBGMyEZJhhUZobg9C0t3qFE4jVF1tGBsrLDcdBeLR9fwOogIPY9Hf0/wqSlAryAimICbr5hkpa5GSw==", + "license": "MIT", + "dependencies": { + "@chakra-ui/react-use-callback-ref": "2.1.0" + }, + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/react-use-event-listener": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-event-listener/-/react-use-event-listener-2.1.0.tgz", + "integrity": "sha512-U5greryDLS8ISP69DKDsYcsXRtAdnTQT+jjIlRYZ49K/XhUR/AqVZCK5BkR1spTDmO9H8SPhgeNKI70ODuDU/Q==", + "license": "MIT", + "dependencies": { + "@chakra-ui/react-use-callback-ref": "2.1.0" + }, + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/react-use-focus-effect": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-focus-effect/-/react-use-focus-effect-2.1.0.tgz", + "integrity": "sha512-xzVboNy7J64xveLcxTIJ3jv+lUJKDwRM7Szwn9tNzUIPD94O3qwjV7DDCUzN2490nSYDF4OBMt/wuDBtaR3kUQ==", + "license": "MIT", + "dependencies": { + "@chakra-ui/dom-utils": "2.1.0", + "@chakra-ui/react-use-event-listener": "2.1.0", + "@chakra-ui/react-use-safe-layout-effect": "2.1.0", + "@chakra-ui/react-use-update-effect": "2.1.0" + }, + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/react-use-focus-on-pointer-down": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-focus-on-pointer-down/-/react-use-focus-on-pointer-down-2.1.0.tgz", + "integrity": "sha512-2jzrUZ+aiCG/cfanrolsnSMDykCAbv9EK/4iUyZno6BYb3vziucmvgKuoXbMPAzWNtwUwtuMhkby8rc61Ue+Lg==", + "license": "MIT", + "dependencies": { + "@chakra-ui/react-use-event-listener": "2.1.0" + }, + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/react-use-interval": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-interval/-/react-use-interval-2.1.0.tgz", + "integrity": "sha512-8iWj+I/+A0J08pgEXP1J1flcvhLBHkk0ln7ZvGIyXiEyM6XagOTJpwNhiu+Bmk59t3HoV/VyvyJTa+44sEApuw==", + "license": "MIT", + "dependencies": { + "@chakra-ui/react-use-callback-ref": "2.1.0" + }, + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/react-use-latest-ref": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-latest-ref/-/react-use-latest-ref-2.1.0.tgz", + "integrity": "sha512-m0kxuIYqoYB0va9Z2aW4xP/5b7BzlDeWwyXCH6QpT2PpW3/281L3hLCm1G0eOUcdVlayqrQqOeD6Mglq+5/xoQ==", + "license": "MIT", + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/react-use-merge-refs": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-merge-refs/-/react-use-merge-refs-2.1.0.tgz", + "integrity": "sha512-lERa6AWF1cjEtWSGjxWTaSMvneccnAVH4V4ozh8SYiN9fSPZLlSG3kNxfNzdFvMEhM7dnP60vynF7WjGdTgQbQ==", + "license": "MIT", + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/react-use-outside-click": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-outside-click/-/react-use-outside-click-2.2.0.tgz", + "integrity": "sha512-PNX+s/JEaMneijbgAM4iFL+f3m1ga9+6QK0E5Yh4s8KZJQ/bLwZzdhMz8J/+mL+XEXQ5J0N8ivZN28B82N1kNw==", + "license": "MIT", + "dependencies": { + "@chakra-ui/react-use-callback-ref": "2.1.0" + }, + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/react-use-pan-event": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-pan-event/-/react-use-pan-event-2.1.0.tgz", + "integrity": "sha512-xmL2qOHiXqfcj0q7ZK5s9UjTh4Gz0/gL9jcWPA6GVf+A0Od5imEDa/Vz+533yQKWiNSm1QGrIj0eJAokc7O4fg==", + "license": "MIT", + "dependencies": { + "@chakra-ui/event-utils": "2.0.8", + "@chakra-ui/react-use-latest-ref": "2.1.0", + "framesync": "6.1.2" + }, + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/react-use-previous": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-previous/-/react-use-previous-2.1.0.tgz", + "integrity": "sha512-pjxGwue1hX8AFcmjZ2XfrQtIJgqbTF3Qs1Dy3d1krC77dEsiCUbQ9GzOBfDc8pfd60DrB5N2tg5JyHbypqh0Sg==", + "license": "MIT", + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/react-use-safe-layout-effect": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-safe-layout-effect/-/react-use-safe-layout-effect-2.1.0.tgz", + "integrity": "sha512-Knbrrx/bcPwVS1TorFdzrK/zWA8yuU/eaXDkNj24IrKoRlQrSBFarcgAEzlCHtzuhufP3OULPkELTzz91b0tCw==", + "license": "MIT", + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/react-use-size": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-size/-/react-use-size-2.1.0.tgz", + "integrity": "sha512-tbLqrQhbnqOjzTaMlYytp7wY8BW1JpL78iG7Ru1DlV4EWGiAmXFGvtnEt9HftU0NJ0aJyjgymkxfVGI55/1Z4A==", + "license": "MIT", + "dependencies": { + "@zag-js/element-size": "0.10.5" + }, + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/react-use-timeout": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-timeout/-/react-use-timeout-2.1.0.tgz", + "integrity": "sha512-cFN0sobKMM9hXUhyCofx3/Mjlzah6ADaEl/AXl5Y+GawB5rgedgAcu2ErAgarEkwvsKdP6c68CKjQ9dmTQlJxQ==", + "license": "MIT", + "dependencies": { + "@chakra-ui/react-use-callback-ref": "2.1.0" + }, + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/react-use-update-effect": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-update-effect/-/react-use-update-effect-2.1.0.tgz", + "integrity": "sha512-ND4Q23tETaR2Qd3zwCKYOOS1dfssojPLJMLvUtUbW5M9uW1ejYWgGUobeAiOVfSplownG8QYMmHTP86p/v0lbA==", + "license": "MIT", + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/react-utils": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-utils/-/react-utils-2.0.12.tgz", + "integrity": "sha512-GbSfVb283+YA3kA8w8xWmzbjNWk14uhNpntnipHCftBibl0lxtQ9YqMFQLwuFOO0U2gYVocszqqDWX+XNKq9hw==", + "license": "MIT", + "dependencies": { + "@chakra-ui/utils": "2.0.15" + }, + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/select": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/select/-/select-2.1.2.tgz", + "integrity": "sha512-ZwCb7LqKCVLJhru3DXvKXpZ7Pbu1TDZ7N0PdQ0Zj1oyVLJyrpef1u9HR5u0amOpqcH++Ugt0f5JSmirjNlctjA==", + "license": "MIT", + "dependencies": { + "@chakra-ui/form-control": "2.2.0", + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/shared-utils": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@chakra-ui/shared-utils/-/shared-utils-2.0.5.tgz", + "integrity": "sha512-4/Wur0FqDov7Y0nCXl7HbHzCg4aq86h+SXdoUeuCMD3dSj7dpsVnStLYhng1vxvlbUnLpdF4oz5Myt3i/a7N3Q==", + "license": "MIT" + }, + "node_modules/@chakra-ui/skeleton": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/skeleton/-/skeleton-2.1.0.tgz", + "integrity": "sha512-JNRuMPpdZGd6zFVKjVQ0iusu3tXAdI29n4ZENYwAJEMf/fN0l12sVeirOxkJ7oEL0yOx2AgEYFSKdbcAgfUsAQ==", + "license": "MIT", + "dependencies": { + "@chakra-ui/media-query": "3.3.0", + "@chakra-ui/react-use-previous": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/skip-nav": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/skip-nav/-/skip-nav-2.1.0.tgz", + "integrity": "sha512-Hk+FG+vadBSH0/7hwp9LJnLjkO0RPGnx7gBJWI4/SpoJf3e4tZlWYtwGj0toYY4aGKl93jVghuwGbDBEMoHDug==", + "license": "MIT", + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/slider": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/slider/-/slider-2.1.0.tgz", + "integrity": "sha512-lUOBcLMCnFZiA/s2NONXhELJh6sY5WtbRykPtclGfynqqOo47lwWJx+VP7xaeuhDOPcWSSecWc9Y1BfPOCz9cQ==", + "license": "MIT", + "dependencies": { + "@chakra-ui/number-utils": "2.0.7", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-types": "2.0.7", + "@chakra-ui/react-use-callback-ref": "2.1.0", + "@chakra-ui/react-use-controllable-state": "2.1.0", + "@chakra-ui/react-use-latest-ref": "2.1.0", + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/react-use-pan-event": "2.1.0", + "@chakra-ui/react-use-size": "2.1.0", + "@chakra-ui/react-use-update-effect": "2.1.0" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/spinner": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/spinner/-/spinner-2.1.0.tgz", + "integrity": "sha512-hczbnoXt+MMv/d3gE+hjQhmkzLiKuoTo42YhUG7Bs9OSv2lg1fZHW1fGNRFP3wTi6OIbD044U1P9HK+AOgFH3g==", + "license": "MIT", + "dependencies": { + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/stat": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/stat/-/stat-2.1.1.tgz", + "integrity": "sha512-LDn0d/LXQNbAn2KaR3F1zivsZCewY4Jsy1qShmfBMKwn6rI8yVlbvu6SiA3OpHS0FhxbsZxQI6HefEoIgtqY6Q==", + "license": "MIT", + "dependencies": { + "@chakra-ui/icon": "3.2.0", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/stepper": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/stepper/-/stepper-2.3.1.tgz", + "integrity": "sha512-ky77lZbW60zYkSXhYz7kbItUpAQfEdycT0Q4bkHLxfqbuiGMf8OmgZOQkOB9uM4v0zPwy2HXhe0vq4Dd0xa55Q==", + "license": "MIT", + "dependencies": { + "@chakra-ui/icon": "3.2.0", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/styled-system": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/styled-system/-/styled-system-2.9.2.tgz", + "integrity": "sha512-To/Z92oHpIE+4nk11uVMWqo2GGRS86coeMmjxtpnErmWRdLcp1WVCVRAvn+ZwpLiNR+reWFr2FFqJRsREuZdAg==", + "license": "MIT", + "dependencies": { + "@chakra-ui/shared-utils": "2.0.5", + "csstype": "^3.1.2", + "lodash.mergewith": "4.6.2" + } + }, + "node_modules/@chakra-ui/switch": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/switch/-/switch-2.1.2.tgz", + "integrity": "sha512-pgmi/CC+E1v31FcnQhsSGjJnOE2OcND4cKPyTE+0F+bmGm48Q/b5UmKD9Y+CmZsrt/7V3h8KNczowupfuBfIHA==", + "license": "MIT", + "dependencies": { + "@chakra-ui/checkbox": "2.3.2", + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "framer-motion": ">=4.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/system": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/system/-/system-2.6.2.tgz", + "integrity": "sha512-EGtpoEjLrUu4W1fHD+a62XR+hzC5YfsWm+6lO0Kybcga3yYEij9beegO0jZgug27V+Rf7vns95VPVP6mFd/DEQ==", + "license": "MIT", + "dependencies": { + "@chakra-ui/color-mode": "2.2.0", + "@chakra-ui/object-utils": "2.1.0", + "@chakra-ui/react-utils": "2.0.12", + "@chakra-ui/styled-system": "2.9.2", + "@chakra-ui/theme-utils": "2.0.21", + "@chakra-ui/utils": "2.0.15", + "react-fast-compare": "3.2.2" + }, + "peerDependencies": { + "@emotion/react": "^11.0.0", + "@emotion/styled": "^11.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/table": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/table/-/table-2.1.0.tgz", + "integrity": "sha512-o5OrjoHCh5uCLdiUb0Oc0vq9rIAeHSIRScc2ExTC9Qg/uVZl2ygLrjToCaKfaaKl1oQexIeAcZDKvPG8tVkHyQ==", + "license": "MIT", + "dependencies": { + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/tabs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/tabs/-/tabs-3.0.0.tgz", + "integrity": "sha512-6Mlclp8L9lqXmsGWF5q5gmemZXOiOYuh0SGT/7PgJVNPz3LXREXlXg2an4MBUD8W5oTkduCX+3KTMCwRrVrDYw==", + "license": "MIT", + "dependencies": { + "@chakra-ui/clickable": "2.1.0", + "@chakra-ui/descendant": "3.1.0", + "@chakra-ui/lazy-utils": "2.0.5", + "@chakra-ui/react-children-utils": "2.0.6", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-use-controllable-state": "2.1.0", + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/react-use-safe-layout-effect": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/tag": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/tag/-/tag-3.1.1.tgz", + "integrity": "sha512-Bdel79Dv86Hnge2PKOU+t8H28nm/7Y3cKd4Kfk9k3lOpUh4+nkSGe58dhRzht59lEqa4N9waCgQiBdkydjvBXQ==", + "license": "MIT", + "dependencies": { + "@chakra-ui/icon": "3.2.0", + "@chakra-ui/react-context": "2.1.0" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/textarea": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/textarea/-/textarea-2.1.2.tgz", + "integrity": "sha512-ip7tvklVCZUb2fOHDb23qPy/Fr2mzDOGdkrpbNi50hDCiV4hFX02jdQJdi3ydHZUyVgZVBKPOJ+lT9i7sKA2wA==", + "license": "MIT", + "dependencies": { + "@chakra-ui/form-control": "2.2.0", + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/theme": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/theme/-/theme-3.3.1.tgz", + "integrity": "sha512-Hft/VaT8GYnItGCBbgWd75ICrIrIFrR7lVOhV/dQnqtfGqsVDlrztbSErvMkoPKt0UgAkd9/o44jmZ6X4U2nZQ==", + "license": "MIT", + "dependencies": { + "@chakra-ui/anatomy": "2.2.2", + "@chakra-ui/shared-utils": "2.0.5", + "@chakra-ui/theme-tools": "2.1.2" + }, + "peerDependencies": { + "@chakra-ui/styled-system": ">=2.8.0" + } + }, + "node_modules/@chakra-ui/theme-tools": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/theme-tools/-/theme-tools-2.1.2.tgz", + "integrity": "sha512-Qdj8ajF9kxY4gLrq7gA+Azp8CtFHGO9tWMN2wfF9aQNgG9AuMhPrUzMq9AMQ0MXiYcgNq/FD3eegB43nHVmXVA==", + "license": "MIT", + "dependencies": { + "@chakra-ui/anatomy": "2.2.2", + "@chakra-ui/shared-utils": "2.0.5", + "color2k": "^2.0.2" + }, + "peerDependencies": { + "@chakra-ui/styled-system": ">=2.0.0" + } + }, + "node_modules/@chakra-ui/theme-utils": { + "version": "2.0.21", + "resolved": "https://registry.npmjs.org/@chakra-ui/theme-utils/-/theme-utils-2.0.21.tgz", + "integrity": "sha512-FjH5LJbT794r0+VSCXB3lT4aubI24bLLRWB+CuRKHijRvsOg717bRdUN/N1fEmEpFnRVrbewttWh/OQs0EWpWw==", + "license": "MIT", + "dependencies": { + "@chakra-ui/shared-utils": "2.0.5", + "@chakra-ui/styled-system": "2.9.2", + "@chakra-ui/theme": "3.3.1", + "lodash.mergewith": "4.6.2" + } + }, + "node_modules/@chakra-ui/toast": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/toast/-/toast-7.0.2.tgz", + "integrity": "sha512-yvRP8jFKRs/YnkuE41BVTq9nB2v/KDRmje9u6dgDmE5+1bFt3bwjdf9gVbif4u5Ve7F7BGk5E093ARRVtvLvXA==", + "license": "MIT", + "dependencies": { + "@chakra-ui/alert": "2.2.2", + "@chakra-ui/close-button": "2.1.1", + "@chakra-ui/portal": "2.1.0", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-use-timeout": "2.1.0", + "@chakra-ui/react-use-update-effect": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5", + "@chakra-ui/styled-system": "2.9.2", + "@chakra-ui/theme": "3.3.1" + }, + "peerDependencies": { + "@chakra-ui/system": "2.6.2", + "framer-motion": ">=4.0.0", + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@chakra-ui/tooltip": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/tooltip/-/tooltip-2.3.1.tgz", + "integrity": "sha512-Rh39GBn/bL4kZpuEMPPRwYNnccRCL+w9OqamWHIB3Qboxs6h8cOyXfIdGxjo72lvhu1QI/a4KFqkM3St+WfC0A==", + "license": "MIT", + "dependencies": { + "@chakra-ui/dom-utils": "2.1.0", + "@chakra-ui/popper": "3.1.0", + "@chakra-ui/portal": "2.1.0", + "@chakra-ui/react-types": "2.0.7", + "@chakra-ui/react-use-disclosure": "2.1.0", + "@chakra-ui/react-use-event-listener": "2.1.0", + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "framer-motion": ">=4.0.0", + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@chakra-ui/transition": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/transition/-/transition-2.1.0.tgz", + "integrity": "sha512-orkT6T/Dt+/+kVwJNy7zwJ+U2xAZ3EU7M3XCs45RBvUnZDr/u9vdmaM/3D/rOpmQJWgQBwKPJleUXrYWUagEDQ==", + "license": "MIT", + "dependencies": { + "@chakra-ui/shared-utils": "2.0.5" }, - "engines": { - "node": ">=6.9.0" + "peerDependencies": { + "framer-motion": ">=4.0.0", + "react": ">=18" } }, - "node_modules/@babel/helper-string-parser": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.7.tgz", - "integrity": "sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg==", - "dev": true, + "node_modules/@chakra-ui/utils": { + "version": "2.0.15", + "resolved": "https://registry.npmjs.org/@chakra-ui/utils/-/utils-2.0.15.tgz", + "integrity": "sha512-El4+jL0WSaYYs+rJbuYFDbjmfCcfGDmRY95GO4xwzit6YAPZBLcR65rOEwLps+XWluZTy1xdMrusg/hW0c1aAA==", "license": "MIT", - "engines": { - "node": ">=6.9.0" + "dependencies": { + "@types/lodash.mergewith": "4.6.7", + "css-box-model": "1.2.1", + "framesync": "6.1.2", + "lodash.mergewith": "4.6.2" } }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", - "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", - "dev": true, + "node_modules/@chakra-ui/visually-hidden": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/visually-hidden/-/visually-hidden-2.2.0.tgz", + "integrity": "sha512-KmKDg01SrQ7VbTD3+cPWf/UfpF5MSwm3v7MWi0n5t8HnnadT13MF0MJCDSXbBWnzLv1ZKJ6zlyAOeARWX+DpjQ==", "license": "MIT", - "engines": { - "node": ">=6.9.0" + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" } }, - "node_modules/@babel/helper-validator-option": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.7.tgz", - "integrity": "sha512-yy1/KvjhV/ZCL+SM7hBrvnZJ3ZuT9OuZgIJAGpPEToANvc3iM6iDvBnRjtElWibHU6n8/LPR/EjX9EtIEYO3pw==", - "dev": true, + "node_modules/@emotion/babel-plugin": { + "version": "11.11.0", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz", + "integrity": "sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ==", "license": "MIT", - "engines": { - "node": ">=6.9.0" + "dependencies": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.1", + "@emotion/memoize": "^0.8.1", + "@emotion/serialize": "^1.1.2", + "babel-plugin-macros": "^3.1.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.2.0" } }, - "node_modules/@babel/helpers": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.7.tgz", - "integrity": "sha512-NlmJJtvcw72yRJRcnCmGvSi+3jDEg8qFu3z0AFoymmzLx5ERVWyzd9kVXr7Th9/8yIJi2Zc6av4Tqz3wFs8QWg==", - "dev": true, + "node_modules/@emotion/babel-plugin/node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "license": "MIT" + }, + "node_modules/@emotion/babel-plugin/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "license": "MIT", - "dependencies": { - "@babel/template": "^7.24.7", - "@babel/types": "^7.24.7" - }, "engines": { - "node": ">=6.9.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@babel/highlight": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", - "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", - "dev": true, + "node_modules/@emotion/cache": { + "version": "11.11.0", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.11.0.tgz", + "integrity": "sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ==", "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.24.7", - "chalk": "^2.4.2", - "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" - }, - "engines": { - "node": ">=6.9.0" + "@emotion/memoize": "^0.8.1", + "@emotion/sheet": "^1.2.2", + "@emotion/utils": "^1.2.1", + "@emotion/weak-memoize": "^0.3.1", + "stylis": "4.2.0" } }, - "node_modules/@babel/parser": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.7.tgz", - "integrity": "sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw==", - "dev": true, - "license": "MIT", - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } + "node_modules/@emotion/hash": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.1.tgz", + "integrity": "sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ==", + "license": "MIT" }, - "node_modules/@babel/plugin-transform-react-jsx-self": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.24.7.tgz", - "integrity": "sha512-fOPQYbGSgH0HUp4UJO4sMBFjY6DuWq+2i8rixyUMb3CdGixs/gccURvYOAhajBdKDoGajFr3mUq5rH3phtkGzw==", - "dev": true, + "node_modules/@emotion/is-prop-valid": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.2.tgz", + "integrity": "sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@emotion/memoize": "^0.8.1" } }, - "node_modules/@babel/plugin-transform-react-jsx-source": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.24.7.tgz", - "integrity": "sha512-J2z+MWzZHVOemyLweMqngXrgGC42jQ//R0KdxqkIz/OrbVIIlhFI3WigZ5fO+nwFvBlncr4MGapd8vTyc7RPNQ==", - "dev": true, + "node_modules/@emotion/memoize": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz", + "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==", + "license": "MIT" + }, + "node_modules/@emotion/react": { + "version": "11.11.4", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.11.4.tgz", + "integrity": "sha512-t8AjMlF0gHpvvxk5mAtCqR4vmxiGHCeJBaQO6gncUSdklELOgtwjerNY2yuJNfwnc6vi16U/+uMF+afIawJ9iw==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.11.0", + "@emotion/cache": "^11.11.0", + "@emotion/serialize": "^1.1.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.0.1", + "@emotion/utils": "^1.2.1", + "@emotion/weak-memoize": "^0.3.1", + "hoist-non-react-statics": "^3.3.1" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, - "node_modules/@babel/template": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.7.tgz", - "integrity": "sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==", - "dev": true, + "node_modules/@emotion/serialize": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.1.4.tgz", + "integrity": "sha512-RIN04MBT8g+FnDwgvIUi8czvr1LU1alUMI05LekWB5DGyTm8cCBMCRpq3GqaiyEDRptEXOyXnvZ58GZYu4kBxQ==", "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.24.7", - "@babel/parser": "^7.24.7", - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" + "@emotion/hash": "^0.9.1", + "@emotion/memoize": "^0.8.1", + "@emotion/unitless": "^0.8.1", + "@emotion/utils": "^1.2.1", + "csstype": "^3.0.2" } }, - "node_modules/@babel/traverse": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.7.tgz", - "integrity": "sha512-yb65Ed5S/QAcewNPh0nZczy9JdYXkkAbIsEo+P7BE7yO3txAY30Y/oPa3QkQ5It3xVG2kpKMg9MsdxZaO31uKA==", - "dev": true, + "node_modules/@emotion/sheet": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.2.2.tgz", + "integrity": "sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA==", + "license": "MIT" + }, + "node_modules/@emotion/styled": { + "version": "11.11.5", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.11.5.tgz", + "integrity": "sha512-/ZjjnaNKvuMPxcIiUkf/9SHoG4Q196DRl1w82hQ3WCsjo1IUR8uaGWrC6a87CrYAW0Kb/pK7hk8BnLgLRi9KoQ==", "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.24.7", - "@babel/generator": "^7.24.7", - "@babel/helper-environment-visitor": "^7.24.7", - "@babel/helper-function-name": "^7.24.7", - "@babel/helper-hoist-variables": "^7.24.7", - "@babel/helper-split-export-declaration": "^7.24.7", - "@babel/parser": "^7.24.7", - "@babel/types": "^7.24.7", - "debug": "^4.3.1", - "globals": "^11.1.0" + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.11.0", + "@emotion/is-prop-valid": "^1.2.2", + "@emotion/serialize": "^1.1.4", + "@emotion/use-insertion-effect-with-fallbacks": "^1.0.1", + "@emotion/utils": "^1.2.1" }, - "engines": { - "node": ">=6.9.0" + "peerDependencies": { + "@emotion/react": "^11.0.0-rc.0", + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, - "node_modules/@babel/types": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.7.tgz", - "integrity": "sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q==", - "dev": true, + "node_modules/@emotion/unitless": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz", + "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==", + "license": "MIT" + }, + "node_modules/@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.1.tgz", + "integrity": "sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw==", "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.24.7", - "@babel/helper-validator-identifier": "^7.24.7", - "to-fast-properties": "^2.0.0" - }, - "engines": { - "node": ">=6.9.0" + "peerDependencies": { + "react": ">=16.8.0" } }, + "node_modules/@emotion/utils": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.2.1.tgz", + "integrity": "sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg==", + "license": "MIT" + }, + "node_modules/@emotion/weak-memoize": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz", + "integrity": "sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==", + "license": "MIT" + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", @@ -901,7 +2331,6 @@ "version": "0.3.5", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/set-array": "^1.2.1", @@ -916,7 +2345,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.0.0" @@ -926,7 +2354,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.0.0" @@ -936,14 +2363,12 @@ "version": "1.4.15", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", - "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.25", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -988,6 +2413,16 @@ "node": ">= 8" } }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.18.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.18.0.tgz", @@ -1264,18 +2699,39 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/lodash": { + "version": "4.17.7", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.7.tgz", + "integrity": "sha512-8wTvZawATi/lsmNu10/j2hk1KEP0IvjubqPE3cu1Xz7xfXXt5oCq3SNUz4fMIP4XGF9Ky+Ue2tBA3hcS7LSBlA==", + "license": "MIT" + }, + "node_modules/@types/lodash.mergewith": { + "version": "4.6.7", + "resolved": "https://registry.npmjs.org/@types/lodash.mergewith/-/lodash.mergewith-4.6.7.tgz", + "integrity": "sha512-3m+lkO5CLRRYU0fhGRp7zbsGi6+BZj0uTVSwvcKU+nSlhjA9/QRNfuSGnD2mX6hQA7ZbmcCkzk5h4ZYGOtk14A==", + "license": "MIT", + "dependencies": { + "@types/lodash": "*" + } + }, + "node_modules/@types/parse-json": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", + "license": "MIT" + }, "node_modules/@types/prop-types": { "version": "15.7.12", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz", "integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/@types/react": { "version": "18.3.3", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.3.tgz", "integrity": "sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@types/prop-types": "*", @@ -1319,6 +2775,27 @@ "vite": "^4.2.0 || ^5.0.0" } }, + "node_modules/@zag-js/dom-query": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/@zag-js/dom-query/-/dom-query-0.16.0.tgz", + "integrity": "sha512-Oqhd6+biWyKnhKwFFuZrrf6lxBz2tX2pRQe6grUnYwO6HJ8BcbqZomy2lpOdr+3itlaUqx+Ywj5E5ZZDr/LBfQ==", + "license": "MIT" + }, + "node_modules/@zag-js/element-size": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/@zag-js/element-size/-/element-size-0.10.5.tgz", + "integrity": "sha512-uQre5IidULANvVkNOBQ1tfgwTQcGl4hliPSe69Fct1VfYb2Fd0jdAcGzqQgPhfrXFpR62MxLPB7erxJ/ngtL8w==", + "license": "MIT" + }, + "node_modules/@zag-js/focus-visible": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/@zag-js/focus-visible/-/focus-visible-0.16.0.tgz", + "integrity": "sha512-a7U/HSopvQbrDU4GLerpqiMcHKEkQkNPeDZJWz38cw/6Upunh41GjHetq5TB84hxyCaDzJ6q2nEdNoBQfC0FKA==", + "license": "MIT", + "dependencies": { + "@zag-js/dom-query": "0.16.0" + } + }, "node_modules/acorn": { "version": "8.12.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", @@ -1373,7 +2850,6 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, "license": "MIT", "dependencies": { "color-convert": "^1.9.0" @@ -1389,6 +2865,18 @@ "dev": true, "license": "Python-2.0" }, + "node_modules/aria-hidden": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.4.tgz", + "integrity": "sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/array-buffer-byte-length": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", @@ -1539,6 +3027,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, "node_modules/available-typed-arrays": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", @@ -1555,6 +3049,49 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/axios": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz", + "integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + } + }, + "node_modules/babel-plugin-macros/node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "license": "MIT", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -1630,7 +3167,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -1661,7 +3197,6 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^3.2.1", @@ -1676,7 +3211,6 @@ "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, "license": "MIT", "dependencies": { "color-name": "1.1.3" @@ -1686,7 +3220,30 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true, + "license": "MIT" + }, + "node_modules/color2k": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/color2k/-/color2k-2.0.3.tgz", + "integrity": "sha512-zW190nQTIoXcGCaU08DvVNFTmQhUpnJfVuAKfWqUQkflXKpaDdpaYoM0iluLS9lgJNHyBF58KKA2FBEwkD7wog==", + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/compute-scroll-into-view": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-3.0.3.tgz", + "integrity": "sha512-nadqwNxghAGTamwIqQSG433W6OADZx2vCo3UXHNrzTRHK/htu+7+L0zhjEoaeaQVNAi3YgqWDv8+tzf0hRfR+A==", "license": "MIT" }, "node_modules/concat-map": { @@ -1703,6 +3260,31 @@ "dev": true, "license": "MIT" }, + "node_modules/copy-to-clipboard": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz", + "integrity": "sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==", + "license": "MIT", + "dependencies": { + "toggle-selection": "^1.0.6" + } + }, + "node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "license": "MIT", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -1718,11 +3300,19 @@ "node": ">= 8" } }, + "node_modules/css-box-model": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz", + "integrity": "sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==", + "license": "MIT", + "dependencies": { + "tiny-invariant": "^1.0.6" + } + }, "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true, "license": "MIT" }, "node_modules/data-view-buffer": { @@ -1783,7 +3373,6 @@ "version": "4.3.5", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", - "dev": true, "license": "MIT", "dependencies": { "ms": "2.1.2" @@ -1840,6 +3429,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/detect-node-es": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", + "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", + "license": "MIT" + }, "node_modules/doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -1860,6 +3464,15 @@ "dev": true, "license": "ISC" }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, "node_modules/es-abstract": { "version": "1.23.3", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz", @@ -2079,7 +3692,6 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.8.0" @@ -2453,6 +4065,12 @@ "node": "^10.12.0 || >=12.0.0" } }, + "node_modules/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", + "license": "MIT" + }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -2492,6 +4110,38 @@ "dev": true, "license": "ISC" }, + "node_modules/focus-lock": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/focus-lock/-/focus-lock-1.3.5.tgz", + "integrity": "sha512-QFaHbhv9WPUeLYBDe/PAuLKJ4Dd9OPvKs9xZBr3yLXnUrDNaVXKu2baDBXe3naPY30hgHYSsf2JW4jzas2mDEQ==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/for-each": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", @@ -2502,6 +4152,60 @@ "is-callable": "^1.1.3" } }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/framer-motion": { + "version": "11.3.4", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.3.4.tgz", + "integrity": "sha512-LC+luwNmz4zEg0AU0rol3yLUFKSJ9GDmIyvigXBAwEbUBG59EWmcRQ2Xh+0py7IkmvWxFUH0TN42v1Dda8xgUg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0", + "react-dom": "^18.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/framesync": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/framesync/-/framesync-6.1.2.tgz", + "integrity": "sha512-jBTqhX6KaQVDyus8muwZbBeGGP0XgujBRbQ7gM7BRdS3CadCZIHiawyzYLnafYcvZIh5j8WE7cxZKFn7dXhu9g==", + "license": "MIT", + "dependencies": { + "tslib": "2.4.0" + } + }, + "node_modules/framesync/node_modules/tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", + "license": "0BSD" + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -2528,7 +4232,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -2593,6 +4296,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-nonce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", + "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/get-symbol-description": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", @@ -2650,7 +4362,6 @@ "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, "license": "MIT", "engines": { "node": ">=4" @@ -2707,7 +4418,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, "license": "MIT", "engines": { "node": ">=4" @@ -2772,7 +4482,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -2781,6 +4490,15 @@ "node": ">= 0.4" } }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "license": "BSD-3-Clause", + "dependencies": { + "react-is": "^16.7.0" + } + }, "node_modules/ignore": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", @@ -2795,7 +4513,6 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, "license": "MIT", "dependencies": { "parent-module": "^1.0.0", @@ -2852,6 +4569,15 @@ "node": ">= 0.4" } }, + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, "node_modules/is-array-buffer": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", @@ -2869,6 +4595,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "license": "MIT" + }, "node_modules/is-async-function": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.0.0.tgz", @@ -2932,7 +4664,6 @@ "version": "2.14.0", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.14.0.tgz", "integrity": "sha512-a5dFJih5ZLYlRtDc0dZWP7RiKr6xIKzmn/oAYCDvdLThadVgyJwlaoQPmRtMSpz+rk0OGAgIu+TcM9HUF0fk1A==", - "dev": true, "license": "MIT", "dependencies": { "hasown": "^2.0.2" @@ -3268,7 +4999,6 @@ "version": "2.5.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true, "license": "MIT", "bin": { "jsesc": "bin/jsesc" @@ -3284,6 +5014,12 @@ "dev": true, "license": "MIT" }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "license": "MIT" + }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -3351,6 +5087,12 @@ "node": ">= 0.8.0" } }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "license": "MIT" + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -3374,6 +5116,12 @@ "dev": true, "license": "MIT" }, + "node_modules/lodash.mergewith": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", + "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==", + "license": "MIT" + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -3396,6 +5144,27 @@ "yallist": "^3.0.2" } }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -3413,7 +5182,6 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true, "license": "MIT" }, "node_modules/nanoid": { @@ -3453,7 +5221,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -3635,7 +5402,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, "license": "MIT", "dependencies": { "callsites": "^3.0.0" @@ -3644,6 +5410,24 @@ "node": ">=6" } }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -3678,14 +5462,21 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true, "license": "MIT" }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/picocolors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", - "dev": true, "license": "ISC" }, "node_modules/possible-typed-array-names": { @@ -3741,7 +5532,6 @@ "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "dev": true, "license": "MIT", "dependencies": { "loose-envify": "^1.4.0", @@ -3749,6 +5539,12 @@ "react-is": "^16.13.1" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -3792,6 +5588,18 @@ "node": ">=0.10.0" } }, + "node_modules/react-clientside-effect": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/react-clientside-effect/-/react-clientside-effect-1.2.6.tgz", + "integrity": "sha512-XGGGRQAKY+q25Lz9a/4EPqom7WRjz3z9R2k4jhVKA/puQFH/5Nt27vFZYql4m4NVNdUvX8PS3O7r/Zzm7cjUlg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.13" + }, + "peerDependencies": { + "react": "^15.3.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/react-dom": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", @@ -3805,11 +5613,48 @@ "react": "^18.3.1" } }, + "node_modules/react-fast-compare": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz", + "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==", + "license": "MIT" + }, + "node_modules/react-focus-lock": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/react-focus-lock/-/react-focus-lock-2.12.1.tgz", + "integrity": "sha512-lfp8Dve4yJagkHiFrC1bGtib3mF2ktqwPJw4/WGcgPW+pJ/AVQA5X2vI7xgp13FcxFEpYBBHpXai/N2DBNC0Jw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.0.0", + "focus-lock": "^1.3.5", + "prop-types": "^15.6.2", + "react-clientside-effect": "^1.2.6", + "use-callback-ref": "^1.3.2", + "use-sidecar": "^1.1.2" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-icons": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.2.1.tgz", + "integrity": "sha512-zdbW5GstTzXaVKvGSyTaBalt7HSfuK5ovrzlpyiWHAFXndXTdd/1hdDHI4xBM1Mn7YriT6aqESucFl9kEXzrdw==", + "license": "MIT", + "peerDependencies": { + "react": "*" + } + }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true, "license": "MIT" }, "node_modules/react-refresh": { @@ -3822,6 +5667,76 @@ "node": ">=0.10.0" } }, + "node_modules/react-remove-scroll": { + "version": "2.5.10", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.10.tgz", + "integrity": "sha512-m3zvBRANPBw3qxVVjEIPEQinkcwlFZ4qyomuWVpNJdv4c6MvHfXV0C3L9Jx5rr3HeBHKNRX+1jreB5QloDIJjA==", + "license": "MIT", + "dependencies": { + "react-remove-scroll-bar": "^2.3.6", + "react-style-singleton": "^2.2.1", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.0", + "use-sidecar": "^1.1.2" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-remove-scroll-bar": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.6.tgz", + "integrity": "sha512-DtSYaao4mBmX+HDo5YWYdBWQwYIQQshUV/dVxFxK+KM26Wjwp1gZ6rv6OC3oujI6Bfu6Xyg3TwK533AQutsn/g==", + "license": "MIT", + "dependencies": { + "react-style-singleton": "^2.2.1", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-style-singleton": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz", + "integrity": "sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==", + "license": "MIT", + "dependencies": { + "get-nonce": "^1.0.0", + "invariant": "^2.2.4", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/reflect.getprototypeof": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.6.tgz", @@ -3844,6 +5759,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "license": "MIT" + }, "node_modules/regexp.prototype.flags": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", @@ -3885,7 +5806,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, "license": "MIT", "engines": { "node": ">=4" @@ -4111,6 +6031,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-js": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", @@ -4226,11 +6155,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==", + "license": "MIT" + }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, "license": "MIT", "dependencies": { "has-flag": "^3.0.0" @@ -4243,7 +6177,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -4259,16 +6192,33 @@ "dev": true, "license": "MIT" }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "license": "MIT" + }, "node_modules/to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "dev": true, "license": "MIT", "engines": { "node": ">=4" } }, + "node_modules/toggle-selection": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz", + "integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==", + "license": "MIT" + }, + "node_modules/tslib": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "license": "0BSD" + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -4372,6 +6322,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/typescript": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.3.tgz", + "integrity": "sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "node_modules/unbox-primitive": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", @@ -4429,6 +6393,49 @@ "punycode": "^2.1.0" } }, + "node_modules/use-callback-ref": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.2.tgz", + "integrity": "sha512-elOQwe6Q8gqZgDA8mrh44qRTQqpIHDcZ3hXTLjBe1i4ph8XpNJnO+aQf3NaG+lriLopI4HMx9VjQLfPQ6vhnoA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sidecar": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.2.tgz", + "integrity": "sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==", + "license": "MIT", + "dependencies": { + "detect-node-es": "^1.1.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.9.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/vite": { "version": "5.3.3", "resolved": "https://registry.npmjs.org/vite/-/vite-5.3.3.tgz", @@ -4608,6 +6615,15 @@ "dev": true, "license": "ISC" }, + "node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/Topers.Client/package.json b/Topers.Client/package.json index eb3fe7c..6533ece 100644 --- a/Topers.Client/package.json +++ b/Topers.Client/package.json @@ -10,8 +10,15 @@ "preview": "vite preview" }, "dependencies": { + "@chakra-ui/icons": "^2.1.1", + "@chakra-ui/react": "^2.8.2", + "@emotion/react": "^11.11.4", + "@emotion/styled": "^11.11.5", + "axios": "^1.7.2", + "framer-motion": "^11.3.4", "react": "^18.3.1", - "react-dom": "^18.3.1" + "react-dom": "^18.3.1", + "react-icons": "^5.2.1" }, "devDependencies": { "@types/react": "^18.3.3", @@ -21,6 +28,7 @@ "eslint-plugin-react": "^7.34.2", "eslint-plugin-react-hooks": "^4.6.2", "eslint-plugin-react-refresh": "^0.4.7", + "typescript": "^5.5.3", "vite": "^5.3.1" } } diff --git a/Topers.Client/src/App.jsx b/Topers.Client/src/App.jsx deleted file mode 100644 index 5c4d3c1..0000000 --- a/Topers.Client/src/App.jsx +++ /dev/null @@ -1,9 +0,0 @@ -import { useState } from 'react' - -function App() { - return ( - <> - ) -} - -export default App diff --git a/Topers.Client/src/app/images/logo.svg b/Topers.Client/src/app/images/logo.svg new file mode 100644 index 0000000..46a4041 --- /dev/null +++ b/Topers.Client/src/app/images/logo.svg @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Topers.Client/src/app/images/site_logo.svg b/Topers.Client/src/app/images/site_logo.svg new file mode 100644 index 0000000..f8fe2a4 --- /dev/null +++ b/Topers.Client/src/app/images/site_logo.svg @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Topers.Client/src/app/images/stock_bg_hands.jpg b/Topers.Client/src/app/images/stock_bg_hands.jpg new file mode 100644 index 0000000..0a18b07 Binary files /dev/null and b/Topers.Client/src/app/images/stock_bg_hands.jpg differ diff --git a/Topers.Client/src/app/styles/Header.css b/Topers.Client/src/app/styles/Header.css new file mode 100644 index 0000000..936c2ce --- /dev/null +++ b/Topers.Client/src/app/styles/Header.css @@ -0,0 +1,11 @@ +.navlinks, .shop-cart-button { + cursor: pointer; +} + +.navbar-links { + gap: 30px; +} + +.active { + color: #dc3d3d; +} \ No newline at end of file diff --git a/Topers.Client/src/app/styles/index.css b/Topers.Client/src/app/styles/index.css new file mode 100644 index 0000000..03afaff --- /dev/null +++ b/Topers.Client/src/app/styles/index.css @@ -0,0 +1,4 @@ +body { + margin: 0; + padding: 0; +} \ No newline at end of file diff --git a/Topers.Client/src/entities/Good.ts b/Topers.Client/src/entities/Good.ts new file mode 100644 index 0000000..a4d85e4 --- /dev/null +++ b/Topers.Client/src/entities/Good.ts @@ -0,0 +1,8 @@ +import { Scope } from "./Scope"; + +export interface Good { + id: string; + name: string; + description: string; + scopes: Scope[] +} \ No newline at end of file diff --git a/Topers.Client/src/entities/Scope.ts b/Topers.Client/src/entities/Scope.ts new file mode 100644 index 0000000..68d0856 --- /dev/null +++ b/Topers.Client/src/entities/Scope.ts @@ -0,0 +1,7 @@ +export interface Scope { + id: string; + goodId: string, + litre: number; + price: number; + imageName: string; +} \ No newline at end of file diff --git a/Topers.Client/src/features/Goods.ts b/Topers.Client/src/features/Goods.ts new file mode 100644 index 0000000..3fa749b --- /dev/null +++ b/Topers.Client/src/features/Goods.ts @@ -0,0 +1,44 @@ +import axios from "axios"; +import { Good } from "../entities/Good"; + +const baseUrl = 'http://localhost:5264'; + +export const GetAllGoods = async () => { + try { + const response = await axios.get(`${baseUrl}/api/goods`) + return response.data; + } catch (error) { + console.error("Error in receiving goods: ", error); + throw error; + } +}; + +export const GetGoodById = async (id: string) => { + try { + const response = await axios.get(`${baseUrl}/api/goods/${id}`) + return response.data; + } catch (error) { + console.error("Error in receiving good: ", error); + throw error; + } +}; + +export const UpdateGoodById = async (id: string, data: Good) => { + try { + const response = await axios.put(`${baseUrl}/api/goods/${id}`, data) + return response.data; + } catch (error) { + console.error("Error in updating good: ", error); + throw error; + } +}; + +export const DeleteGoodsById = async (id: string) => { + try { + const response = await axios.delete(`${baseUrl}/api/goods/${id}`) + return response.data; + } catch (error) { + console.error("Error in deleting good: ", error); + throw error; + } +}; \ No newline at end of file diff --git a/Topers.Client/src/index.css b/Topers.Client/src/index.css deleted file mode 100644 index e69de29..0000000 diff --git a/Topers.Client/src/main.jsx b/Topers.Client/src/main.jsx deleted file mode 100644 index 54b39dd..0000000 --- a/Topers.Client/src/main.jsx +++ /dev/null @@ -1,10 +0,0 @@ -import React from 'react' -import ReactDOM from 'react-dom/client' -import App from './App.jsx' -import './index.css' - -ReactDOM.createRoot(document.getElementById('root')).render( - - - , -) diff --git a/Topers.Client/src/main.tsx b/Topers.Client/src/main.tsx new file mode 100644 index 0000000..5233105 --- /dev/null +++ b/Topers.Client/src/main.tsx @@ -0,0 +1,10 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' +import App from './pages/MainPage' +import './app/styles/index.css' + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + , +) diff --git a/Topers.Client/src/pages/MainPage.tsx b/Topers.Client/src/pages/MainPage.tsx new file mode 100644 index 0000000..b8b4a8b --- /dev/null +++ b/Topers.Client/src/pages/MainPage.tsx @@ -0,0 +1,30 @@ +import Header from '../widgets/Header' +import { ChakraProvider, Box } from '@chakra-ui/react' +import HeroSection from '../shared/HeroSection' +import GoodsSection from '../shared/GoodsSection' + + +function MainPage() { + return ( + + + +
+ + + + + + + + + + ) +} + +export default MainPage diff --git a/Topers.Client/src/shared/Cart.jsx b/Topers.Client/src/shared/Cart.jsx new file mode 100644 index 0000000..fc8c0da --- /dev/null +++ b/Topers.Client/src/shared/Cart.jsx @@ -0,0 +1,29 @@ +import React from "react"; +import { CiPillsBottle1 } from "react-icons/ci"; +import { Text, Box, IconButton, Icon, Flex } from '@chakra-ui/react'; + +export default function Cart({ cartItemCount, cartTotalPrice, cartOpen, setCartOpen }) { + return ( + + + } + variant="transparent" + size="lg" + fontSize="25px" + onClick={() => setCartOpen(!cartOpen)} + className={cartOpen ? 'active' : ''} + /> + {cartItemCount > 0 && ( + + {cartTotalPrice} £ + + )} + + + ); +} diff --git a/Topers.Client/src/shared/GoodsSection.tsx b/Topers.Client/src/shared/GoodsSection.tsx new file mode 100644 index 0000000..eb28a71 --- /dev/null +++ b/Topers.Client/src/shared/GoodsSection.tsx @@ -0,0 +1,51 @@ +import React, { useEffect, useState } from 'react'; +import { GoodCard } from '../widgets/GoodCard'; +import { Box, Flex, Grid, Heading } from '@chakra-ui/react'; +import { Good } from '../entities/Good'; +import { GetAllGoods } from '../features/Goods'; + +export default function GoodsSection() { + const [goods, setGoods] = useState([]); + const baseUrl = 'http://localhost:5264'; + + useEffect(() => { + const fetchGoods = async() => { + const goodsData = await GetAllGoods(); + setGoods(goodsData); + } + fetchGoods(); + }, []); + + return ( + + Our goods + + {goods.map(good => ( + + + + {good.scopes.map(scope => ( + + + + ))} + + + + ))} + + + ); +} diff --git a/Topers.Client/src/shared/HeroSection.tsx b/Topers.Client/src/shared/HeroSection.tsx new file mode 100644 index 0000000..a20430a --- /dev/null +++ b/Topers.Client/src/shared/HeroSection.tsx @@ -0,0 +1,54 @@ +import React from "react"; +import { Box, Flex, Text, Heading, Button, Image } from "@chakra-ui/react"; +import { ArrowForwardIcon } from "@chakra-ui/icons"; +import handsImage from "../app/images/stock_bg_hands.jpg"; + +export default function HeroSection() { + return ( + + + + A reliable helper for your plant + + + TOPERS is a manufacturer and supplier of complex water-soluble + fertilisers for good and safe plant growth + + + + + Hands + + + ); +} diff --git a/Topers.Client/src/widgets/AuthDrawer.tsx b/Topers.Client/src/widgets/AuthDrawer.tsx new file mode 100644 index 0000000..21c762d --- /dev/null +++ b/Topers.Client/src/widgets/AuthDrawer.tsx @@ -0,0 +1,81 @@ +import { AddIcon } from "@chakra-ui/icons"; +import { + Box, + Button, + Drawer, + DrawerBody, + DrawerCloseButton, + DrawerContent, + DrawerFooter, + DrawerHeader, + DrawerOverlay, + Flex, + FormLabel, + Input, + InputGroup, + InputLeftAddon, + InputRightAddon, + Select, + Stack, + Textarea, + useDisclosure, +} from "@chakra-ui/react"; +import React from "react"; + +interface AuthDrawerProps { + isOpen: boolean; + onClose: () => void; +} + +const AuthDrawer: React.FC = ({ isOpen, onClose }) => { + const firstField = React.useRef(null); + + return ( + + + + + + Create a new account + + + + + + Username + + + + + Password + + + + + + + + + + + + + ); +}; + +export default AuthDrawer; \ No newline at end of file diff --git a/Topers.Client/src/widgets/GoodCard.tsx b/Topers.Client/src/widgets/GoodCard.tsx new file mode 100644 index 0000000..58031ac --- /dev/null +++ b/Topers.Client/src/widgets/GoodCard.tsx @@ -0,0 +1,66 @@ +import { + Card, + CardBody, + Image, + Text, + Heading, + Stack, + ButtonGroup, + Button, + CardFooter, + Divider, +} from "@chakra-ui/react"; +import React from "react"; + +interface GoodCardProps { + goodId: string; + goodTitle: string; + goodDescription: string; + goodLitre: number; + goodImage: string; + goodPrice: number; +} + +export const GoodCard = ({ + goodId, + goodTitle, + goodDescription, + goodLitre, + goodImage, + goodPrice +}: GoodCardProps) => { + return ( + + + {goodTitle} + + {goodTitle} ({goodLitre} litre) + + {goodDescription} + + + {goodPrice} $ + + + + + + + + + + + + ); +}; \ No newline at end of file diff --git a/Topers.Client/src/widgets/GoodModal.tsx b/Topers.Client/src/widgets/GoodModal.tsx new file mode 100644 index 0000000..81c9ec8 --- /dev/null +++ b/Topers.Client/src/widgets/GoodModal.tsx @@ -0,0 +1,49 @@ +import { Button, FormControl, FormLabel, Input, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay, useDisclosure } from "@chakra-ui/react" +import React from "react" + +function InitialFocus() { + const { isOpen, onOpen, onClose } = useDisclosure() + + const initialRef = React.useRef(null) + const finalRef = React.useRef(null) + + return ( + <> + + + + + + + Create your account + + + + First name + + + + + Last name + + + + + + + + + + + + ) + } \ No newline at end of file diff --git a/Topers.Client/src/widgets/Header.tsx b/Topers.Client/src/widgets/Header.tsx new file mode 100644 index 0000000..cee7202 --- /dev/null +++ b/Topers.Client/src/widgets/Header.tsx @@ -0,0 +1,91 @@ +import React, { useState } from 'react'; +import { Image, Box, Divider, Button, Flex, Link, IconButton, Collapse, VStack, useDisclosure } from '@chakra-ui/react'; +import Logo from '../app/images/logo.svg' +import Cart from '../shared/Cart'; +import { HamburgerIcon } from '@chakra-ui/icons'; +import AuthDrawer from './AuthDrawer'; + +export default function Header() { + const [cartOpen, setCartOpen] = useState(false); + const [cartItemCount, setCartItemCount] = useState(1); + const [cartTotalPrice, setCartTotalPrice] = useState(100); + const { isOpen, onOpen, onClose } = useDisclosure(); + + return ( +
+ + + + + Topers Logo + + + + About + + + Goods + + + Contacts + + + + + + + + + + } + display={{ base: 'block', md: 'none' }} + /> + + + + + + About + + + + + Goods + + + + + Contacts + + + + + + + + +
+ ); +} diff --git a/Topers.Client/tsconfig.json b/Topers.Client/tsconfig.json new file mode 100644 index 0000000..9c10f12 --- /dev/null +++ b/Topers.Client/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noEmit": true, + "esModuleInterop": true, + "module": "commonjs", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "react-jsx" + }, + "include": ["src"], + "exclude": ["node_modules", "build"] +} diff --git a/Topers.Core/Abstractions/IAddressesRepository.cs b/Topers.Core/Abstractions/IAddressesRepository.cs new file mode 100644 index 0000000..b36aff5 --- /dev/null +++ b/Topers.Core/Abstractions/IAddressesRepository.cs @@ -0,0 +1,8 @@ +namespace Topers.Core.Abstractions; + +using Topers.Core.Models; + +public interface IAddressesRepository +{ + Task CreateAsync(Address address, CancellationToken cancellationToken = default); +}; \ No newline at end of file diff --git a/Topers.Core/Abstractions/IAddressesService.cs b/Topers.Core/Abstractions/IAddressesService.cs new file mode 100644 index 0000000..e428ccd --- /dev/null +++ b/Topers.Core/Abstractions/IAddressesService.cs @@ -0,0 +1,9 @@ +namespace Topers.Core.Abstractions; + +using Topers.Core.Dtos; +using Topers.Core.Models; + +public interface IAddressesService +{ + Task AddAddressToCustomerAsync(Address address, CancellationToken cancellationToken = default); +}; \ No newline at end of file diff --git a/Topers.Core/Abstractions/ICartService.cs b/Topers.Core/Abstractions/ICartService.cs new file mode 100644 index 0000000..2b149b4 --- /dev/null +++ b/Topers.Core/Abstractions/ICartService.cs @@ -0,0 +1,13 @@ +namespace Topers.Core.Abstractions; + +using Topers.Core.Dtos; +using Topers.Core.Models; + +public interface ICartsService +{ + Task GetCartById(Guid cartId, CancellationToken cancellationToken = default); + Task GetCartByCustomerId(Guid customerId, CancellationToken cancellationToken = default); + Task CreateCartAsync(Cart cart, CancellationToken cancellationToken = default); + Task DeleteCartAsync(Guid cartId, CancellationToken cancellationToken = default); + Task AddGoodToCartAsync(CartItems cartDetails, GoodScope good, CancellationToken cancellationToken = default); +}; \ No newline at end of file diff --git a/Topers.Core/Abstractions/ICartsRepository.cs b/Topers.Core/Abstractions/ICartsRepository.cs new file mode 100644 index 0000000..b508431 --- /dev/null +++ b/Topers.Core/Abstractions/ICartsRepository.cs @@ -0,0 +1,12 @@ +namespace Topers.Core.Abstractions; + +using Topers.Core.Models; + +public interface ICartsRepository +{ + Task GetById(Guid cartId, CancellationToken cancellationToken = default); + Task GetByCustomerId(Guid customerId, CancellationToken cancellationToken = default); + Task CreateAsync(Cart cart, CancellationToken cancellationToken = default); + Task DeleteAsync(Guid cartId, CancellationToken cancellationToken = default); + Task AddDetailAsync(CartItems cartDetails, CancellationToken cancellationToken = default); +}; diff --git a/Topers.Core/Abstractions/ICategoriesRespository.cs b/Topers.Core/Abstractions/ICategoriesRespository.cs new file mode 100644 index 0000000..6011fae --- /dev/null +++ b/Topers.Core/Abstractions/ICategoriesRespository.cs @@ -0,0 +1,13 @@ +namespace Topers.Core.Abstractions; + +using Topers.Core.Models; + +public interface ICategoriesRepository +{ + Task CreateAsync(Category category, CancellationToken cancellationToken = default); + Task> GetAllAsync(CancellationToken cancellationToken = default); + Task GetByIdAsync(Guid categoryId, CancellationToken cancellationToken = default); + Task> GetGoodsByIdAsync(Guid categoryId, CancellationToken cancellationToken = default); + Task UpdateAsync(Category category, CancellationToken cancellationToken = default); + Task DeleteAsync(Guid categoryId, CancellationToken cancellationToken = default); +}; \ No newline at end of file diff --git a/Topers.Core/Abstractions/ICategoriesService.cs b/Topers.Core/Abstractions/ICategoriesService.cs new file mode 100644 index 0000000..b49855e --- /dev/null +++ b/Topers.Core/Abstractions/ICategoriesService.cs @@ -0,0 +1,14 @@ +namespace Topers.Core.Abstractions; + +using Topers.Core.Dtos; +using Topers.Core.Models; + +public interface ICategoriesService +{ + Task CreateCategoryAsync(Category category, CancellationToken cancellationToken = default); + Task> GetAllCategoriesAsync(CancellationToken cancellationToken = default); + Task GetCategoryByIdAsync(Guid categoryId, CancellationToken cancellationToken = default); + Task> GetGoodsByCategoryIdAsync(Guid categoryId, CancellationToken cancellationToken = default); + Task UpdateCategoryAsync(Category category, CancellationToken cancellationToken = default); + Task DeleteCategoryAsync(Guid categoryId, CancellationToken cancellationToken = default); +}; \ No newline at end of file diff --git a/Topers.Core/Abstractions/ICustomersRepository.cs b/Topers.Core/Abstractions/ICustomersRepository.cs new file mode 100644 index 0000000..34c68b2 --- /dev/null +++ b/Topers.Core/Abstractions/ICustomersRepository.cs @@ -0,0 +1,10 @@ +namespace Topers.Core.Abstractions; + +using Topers.Core.Models; + +public interface ICustomersRepository +{ + Task> GetAllAsync(CancellationToken cancellationToken = default); + Task GetByIdAsync(Guid customerId, CancellationToken cancellationToken = default); + Task CreateAsync(Customer customer, CancellationToken cancellationToken = default); +} \ No newline at end of file diff --git a/Topers.Core/Abstractions/ICustomersService.cs b/Topers.Core/Abstractions/ICustomersService.cs new file mode 100644 index 0000000..2a01e36 --- /dev/null +++ b/Topers.Core/Abstractions/ICustomersService.cs @@ -0,0 +1,11 @@ +namespace Topers.Core.Abstractions; + +using Topers.Core.Dtos; +using Topers.Core.Models; + +public interface ICustomersService +{ + Task> GetAllCustomersAsync(CancellationToken cancellationToken = default); + Task GetCustomerByIdAsync(Guid customerId, CancellationToken cancellationToken = default); + Task CreateCustomerAsync(Customer customer, CancellationToken cancellationToken = default); +}; \ No newline at end of file diff --git a/Topers.Core/Abstractions/IFileService.cs b/Topers.Core/Abstractions/IFileService.cs new file mode 100644 index 0000000..da674dc --- /dev/null +++ b/Topers.Core/Abstractions/IFileService.cs @@ -0,0 +1,10 @@ + +using Microsoft.AspNetCore.Http; + +namespace Topers.Core.Abstractions; + +public interface IFileService +{ + Tuple SaveImage(IFormFile imageFile); + void DeleteImage(string fullFileName); +}; \ No newline at end of file diff --git a/Topers.Core/Abstractions/IGoodsRepository.cs b/Topers.Core/Abstractions/IGoodsRepository.cs new file mode 100644 index 0000000..5a734b9 --- /dev/null +++ b/Topers.Core/Abstractions/IGoodsRepository.cs @@ -0,0 +1,16 @@ +namespace Topers.Core.Abstractions; + +using Topers.Core.Models; + +public interface IGoodsRepository +{ + Task CreateAsync(Good good, CancellationToken cancellationToken = default); + Task> GetAllAsync(CancellationToken cancellationToken = default); + Task GetByIdAsync(Guid goodId, CancellationToken cancellationToken = default); + Task> GetByFilterAsync(string title, CancellationToken cancellationToken = default); + Task UpdateAsync(Good good, CancellationToken cancellationToken = default); + Task DeleteAsync(Guid goodId, CancellationToken cancellationToken = default); + Task GetScopeAsync(Guid goodId, int litre, CancellationToken cancellationToken = default); + Task AddScopeAsync(GoodScope goodScope, CancellationToken cancellationToken = default); + Task UpdateScopeAsync(GoodScope goodScope, CancellationToken cancellationToken = default); +}; \ No newline at end of file diff --git a/Topers.Core/Abstractions/IGoodsService.cs b/Topers.Core/Abstractions/IGoodsService.cs new file mode 100644 index 0000000..101cb4e --- /dev/null +++ b/Topers.Core/Abstractions/IGoodsService.cs @@ -0,0 +1,16 @@ +namespace Topers.Core.Abstractions; + +using Topers.Core.Dtos; +using Topers.Core.Models; + +public interface IGoodsService +{ + Task CreateGoodAsync(Good good, CancellationToken cancellationToken = default); + Task> GetAllGoodsAsync(CancellationToken cancellationToken = default); + Task GetGoodByIdAsync(Guid goodId, CancellationToken cancellationToken = default); + Task UpdateGoodAsync(Good good, CancellationToken cancellationToken = default); + Task DeleteGoodAsync(Guid goodId, CancellationToken cancellationToken = default); + Task AddGoodScopeAsync(GoodScope scope, CancellationToken cancellationToken = default); + Task UpdateGoodScopeAsync(GoodScope scope, CancellationToken cancellationToken = default); + Task IsGoodScopeExistsAsync(Guid goodId, int litre, CancellationToken cancellationToken = default); +}; \ No newline at end of file diff --git a/Topers.Core/Abstractions/IJwtProvider.cs b/Topers.Core/Abstractions/IJwtProvider.cs new file mode 100644 index 0000000..c9bee91 --- /dev/null +++ b/Topers.Core/Abstractions/IJwtProvider.cs @@ -0,0 +1,8 @@ +namespace Topers.Core.Abstractions; + +using Topers.Core.Models; + +public interface IJwtProvider +{ + string GenerateToken(User user); +}; \ No newline at end of file diff --git a/Topers.Core/Abstractions/IOrdersRepository.cs b/Topers.Core/Abstractions/IOrdersRepository.cs new file mode 100644 index 0000000..193937c --- /dev/null +++ b/Topers.Core/Abstractions/IOrdersRepository.cs @@ -0,0 +1,13 @@ +namespace Topers.Core.Abstractions; + +using Topers.Core.Models; + +public interface IOrdersRepository +{ + Task> GetAllAsync(CancellationToken cancellationToken = default); + Task GetByIdAsync(Guid orderId, CancellationToken cancellationToken = default); + Task CreateAsync(Order order, CancellationToken cancellationToken = default); + Task UpdateAsync(Order order, CancellationToken cancellationToken = default); + Task DeleteAsync(Guid orderId, CancellationToken cancellationToken = default); + Task AddDetailAsync(OrderDetails detail, CancellationToken cancellationToken = default); +}; diff --git a/Topers.Core/Abstractions/IOrdersService.cs b/Topers.Core/Abstractions/IOrdersService.cs new file mode 100644 index 0000000..0a02b6b --- /dev/null +++ b/Topers.Core/Abstractions/IOrdersService.cs @@ -0,0 +1,14 @@ +namespace Topers.Core.Abstractions; + +using Topers.Core.Dtos; +using Topers.Core.Models; + +public interface IOrdersService +{ + Task> GetAllOrdersAsync(CancellationToken cancellationToken = default); + Task GetOrderByIdAsync(Guid orderId, CancellationToken cancellationToken = default); + Task CreateOrderAsync(Order order, CancellationToken cancellationToken = default); + Task UpdateOrderAsync(Order order, CancellationToken cancellationToken = default); + Task DeleteOrderAsync(Guid orderId, CancellationToken cancellationToken = default); + Task AddGoodToOrderAsync(OrderDetails detail, GoodScope good, CancellationToken cancellationToken = default); +}; \ No newline at end of file diff --git a/Topers.Core/Abstractions/IPasswordHasher.cs b/Topers.Core/Abstractions/IPasswordHasher.cs new file mode 100644 index 0000000..35f8b2e --- /dev/null +++ b/Topers.Core/Abstractions/IPasswordHasher.cs @@ -0,0 +1,7 @@ +namespace Topers.Core.Abstractions; + +public interface IPasswordHasher +{ + string Generate(string password); + bool Verify(string password, string hashedPassword); +}; \ No newline at end of file diff --git a/Topers.Core/Abstractions/IUsersRepository.cs b/Topers.Core/Abstractions/IUsersRepository.cs new file mode 100644 index 0000000..7025124 --- /dev/null +++ b/Topers.Core/Abstractions/IUsersRepository.cs @@ -0,0 +1,9 @@ +namespace Topers.Core.Abstractions; + +using Topers.Core.Models; + +public interface IUsersRepository +{ + Task Add(User user, CancellationToken cancellationToken = default); + Task GetByName(string username, CancellationToken cancellationToken = default); +}; \ No newline at end of file diff --git a/Topers.Core/Abstractions/IUsersService.cs b/Topers.Core/Abstractions/IUsersService.cs new file mode 100644 index 0000000..29dc3ae --- /dev/null +++ b/Topers.Core/Abstractions/IUsersService.cs @@ -0,0 +1,7 @@ +namespace Topers.Core.Abstractions; + +public interface IUsersService +{ + Task Register(string username, string email, string password, CancellationToken cancellationToken = default); + Task Login(string username, string password, CancellationToken cancellationToken = default); +}; \ No newline at end of file diff --git a/Topers.Core/Dtos/AddressDto.cs b/Topers.Core/Dtos/AddressDto.cs new file mode 100644 index 0000000..8257942 --- /dev/null +++ b/Topers.Core/Dtos/AddressDto.cs @@ -0,0 +1,19 @@ +namespace Topers.Core.Dtos; + +public record AddressResponseDto( + Guid Id, + Guid CustomerId, + string Street = "", + string City = "", + string State = "", + string PostalCode = "", + string Country = "" +); + +public record AddressRequestDto( + string Street = "", + string City = "", + string State = "", + string PostalCode = "", + string Country = "" +); \ No newline at end of file diff --git a/Topers.Core/Dtos/CartDto.cs b/Topers.Core/Dtos/CartDto.cs new file mode 100644 index 0000000..a1442ce --- /dev/null +++ b/Topers.Core/Dtos/CartDto.cs @@ -0,0 +1,13 @@ +namespace Topers.Core.Dtos; + +public record CartResponseDto( + Guid Id, + Guid CustomerId, + DateTime CreatedDate, + DateTime UpdatedDate, + List? CartDetails = null +); + +public record CartRequestDto( + Guid CustomerId +); diff --git a/Topers.Core/Dtos/CartItemDto.cs b/Topers.Core/Dtos/CartItemDto.cs new file mode 100644 index 0000000..bb8e55c --- /dev/null +++ b/Topers.Core/Dtos/CartItemDto.cs @@ -0,0 +1,16 @@ +namespace Topers.Core.Dtos; + +public record CartItemResponseDto( + Guid Id, + Guid CartId, + Guid GoodId, + int Quantity, + decimal Price +); + +public record CartItemRequestDto( + Guid CartId, + Guid GoodId, + int Quantity, + decimal Price +); \ No newline at end of file diff --git a/Topers.Core/Dtos/CategoryDto.cs b/Topers.Core/Dtos/CategoryDto.cs new file mode 100644 index 0000000..ba62754 --- /dev/null +++ b/Topers.Core/Dtos/CategoryDto.cs @@ -0,0 +1,12 @@ +namespace Topers.Core.Dtos; + +public record CategoryResponseDto( + Guid Id, + string Name = "", + string? Description = "" +); + +public record CategoryRequestDto( + string Name = "", + string Description = "" +); \ No newline at end of file diff --git a/Topers.Core/Dtos/CustomerDto.cs b/Topers.Core/Dtos/CustomerDto.cs new file mode 100644 index 0000000..05d21dd --- /dev/null +++ b/Topers.Core/Dtos/CustomerDto.cs @@ -0,0 +1,17 @@ +using System.ComponentModel.DataAnnotations; + +namespace Topers.Core.Dtos; + +public record CustomerResponseDto( + Guid Id, + string Name = "", + string Email = "", + string Phone = "", + AddressResponseDto? Address = null +); + +public record CustomerRequestDto( + string Name = "", + string Email = "", + string Phone = "" +); \ No newline at end of file diff --git a/Topers.Core/Dtos/GoodDto.cs b/Topers.Core/Dtos/GoodDto.cs new file mode 100644 index 0000000..e4c7178 --- /dev/null +++ b/Topers.Core/Dtos/GoodDto.cs @@ -0,0 +1,14 @@ +namespace Topers.Core.Dtos; + +public record GoodResponseDto( + Guid Id, + string Name = "", + string? Description = "", + List? Scopes = null +); + +public record GoodRequestDto( + Guid CategoryId, + string Name = "", + string Description = "" +); \ No newline at end of file diff --git a/Topers.Core/Dtos/GoodScopeDto.cs b/Topers.Core/Dtos/GoodScopeDto.cs new file mode 100644 index 0000000..3a965fb --- /dev/null +++ b/Topers.Core/Dtos/GoodScopeDto.cs @@ -0,0 +1,19 @@ +using System.ComponentModel.DataAnnotations; +using Microsoft.AspNetCore.Http; + +namespace Topers.Core.Dtos; + +public record GoodScopeResponseDto( + Guid Id = default, + Guid GoodId = default, + int Litre = default, + decimal Price = default, + string? ImageName = "" +); + +public record GoodScopeRequestDto( + [Required] int Litre, + [Required] decimal Price, + string? ImageName = "", + IFormFile? ImageFile = null +); \ No newline at end of file diff --git a/Topers.Core/Dtos/OrderDetailsDto.cs b/Topers.Core/Dtos/OrderDetailsDto.cs new file mode 100644 index 0000000..9281441 --- /dev/null +++ b/Topers.Core/Dtos/OrderDetailsDto.cs @@ -0,0 +1,16 @@ +namespace Topers.Core.Dtos; + +public record OrderDetailsResponseDto( + Guid Id, + Guid OrderId, + Guid GoodId, + int Quantity, + decimal Price +); + +public record OrderDetailsRequestDto( + Guid OrderId, + Guid GoodId, + int Quantity, + decimal Price +); \ No newline at end of file diff --git a/Topers.Core/Dtos/OrderDto.cs b/Topers.Core/Dtos/OrderDto.cs new file mode 100644 index 0000000..391c604 --- /dev/null +++ b/Topers.Core/Dtos/OrderDto.cs @@ -0,0 +1,25 @@ +namespace Topers.Core.Dtos; + +public record OrderResponseDto( + Guid Id, + DateTime Date, + Guid CustomerId, + decimal TotalPrice, + List? OrderDetails = null +); + +public record OrderRequestDto( + DateTime Date, + Guid CustomerId, + decimal TotalPrice +); + +public record UpdateOrderRequestDto( + Guid CustomerId +); + +public record AddProductRequestDto( + Guid GoodScopeId, + int GoodQuantity, + int GoodLitre +); \ No newline at end of file diff --git a/Topers.Core/Dtos/UserDto.cs b/Topers.Core/Dtos/UserDto.cs new file mode 100644 index 0000000..aca386c --- /dev/null +++ b/Topers.Core/Dtos/UserDto.cs @@ -0,0 +1,14 @@ +using System.ComponentModel.DataAnnotations; + +namespace Topers.Core.Dtos; + +public record LoginUserRequestDto( + [Required] string Username = "", + [Required] string Password = "" +); + +public record RegisterUserRequestDto( + [Required] string Username = "", + [Required] string Email = "", + [Required] string Password = "" +); \ No newline at end of file diff --git a/Topers.Core/Models/Address.cs b/Topers.Core/Models/Address.cs new file mode 100644 index 0000000..b6e0e90 --- /dev/null +++ b/Topers.Core/Models/Address.cs @@ -0,0 +1,53 @@ +namespace Topers.Core.Models; + +/// +/// Represents a customer address. +/// +public class Address +{ + public Address(Guid id, Guid customerId, string street, string city, string state, string postalCode, string country) + { + Id = id; + CustomerId = customerId; + Street = street; + City = city; + State = state; + PostalCode = postalCode; + Country = country; + } + + /// + /// Gets or sets an address identifier. + /// + public Guid Id { get; } + + /// + /// Gets or sets a customer identifier. + /// + public Guid CustomerId { get; } + + /// + /// Gets or sets a customer street. + /// + public string Street { get; } = String.Empty; + + /// + /// Gets or sets a customer city. + /// + public string City { get; } = String.Empty; + + /// + /// Gets or sets a customer state. + /// + public string State { get; } = String.Empty; + + /// + /// Gets or sets a customer postal code. + /// + public string PostalCode { get; } = String.Empty; + + /// + /// Gets or sets a customer coutry. + /// + public string Country { get; } = String.Empty; +} \ No newline at end of file diff --git a/Topers.Core/Models/Cart.cs b/Topers.Core/Models/Cart.cs new file mode 100644 index 0000000..754dea6 --- /dev/null +++ b/Topers.Core/Models/Cart.cs @@ -0,0 +1,40 @@ +namespace Topers.Core.Models; + +/// +/// Represents a customer cart. +/// +public class Cart +{ + public Cart(Guid id, Guid customerId, DateTime createdDate, DateTime updatedDate) + { + Id = id; + CustomerId = customerId; + CreatedDate = createdDate; + UpdatedDate = updatedDate; + } + + /// + /// Gets a cart identifier. + /// + public Guid Id { get; } + + /// + /// Gets a customer identifier. + /// + public Guid CustomerId { get; } + + /// + /// Gets a cart created date. + /// + public DateTime CreatedDate { get; } + + /// + /// Gets a cart updated date. + /// + public DateTime UpdatedDate { get; } + + /// + /// Gets or sets a good scopes. + /// + public ICollection CartDetails { get; set; } +} diff --git a/Topers.Core/Models/CartItems.cs b/Topers.Core/Models/CartItems.cs new file mode 100644 index 0000000..f668a94 --- /dev/null +++ b/Topers.Core/Models/CartItems.cs @@ -0,0 +1,41 @@ +namespace Topers.Core.Models; + +/// +/// Represents a customer cart. +/// +public class CartItems +{ + public CartItems(Guid id, Guid cartId, Guid goodId, int quantity, decimal price) + { + Id = id; + CartId = cartId; + GoodId = goodId; + Quantity = quantity; + Price = price; + } + + /// + /// Gets a cart item identifier. + /// + public Guid Id { get; } + + /// + /// Gets a cart identifier. + /// + public Guid CartId { get; } + + /// + /// Gets a cart good scope identifier. + /// + public Guid GoodId { get; } + + /// + /// Gets a good quantity. + /// + public int Quantity { get; } = 0; + + /// + /// Gets a good price. + /// + public decimal Price { get; } = 0; +} diff --git a/Topers.Core/Models/Category.cs b/Topers.Core/Models/Category.cs new file mode 100644 index 0000000..c5f35a6 --- /dev/null +++ b/Topers.Core/Models/Category.cs @@ -0,0 +1,29 @@ +namespace Topers.Core.Models; + +/// +/// Represents a category. +/// +public class Category +{ + public Category(Guid id, string name, string description) + { + Id = id; + Name = name; + Description = description; + } + + /// + /// Gets or sets a category identifier. + /// + public Guid Id { get; } + + /// + /// Gets or sets a category name. + /// + public string Name { get; } = string.Empty; + + /// + /// Gets or sets a category description. + /// + public string? Description { get; } = string.Empty; +} \ No newline at end of file diff --git a/Topers.Core/Models/Customer.cs b/Topers.Core/Models/Customer.cs new file mode 100644 index 0000000..456cc41 --- /dev/null +++ b/Topers.Core/Models/Customer.cs @@ -0,0 +1,41 @@ +namespace Topers.Core.Models; + +/// +/// Represents a customer. +/// +public class Customer +{ + public Customer(Guid id, Address? address, string name, string email, string phone) + { + Id = id; + Address = address; + Name = name; + Email = email; + Phone = phone; + } + + /// + /// Gets or sets a customer identifier. + /// + public Guid Id { get; } + + /// + /// Gets or sets a customer address. + /// + public Address? Address { get; } + + /// + /// Gets or sets a customer name. + /// + public string Name { get; } = string.Empty; + + /// + /// Gets or sets a customer identifier. + /// + public string Email { get; } = string.Empty; + + /// + /// Gets or sets a customer identifier. + /// + public string Phone { get; } = string.Empty; +} \ No newline at end of file diff --git a/Topers.Core/Models/Good.cs b/Topers.Core/Models/Good.cs new file mode 100644 index 0000000..b0e43fb --- /dev/null +++ b/Topers.Core/Models/Good.cs @@ -0,0 +1,44 @@ +namespace Topers.Core.Models; + +using System.Collections; +using Microsoft.AspNetCore.Http; + +/// +/// Represents a good. +/// +public class Good +{ + public Good(Guid id, Guid categoryId, string name, string description) + { + Id = id; + CategoryId = categoryId; + Name = name; + Description = description; + Scopes = new List(); + } + + /// + /// Gets or sets a good identifier. + /// + public Guid Id { get; } + + /// + /// Gets or sets a good category identifier. + /// + public Guid CategoryId { get; } + + /// + /// Gets or sets a good name. + /// + public string Name { get; } = string.Empty; + + /// + /// Gets or sets a good description. + /// + public string? Description { get; } = string.Empty; + + /// + /// Gets or sets a good scopes. + /// + public ICollection? Scopes { get; } +} \ No newline at end of file diff --git a/Topers.Core/Models/GoodScope.cs b/Topers.Core/Models/GoodScope.cs new file mode 100644 index 0000000..976d661 --- /dev/null +++ b/Topers.Core/Models/GoodScope.cs @@ -0,0 +1,41 @@ +namespace Topers.Core.Models; + +/// +/// Represents a good scope entity. +/// +public class GoodScope +{ + public GoodScope(Guid id, Guid goodId, int litre, decimal price = 0, string? image = null) + { + Id = id; + GoodId = goodId; + Litre = litre; + Price = price; + ImageName = image; + } + + /// + /// Gets or sets a good scope identifier. + /// + public Guid Id { get; } + + /// + /// Gets or sets a good identifier. + /// + public Guid GoodId { get; } + + /// + /// Gets or sets the volume in liters. + /// + public int Litre { get; } + + /// + /// Gets or sets the price. + /// + public decimal Price { get; } + + /// + /// Gets or sets the image. + /// + public string? ImageName { get; } +} \ No newline at end of file diff --git a/Topers.Core/Models/Order.cs b/Topers.Core/Models/Order.cs new file mode 100644 index 0000000..047534d --- /dev/null +++ b/Topers.Core/Models/Order.cs @@ -0,0 +1,41 @@ +namespace Topers.Core.Models; + +/// +/// Represents an order. +/// +public class Order +{ + public Order(Guid id, DateTime date, Guid customerId, decimal totalPrice) + { + Id = id; + Date = date; + CustomerId = customerId; + TotalPrice = totalPrice; + OrderDetails = []; + } + + /// + /// Gets or sets an order identifier. + /// + public Guid Id { get; } + + /// + /// Gets or sets an order date. + /// + public DateTime Date { get; } + + /// + /// Gets or sets an order customer identifier. + /// + public Guid CustomerId { get; } + + /// + /// Gets or sets an order total price. + /// + public decimal TotalPrice { get; } = 0; + + /// + /// Gets or sets a good scopes. + /// + public ICollection OrderDetails { get; set; } +} \ No newline at end of file diff --git a/Topers.Core/Models/OrderDetails.cs b/Topers.Core/Models/OrderDetails.cs new file mode 100644 index 0000000..2038d80 --- /dev/null +++ b/Topers.Core/Models/OrderDetails.cs @@ -0,0 +1,41 @@ +namespace Topers.Core.Models; + +/// +/// Represents an order details. +/// +public class OrderDetails +{ + public OrderDetails(Guid id, Guid orderId, Guid goodId, int quantity, decimal price) + { + Id = id; + OrderId = orderId; + GoodId = goodId; + Quantity = quantity; + Price = price; + } + + /// + /// Gets or sets an order details identifier. + /// + public Guid Id { get; } + + /// + /// Gets or sets an order identifier. + /// + public Guid OrderId { get; } + + /// + /// Gets or sets a good identifier. + /// + public Guid GoodId { get; } + + /// + /// Gets or sets a good quantity. + /// + public int Quantity { get; } = 0; + + /// + /// Gets or sets a good price. + /// + public decimal Price { get; } = 0; +} diff --git a/Topers.Core/Models/User.cs b/Topers.Core/Models/User.cs new file mode 100644 index 0000000..81ebc28 --- /dev/null +++ b/Topers.Core/Models/User.cs @@ -0,0 +1,42 @@ +using Microsoft.AspNetCore.Http; + +namespace Topers.Core.Models; + +/// +/// Represents a user. +/// +public class User +{ + private User(Guid id, string username, string passwordHash, string email) + { + Id = id; + Username = username; + PasswordHash = passwordHash; + Email = email; + } + + /// + /// Gets or sets a user identifier. + /// + public Guid Id { get; set;} + + /// + /// Gets a user name. + /// + public string Username { get; } = string.Empty; + + /// + /// Gets a user hash password. + /// + public string PasswordHash { get; } = string.Empty; + + /// + /// Gets a user email. + /// + public string Email { get; } = string.Empty; + + public static User Create(Guid id, string username, string passwordHash, string email) + { + return new User(id, username, passwordHash, email); + } +} \ No newline at end of file diff --git a/Topers.Core/Topers.Core.csproj b/Topers.Core/Topers.Core.csproj index bb23fb7..341ab4a 100644 --- a/Topers.Core/Topers.Core.csproj +++ b/Topers.Core/Topers.Core.csproj @@ -6,4 +6,13 @@ enable + + + + + + + + + diff --git a/Topers.Core/Validators/AddressDtoValidator.cs b/Topers.Core/Validators/AddressDtoValidator.cs new file mode 100644 index 0000000..97dc3eb --- /dev/null +++ b/Topers.Core/Validators/AddressDtoValidator.cs @@ -0,0 +1,32 @@ +namespace Topers.Core.Validators; + +using Topers.Core.Dtos; +using FluentValidation; + +public class AddressDtoValidator : AbstractValidator +{ + public AddressDtoValidator() + { + RuleFor(a => a.Street) + .NotEmpty().WithMessage("{PropertyName} is required!") + .NotNull() + .MaximumLength(80).WithMessage("{PropertyName} must not exceed 50 characters!"); + RuleFor(a => a.City) + .NotEmpty().WithMessage("{PropertyName} is required!") + .NotNull() + .MaximumLength(60).WithMessage("{PropertyName} must not exceed 50 characters!"); + RuleFor(a => a.State) + .NotEmpty().WithMessage("{PropertyName} is required!") + .NotNull() + .MaximumLength(60).WithMessage("{PropertyName} must not exceed 50 characters!"); + RuleFor(a => a.PostalCode) + .NotEmpty().WithMessage("{PropertyName} is required!") + .NotNull() + .MinimumLength(5).WithMessage("{PropertyName} must not be lower than 5 characters!") + .MaximumLength(5).WithMessage("{PropertyName} must not exceed 5 characters!"); + RuleFor(a => a.Country) + .NotEmpty().WithMessage("{PropertyName} is required!") + .NotNull() + .MaximumLength(60).WithMessage("{PropertyName} must not exceed 50 characters!"); + } +} \ No newline at end of file diff --git a/Topers.Core/Validators/CategoryDtoValidator.cs b/Topers.Core/Validators/CategoryDtoValidator.cs new file mode 100644 index 0000000..1033150 --- /dev/null +++ b/Topers.Core/Validators/CategoryDtoValidator.cs @@ -0,0 +1,15 @@ +namespace Topers.Core.Validators; + +using Topers.Core.Dtos; +using FluentValidation; + +public class CategoryDtoValidator : AbstractValidator +{ + public CategoryDtoValidator() + { + RuleFor(c => c.Name) + .NotEmpty().WithMessage("{PropertyName} is required!") + .NotNull() + .MaximumLength(60).WithMessage("{PropertyName} must not exceed 60 characters!"); + } +} \ No newline at end of file diff --git a/Topers.Core/Validators/CustomerDtoValidator.cs b/Topers.Core/Validators/CustomerDtoValidator.cs new file mode 100644 index 0000000..6ac0e52 --- /dev/null +++ b/Topers.Core/Validators/CustomerDtoValidator.cs @@ -0,0 +1,23 @@ +namespace Topers.Core.Validators; + +using Topers.Core.Dtos; +using FluentValidation; + +public class CustomerDtoValidator : AbstractValidator +{ + public CustomerDtoValidator() + { + RuleFor(c => c.Name) + .NotEmpty().WithMessage("{PropertyName} is required!") + .NotNull() + .MaximumLength(100).WithMessage("{PropertyName} must not exceed 60 characters!"); + RuleFor(c => c.Email) + .NotEmpty().WithMessage("{PropertyName} is required!") + .NotNull() + .EmailAddress().WithMessage("A valid {PropertyName} is required!"); + RuleFor(c => c.Phone) + .NotEmpty().WithMessage("{PropertyName} is required!") + .NotNull() + .Matches(@"^(((\+44\s?\d{4}|\(?0\d{4}\)?)\s?\d{3}\s?\d{3})|((\+44\s?\d{3}|\(?0\d{3}\)?)\s?\d{3}\s?\d{4})|((\+44\s?\d{2}|\(?0\d{2}\)?)\s?\d{4}\s?\d{4}))(\s?\#(\d{4}|\d{3}))?$").WithMessage("A valid {PropertyName} is required!"); + } +} \ No newline at end of file diff --git a/Topers.Core/Validators/GoodDtoValidator.cs b/Topers.Core/Validators/GoodDtoValidator.cs new file mode 100644 index 0000000..2c0fe7c --- /dev/null +++ b/Topers.Core/Validators/GoodDtoValidator.cs @@ -0,0 +1,18 @@ +namespace Topers.Core.Validators; + +using Topers.Core.Dtos; +using FluentValidation; + +public class GoodDtoValidator : AbstractValidator +{ + public GoodDtoValidator() + { + RuleFor(g => g.CategoryId) + .NotEmpty().WithMessage("{PropertyName} is required!") + .NotNull(); + RuleFor(g => g.Name) + .NotEmpty().WithMessage("{PropertyName} is required!") + .NotNull() + .MaximumLength(120).WithMessage("{PropertyName} must not exceed 120 characters!"); + } +} \ No newline at end of file diff --git a/Topers.Core/Validators/GoodScopeDtoValidator.cs b/Topers.Core/Validators/GoodScopeDtoValidator.cs new file mode 100644 index 0000000..9b3235c --- /dev/null +++ b/Topers.Core/Validators/GoodScopeDtoValidator.cs @@ -0,0 +1,22 @@ +namespace Topers.Core.Validators; + +using Topers.Core.Dtos; +using FluentValidation; + +public class GoodScopeDtoValidator : AbstractValidator +{ + public GoodScopeDtoValidator() + { + RuleFor(g => g.Litre) + .Must(IsLitreValid).WithMessage("Litre must be one of the following values: 1, 5, 10, 15, 25, 1000!"); + RuleFor(g => g.Price) + .NotEmpty().WithMessage("{PropertyName} is required!") + .NotNull(); + } + + private bool IsLitreValid(int litre) + { + int[] validLitres = { 1, 5, 10, 15, 25, 1000 }; + return validLitres.Contains(litre); + } +} \ No newline at end of file diff --git a/Topers.Core/Validators/OrderDetailsDtoValidator.cs b/Topers.Core/Validators/OrderDetailsDtoValidator.cs new file mode 100644 index 0000000..3afe6cc --- /dev/null +++ b/Topers.Core/Validators/OrderDetailsDtoValidator.cs @@ -0,0 +1,25 @@ +namespace Topers.Core.Validators; + +using Topers.Core.Dtos; +using FluentValidation; +using System.Data; + +public class OrderDetailsDtoValidator : AbstractValidator +{ + public OrderDetailsDtoValidator() + { + RuleFor(d => d.OrderId) + .NotEmpty().WithMessage("{PropertyName} is required!") + .NotNull(); + RuleFor(d => d.GoodId) + .NotEmpty().WithMessage("{PropertyName} is required!") + .NotNull(); + RuleFor(d => d.Quantity) + .NotEmpty().WithMessage("{PropertyName} is required!") + .NotNull() + .GreaterThan(0).WithMessage("A product {PropertyName} must be greater than 0!"); + RuleFor(d => d.Price) + .NotEmpty().WithMessage("{PropertyName} is required!") + .NotNull(); + } +} \ No newline at end of file diff --git a/Topers.Core/Validators/OrderDtoValidator.cs b/Topers.Core/Validators/OrderDtoValidator.cs new file mode 100644 index 0000000..0390660 --- /dev/null +++ b/Topers.Core/Validators/OrderDtoValidator.cs @@ -0,0 +1,20 @@ +namespace Topers.Core.Validators; + +using Topers.Core.Dtos; +using FluentValidation; + +public class OrderDtoValidator : AbstractValidator +{ + public OrderDtoValidator() + { + RuleFor(o => o.CustomerId) + .NotEmpty().WithMessage("{PropertyName} is required!") + .NotNull(); + RuleFor(o => o.Date) + .NotEmpty().WithMessage("{PropertyName} is required!") + .NotNull(); + RuleFor(o => o.TotalPrice) + .NotEmpty().WithMessage("{PropertyName} is required!") + .NotNull(); + } +} \ No newline at end of file diff --git a/Topers.DataAccess.Postgres/Configurations/AddressConfiguration.cs b/Topers.DataAccess.Postgres/Configurations/AddressConfiguration.cs new file mode 100644 index 0000000..3279960 --- /dev/null +++ b/Topers.DataAccess.Postgres/Configurations/AddressConfiguration.cs @@ -0,0 +1,18 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Topers.DataAccess.Postgres.Entities; + +namespace Topers.DataAccess.Postgres.Configurations; + +public class AddressConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(a => a.Id); + builder + .HasOne(a => a.Customer) + .WithOne(c => c.Address) + .HasForeignKey(a => a.CustomerId); + builder.HasIndex(a => a.CustomerId).IsUnique(true); + } +} \ No newline at end of file diff --git a/Topers.DataAccess.Postgres/Configurations/CartConfiguration.cs b/Topers.DataAccess.Postgres/Configurations/CartConfiguration.cs new file mode 100644 index 0000000..6d8aead --- /dev/null +++ b/Topers.DataAccess.Postgres/Configurations/CartConfiguration.cs @@ -0,0 +1,21 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Topers.DataAccess.Postgres.Entities; + +namespace Topers.DataAccess.Postgres.Configurations; + +public class CartConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(c => c.Id); + builder + .HasOne(c => c.Customer) + .WithOne(o => o.Cart) + .HasForeignKey(c => c.CustomerId); + builder + .HasMany(c => c.CartItems) + .WithOne(i => i.Cart) + .HasForeignKey(i => i.CartId); + } +} \ No newline at end of file diff --git a/Topers.DataAccess.Postgres/Configurations/CartItemConfiguration.cs b/Topers.DataAccess.Postgres/Configurations/CartItemConfiguration.cs new file mode 100644 index 0000000..74640ff --- /dev/null +++ b/Topers.DataAccess.Postgres/Configurations/CartItemConfiguration.cs @@ -0,0 +1,17 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Topers.DataAccess.Postgres.Entities; + +namespace Topers.DataAccess.Postgres.Configurations; + +public class CartItemConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(i => i.Id); + builder + .HasOne(i => i.Good) + .WithMany(g => g.CartDetails) + .HasForeignKey(i => i.GoodId); + } +} \ No newline at end of file diff --git a/Topers.DataAccess.Postgres/Configurations/CategoryConfiguration.cs b/Topers.DataAccess.Postgres/Configurations/CategoryConfiguration.cs new file mode 100644 index 0000000..12996ea --- /dev/null +++ b/Topers.DataAccess.Postgres/Configurations/CategoryConfiguration.cs @@ -0,0 +1,17 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Topers.DataAccess.Postgres.Entities; + +namespace Topers.DataAccess.Postgres.Configurations; + +public class CategoryConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(c => c.Id); + builder + .HasMany(c => c.Goods) + .WithOne(g => g.Category) + .HasForeignKey(g => g.CategoryId); + } +} \ No newline at end of file diff --git a/Topers.DataAccess.Postgres/Configurations/CustomerConfiguration.cs b/Topers.DataAccess.Postgres/Configurations/CustomerConfiguration.cs new file mode 100644 index 0000000..84f959d --- /dev/null +++ b/Topers.DataAccess.Postgres/Configurations/CustomerConfiguration.cs @@ -0,0 +1,17 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Topers.DataAccess.Postgres.Entities; + +namespace Topers.DataAccess.Postgres.Configurations; + +public class CustomerConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(c => c.Id); + builder + .HasMany(c => c.Orders) + .WithOne(o => o.Customer) + .HasForeignKey(o => o.CustomerId); + } +} \ No newline at end of file diff --git a/Topers.DataAccess.Postgres/Configurations/GoodConfiguration.cs b/Topers.DataAccess.Postgres/Configurations/GoodConfiguration.cs new file mode 100644 index 0000000..6eb3eb5 --- /dev/null +++ b/Topers.DataAccess.Postgres/Configurations/GoodConfiguration.cs @@ -0,0 +1,21 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Topers.DataAccess.Postgres.Entities; + +namespace Topers.DataAccess.Postgres.Configurations; + +public class GoodConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(g => g.Id); + builder + .HasOne(g => g.Category) + .WithMany(c => c.Goods) + .HasForeignKey(g => g.CategoryId); + builder + .HasMany(g => g.Scopes) + .WithOne(s => s.Good) + .HasForeignKey(s => s.GoodId); + } +} \ No newline at end of file diff --git a/Topers.DataAccess.Postgres/Configurations/GoodScopeConfiguration.cs b/Topers.DataAccess.Postgres/Configurations/GoodScopeConfiguration.cs new file mode 100644 index 0000000..7d81359 --- /dev/null +++ b/Topers.DataAccess.Postgres/Configurations/GoodScopeConfiguration.cs @@ -0,0 +1,17 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Topers.DataAccess.Postgres.Entities; + +namespace Topers.DataAccess.Postgres.Configurations; + +public class GoodScopeConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(s => s.Id); + builder + .HasOne(s => s.Good) + .WithMany(g => g.Scopes) + .HasForeignKey(s => s.GoodId); + } +} \ No newline at end of file diff --git a/Topers.DataAccess.Postgres/Configurations/OrderConfiguration.cs b/Topers.DataAccess.Postgres/Configurations/OrderConfiguration.cs new file mode 100644 index 0000000..45d158c --- /dev/null +++ b/Topers.DataAccess.Postgres/Configurations/OrderConfiguration.cs @@ -0,0 +1,21 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Topers.DataAccess.Postgres.Entities; + +namespace Topers.DataAccess.Postgres.Configurations; + +public class OrderConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(o => o.Id); + builder + .HasOne(o => o.Customer) + .WithMany(c => c.Orders) + .HasForeignKey(o => o.CustomerId); + builder + .HasMany(o => o.OrderDetails) + .WithOne(d => d.Order) + .HasForeignKey(d => d.OrderId); + } +} \ No newline at end of file diff --git a/Topers.DataAccess.Postgres/Configurations/OrderDetailsConfiguration.cs b/Topers.DataAccess.Postgres/Configurations/OrderDetailsConfiguration.cs new file mode 100644 index 0000000..5d1f5a3 --- /dev/null +++ b/Topers.DataAccess.Postgres/Configurations/OrderDetailsConfiguration.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Topers.DataAccess.Postgres.Entities; + +namespace Topers.DataAccess.Postgres.Configurations; + +public class OrderDetailsConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(d => d.Id); + builder + .HasOne(d => d.Order) + .WithMany(o => o.OrderDetails) + .HasForeignKey(d => d.OrderId); + builder + .HasOne(d => d.Good) + .WithMany(o => o.OrderDetails) + .HasForeignKey(d => d.GoodId); + + } +} \ No newline at end of file diff --git a/Topers.DataAccess.Postgres/Entities/AddressEntity.cs b/Topers.DataAccess.Postgres/Entities/AddressEntity.cs new file mode 100644 index 0000000..ab0d567 --- /dev/null +++ b/Topers.DataAccess.Postgres/Entities/AddressEntity.cs @@ -0,0 +1,47 @@ +namespace Topers.DataAccess.Postgres.Entities; + +/// +/// Represents a address entity. +/// +public class AddressEntity +{ + /// + /// Gets or sets a address identifier. + /// + public Guid Id { get; set; } + + /// + /// Gets or sets a customer street. + /// + public string Street { get; set; } = string.Empty; + + /// + /// Gets or sets a customer city. + /// + public string City { get; set; } = string.Empty; + + /// + /// Gets or sets a customer state. + /// + public string State { get; set; } = string.Empty; + + /// + /// Gets or sets a customer postal code. + /// + public string PostalCode { get; set; } = string.Empty; + + /// + /// Gets or sets a customer country. + /// + public string Country { get; set; } = string.Empty; + + /// + /// Gets or sets a customer identifier. + /// + public Guid CustomerId { get; set; } + + /// + /// Gets or sets a customer. + /// + public CustomerEntity Customer { get; set; } = null!; +} \ No newline at end of file diff --git a/Topers.DataAccess.Postgres/Entities/CartEntity.cs b/Topers.DataAccess.Postgres/Entities/CartEntity.cs new file mode 100644 index 0000000..9c0ae84 --- /dev/null +++ b/Topers.DataAccess.Postgres/Entities/CartEntity.cs @@ -0,0 +1,37 @@ +namespace Topers.DataAccess.Postgres.Entities; + +/// +/// Represents a shopping cart. +/// +public class CartEntity +{ + /// + /// Gets or sets the cart identifier. + /// + public Guid Id { get; set; } + + /// + /// Gets or sets the customer identifier. + /// + public Guid CustomerId { get; set; } + + /// + /// ets or sets the customer. + /// + public CustomerEntity Customer { get; set; } = null!; + + /// + /// Gets or sets the date when the cart was created. + /// + public DateTime CreatedDate { get; set; } = DateTime.UtcNow; + + /// + /// Gets or sets the date when the cart was last updated. + /// + public DateTime UpdatedDate { get; set; } = DateTime.UtcNow; + + /// + /// Gets or sets the cart items. + /// + public ICollection CartItems { get; set; } = []; +} diff --git a/Topers.DataAccess.Postgres/Entities/CartItemEntity.cs b/Topers.DataAccess.Postgres/Entities/CartItemEntity.cs new file mode 100644 index 0000000..3a08afe --- /dev/null +++ b/Topers.DataAccess.Postgres/Entities/CartItemEntity.cs @@ -0,0 +1,42 @@ +namespace Topers.DataAccess.Postgres.Entities; + +/// +/// Represents an item in the shopping cart. +/// +public class CartItemEntity +{ + /// + /// Gets or sets the cart item identifier. + /// + public Guid Id { get; set; } + + /// + /// Gets or sets the cart identifier. + /// + public Guid CartId { get; set; } + + /// + /// Gets or sets the cart. + /// + public CartEntity Cart { get; set; } = null!; + + /// + /// Gets or sets the good identifier. + /// + public Guid GoodId { get; set; } + + /// + /// Gets or sets the good. + /// + public GoodScopeEntity Good { get; set; } = null!; + + /// + /// Gets or sets the quantity of the product. + /// + public int Quantity { get; set; } + + /// + /// Gets or sets the price of the product. + /// + public decimal Price { get; set; } +} diff --git a/Topers.DataAccess.Postgres/Entities/CategoryEntity.cs b/Topers.DataAccess.Postgres/Entities/CategoryEntity.cs new file mode 100644 index 0000000..1373fb7 --- /dev/null +++ b/Topers.DataAccess.Postgres/Entities/CategoryEntity.cs @@ -0,0 +1,27 @@ +namespace Topers.DataAccess.Postgres.Entities; + +/// +/// Represents a category entity. +/// +public class CategoryEntity +{ + /// + /// Gets or sets a category identifier. + /// + public Guid Id { get; set; } + + /// + /// Gets or sets a category name. + /// + public string Name { get; set; } = string.Empty; + + /// + /// Gets or sets a category description. + /// + public string? Description { get; set; } = string.Empty; + + /// + /// Gets or sets a category goods. + /// + public ICollection Goods { get; set; } = []; +} \ No newline at end of file diff --git a/Topers.DataAccess.Postgres/Entities/CustomerEntity.cs b/Topers.DataAccess.Postgres/Entities/CustomerEntity.cs new file mode 100644 index 0000000..eebd2b4 --- /dev/null +++ b/Topers.DataAccess.Postgres/Entities/CustomerEntity.cs @@ -0,0 +1,42 @@ +namespace Topers.DataAccess.Postgres.Entities; + +/// +/// Represents a customer entity. +/// +public class CustomerEntity +{ + /// + /// Gets or sets a customer identifier. + /// + public Guid Id { get; set; } + + /// + /// Gets or sets a customer name. + /// + public string Name { get; set; } = string.Empty; + + /// + /// Gets or sets a customer email. + /// + public string Email { get; set; } = string.Empty; + + /// + /// Gets or sets a customer phone. + /// + public string Phone { get; set; } = string.Empty; + + /// + /// Gets or sets a customer address. + /// + public AddressEntity? Address { get; set; } + + /// + /// Gets or sets a customer cart. + /// + public CartEntity? Cart { get; set; } + + /// + /// Gets or sets a customer orders. + /// + public ICollection Orders { get; set; } = []; +} diff --git a/Topers.DataAccess.Postgres/Entities/GoodEntity.cs b/Topers.DataAccess.Postgres/Entities/GoodEntity.cs new file mode 100644 index 0000000..cb1abfe --- /dev/null +++ b/Topers.DataAccess.Postgres/Entities/GoodEntity.cs @@ -0,0 +1,37 @@ +namespace Topers.DataAccess.Postgres.Entities; + +/// +/// Represents a good entity. +/// +public class GoodEntity +{ + /// + /// Gets or sets a good identifier. + /// + public Guid Id { get; set; } + + /// + /// Gets or sets a good name. + /// + public string Name { get; set; } = string.Empty; + + /// + /// Gets or sets a good description. + /// + public string? Description { get; set; } + + /// + /// Gets or sets a good category identifier. + /// + public Guid CategoryId { get; set; } + + /// + /// Gets or sets a good category; + /// + public CategoryEntity Category { get; set; } = null!; + + /// + /// Gets or sets a good scopes collection. + /// + public ICollection Scopes { get; set; } = []; +} \ No newline at end of file diff --git a/Topers.DataAccess.Postgres/Entities/GoodScopeEntity.cs b/Topers.DataAccess.Postgres/Entities/GoodScopeEntity.cs new file mode 100644 index 0000000..b99950b --- /dev/null +++ b/Topers.DataAccess.Postgres/Entities/GoodScopeEntity.cs @@ -0,0 +1,52 @@ +using System.ComponentModel.DataAnnotations.Schema; +using Microsoft.AspNetCore.Http; + +namespace Topers.DataAccess.Postgres.Entities; + +/// +/// Represents a good scope entity. +/// +public class GoodScopeEntity +{ + /// + /// Gets or sets a good scope identifier. + /// + public Guid Id { get; set; } + + /// + /// Gets or sets a good identifier. + /// + public Guid GoodId { get; set; } + + /// + /// Gets or sets a good. + /// + public GoodEntity Good { get; set; } = null!; + + /// + /// Gets or sets the volume in liters. + /// + public int Litre { get; set; } + + /// + /// Gets or sets the price. + /// + public decimal Price { get; set; } + + /// + /// Gets or sets a good image. + /// + public string? Image { get; set; } = string.Empty; + + [NotMapped] + /// + /// Gets or sets a good image file. + /// + public IFormFile? ImageFile { get; set; } + + /// + /// Gets or sets an order details about good. + /// + public ICollection? OrderDetails { get; set; } = []; + public ICollection? CartDetails { get; set; } = []; +} diff --git a/Topers.DataAccess.Postgres/Entities/OrderDetailsEntity.cs b/Topers.DataAccess.Postgres/Entities/OrderDetailsEntity.cs new file mode 100644 index 0000000..e8dc2fa --- /dev/null +++ b/Topers.DataAccess.Postgres/Entities/OrderDetailsEntity.cs @@ -0,0 +1,44 @@ +using Topers.Core.Models; + +namespace Topers.DataAccess.Postgres.Entities; + +/// +/// Represents an order details. +/// +public class OrderDetailsEntity +{ + /// + /// Gets or sets an order details identifier. + /// + public Guid Id { get; set; } + + /// + /// Gets or sets an order identifier. + /// + public Guid OrderId { get; set; } + + /// + /// Gets or sets an order. + /// + public OrderEntity Order { get; set; } = null!; + + /// + /// Gets or sets a good identifier. + /// + public Guid GoodId { get; set; } + + /// + /// Gets or sets a good. + /// + public GoodScopeEntity Good { get; set; } = null!; + + /// + /// Gets or sets a good quantity. + /// + public int Quantity { get; set; } = 0; + + /// + /// Gets or sets a good price. + /// + public decimal Price { get; set; } = 0; +} \ No newline at end of file diff --git a/Topers.DataAccess.Postgres/Entities/OrderEntity.cs b/Topers.DataAccess.Postgres/Entities/OrderEntity.cs new file mode 100644 index 0000000..2ede893 --- /dev/null +++ b/Topers.DataAccess.Postgres/Entities/OrderEntity.cs @@ -0,0 +1,37 @@ +namespace Topers.DataAccess.Postgres.Entities; + +/// +/// Represents an order. +/// +public class OrderEntity +{ + /// + /// Gets or sets an order identifier. + /// + public Guid Id { get; set; } + + /// + /// Gets or sets an order date. + /// + public DateTime Date { get; set; } + + /// + /// Gets or sets an order customer identifier. + /// + public Guid CustomerId { get; set; } + + /// + /// Gets or sets an order customer. + /// + public CustomerEntity Customer { get; set; } = null!; + + /// + /// Gets or sets an order total price. + /// + public decimal TotalPrice { get; set; } = 0; + + /// + /// Gets or sets an order details. + /// + public ICollection OrderDetails { get; set; } = []; +} \ No newline at end of file diff --git a/Topers.DataAccess.Postgres/Entities/UserEntity.cs b/Topers.DataAccess.Postgres/Entities/UserEntity.cs new file mode 100644 index 0000000..cbc4ff5 --- /dev/null +++ b/Topers.DataAccess.Postgres/Entities/UserEntity.cs @@ -0,0 +1,27 @@ +namespace Topers.DataAccess.Postgres.Entities; + +/// +/// Represents a user entity. +/// +public class UserEntity +{ + /// + /// Gets or sets a user identifier. + /// + public Guid Id { get; set; } + + /// + /// Gets or sets a user name. + /// + public string Username { get; set; } = string.Empty; + + /// + /// Gets or sets a user hash password. + /// + public string PasswordHash { get; set; } = string.Empty; + + /// + /// Gets or sets a user email. + /// + public string Email { get; set; } = string.Empty; +} \ No newline at end of file diff --git a/Topers.DataAccess.Postgres/Migrations/20240708190433_Initial.Designer.cs b/Topers.DataAccess.Postgres/Migrations/20240708190433_Initial.Designer.cs new file mode 100644 index 0000000..2b43f57 --- /dev/null +++ b/Topers.DataAccess.Postgres/Migrations/20240708190433_Initial.Designer.cs @@ -0,0 +1,299 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Topers.DataAccess.Postgres; + +#nullable disable + +namespace Topers.DataAccess.Postgres.Migrations +{ + [DbContext(typeof(TopersDbContext))] + [Migration("20240708190433_Initial")] + partial class Initial + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.6") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.AddressEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("City") + .IsRequired() + .HasColumnType("text"); + + b.Property("Country") + .IsRequired() + .HasColumnType("text"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("PostalCode") + .IsRequired() + .HasColumnType("text"); + + b.Property("State") + .IsRequired() + .HasColumnType("text"); + + b.Property("Street") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Addresses"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.CategoryEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Categories"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.CustomerEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AddressId") + .HasColumnType("uuid"); + + b.Property("Email") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Phone") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("AddressId") + .IsUnique(); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.GoodEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CategoryId") + .HasColumnType("uuid"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("ImageName") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("CategoryId"); + + b.ToTable("Goods"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.GoodScopeEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("GoodId") + .HasColumnType("uuid"); + + b.Property("Litre") + .HasColumnType("integer"); + + b.Property("Price") + .HasColumnType("numeric"); + + b.HasKey("Id"); + + b.HasIndex("GoodId"); + + b.ToTable("GoodScopes"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.OrderDetailsEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("GoodId") + .HasColumnType("uuid"); + + b.Property("OrderId") + .HasColumnType("uuid"); + + b.Property("Price") + .HasColumnType("numeric"); + + b.Property("Quantity") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("GoodId"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderDetails"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.OrderEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("TotalPrice") + .HasColumnType("numeric"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.CustomerEntity", b => + { + b.HasOne("Topers.DataAccess.Postgres.Entities.AddressEntity", "Address") + .WithOne("Customer") + .HasForeignKey("Topers.DataAccess.Postgres.Entities.CustomerEntity", "AddressId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Address"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.GoodEntity", b => + { + b.HasOne("Topers.DataAccess.Postgres.Entities.CategoryEntity", "Category") + .WithMany("Goods") + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Category"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.GoodScopeEntity", b => + { + b.HasOne("Topers.DataAccess.Postgres.Entities.GoodEntity", "Good") + .WithMany("Scopes") + .HasForeignKey("GoodId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Good"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.OrderDetailsEntity", b => + { + b.HasOne("Topers.DataAccess.Postgres.Entities.GoodEntity", "Good") + .WithMany("OrderDetails") + .HasForeignKey("GoodId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Topers.DataAccess.Postgres.Entities.OrderEntity", "Order") + .WithMany("OrderDetails") + .HasForeignKey("OrderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Good"); + + b.Navigation("Order"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.OrderEntity", b => + { + b.HasOne("Topers.DataAccess.Postgres.Entities.CustomerEntity", "Customer") + .WithMany("Orders") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.AddressEntity", b => + { + b.Navigation("Customer") + .IsRequired(); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.CategoryEntity", b => + { + b.Navigation("Goods"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.CustomerEntity", b => + { + b.Navigation("Orders"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.GoodEntity", b => + { + b.Navigation("OrderDetails"); + + b.Navigation("Scopes"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.OrderEntity", b => + { + b.Navigation("OrderDetails"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Topers.DataAccess.Postgres/Migrations/20240708190433_Initial.cs b/Topers.DataAccess.Postgres/Migrations/20240708190433_Initial.cs new file mode 100644 index 0000000..f1c1b42 --- /dev/null +++ b/Topers.DataAccess.Postgres/Migrations/20240708190433_Initial.cs @@ -0,0 +1,210 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Topers.DataAccess.Postgres.Migrations +{ + /// + public partial class Initial : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Addresses", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + Street = table.Column(type: "text", nullable: false), + City = table.Column(type: "text", nullable: false), + State = table.Column(type: "text", nullable: false), + PostalCode = table.Column(type: "text", nullable: false), + Country = table.Column(type: "text", nullable: false), + CustomerId = table.Column(type: "uuid", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Addresses", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Categories", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + Name = table.Column(type: "text", nullable: false), + Description = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Categories", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Customers", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + Name = table.Column(type: "text", nullable: false), + Email = table.Column(type: "text", nullable: false), + Phone = table.Column(type: "text", nullable: false), + AddressId = table.Column(type: "uuid", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Customers", x => x.Id); + table.ForeignKey( + name: "FK_Customers_Addresses_AddressId", + column: x => x.AddressId, + principalTable: "Addresses", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Goods", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + Name = table.Column(type: "text", nullable: false), + Description = table.Column(type: "text", nullable: true), + ImageName = table.Column(type: "text", nullable: true), + CategoryId = table.Column(type: "uuid", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Goods", x => x.Id); + table.ForeignKey( + name: "FK_Goods_Categories_CategoryId", + column: x => x.CategoryId, + principalTable: "Categories", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Orders", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + Date = table.Column(type: "timestamp with time zone", nullable: false), + CustomerId = table.Column(type: "uuid", nullable: false), + TotalPrice = table.Column(type: "numeric", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Orders", x => x.Id); + table.ForeignKey( + name: "FK_Orders_Customers_CustomerId", + column: x => x.CustomerId, + principalTable: "Customers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "GoodScopes", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + GoodId = table.Column(type: "uuid", nullable: false), + Litre = table.Column(type: "integer", nullable: false), + Price = table.Column(type: "numeric", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_GoodScopes", x => x.Id); + table.ForeignKey( + name: "FK_GoodScopes_Goods_GoodId", + column: x => x.GoodId, + principalTable: "Goods", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "OrderDetails", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + OrderId = table.Column(type: "uuid", nullable: false), + GoodId = table.Column(type: "uuid", nullable: false), + Quantity = table.Column(type: "integer", nullable: false), + Price = table.Column(type: "numeric", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_OrderDetails", x => x.Id); + table.ForeignKey( + name: "FK_OrderDetails_Goods_GoodId", + column: x => x.GoodId, + principalTable: "Goods", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_OrderDetails_Orders_OrderId", + column: x => x.OrderId, + principalTable: "Orders", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_Customers_AddressId", + table: "Customers", + column: "AddressId", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_Goods_CategoryId", + table: "Goods", + column: "CategoryId"); + + migrationBuilder.CreateIndex( + name: "IX_GoodScopes_GoodId", + table: "GoodScopes", + column: "GoodId"); + + migrationBuilder.CreateIndex( + name: "IX_OrderDetails_GoodId", + table: "OrderDetails", + column: "GoodId"); + + migrationBuilder.CreateIndex( + name: "IX_OrderDetails_OrderId", + table: "OrderDetails", + column: "OrderId"); + + migrationBuilder.CreateIndex( + name: "IX_Orders_CustomerId", + table: "Orders", + column: "CustomerId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "GoodScopes"); + + migrationBuilder.DropTable( + name: "OrderDetails"); + + migrationBuilder.DropTable( + name: "Goods"); + + migrationBuilder.DropTable( + name: "Orders"); + + migrationBuilder.DropTable( + name: "Categories"); + + migrationBuilder.DropTable( + name: "Customers"); + + migrationBuilder.DropTable( + name: "Addresses"); + } + } +} diff --git a/Topers.DataAccess.Postgres/Migrations/20240709141734_UpdateCustomersRefFields.Designer.cs b/Topers.DataAccess.Postgres/Migrations/20240709141734_UpdateCustomersRefFields.Designer.cs new file mode 100644 index 0000000..641aa5b --- /dev/null +++ b/Topers.DataAccess.Postgres/Migrations/20240709141734_UpdateCustomersRefFields.Designer.cs @@ -0,0 +1,292 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Topers.DataAccess.Postgres; + +#nullable disable + +namespace Topers.DataAccess.Postgres.Migrations +{ + [DbContext(typeof(TopersDbContext))] + [Migration("20240709141734_UpdateCustomersRefFields")] + partial class UpdateCustomersRefFields + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.6") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.AddressEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("City") + .IsRequired() + .HasColumnType("text"); + + b.Property("Country") + .IsRequired() + .HasColumnType("text"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("PostalCode") + .IsRequired() + .HasColumnType("text"); + + b.Property("State") + .IsRequired() + .HasColumnType("text"); + + b.Property("Street") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId") + .IsUnique(); + + b.ToTable("Addresses"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.CategoryEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Categories"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.CustomerEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Email") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Phone") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.GoodEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CategoryId") + .HasColumnType("uuid"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("ImageName") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("CategoryId"); + + b.ToTable("Goods"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.GoodScopeEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("GoodId") + .HasColumnType("uuid"); + + b.Property("Litre") + .HasColumnType("integer"); + + b.Property("Price") + .HasColumnType("numeric"); + + b.HasKey("Id"); + + b.HasIndex("GoodId"); + + b.ToTable("GoodScopes"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.OrderDetailsEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("GoodId") + .HasColumnType("uuid"); + + b.Property("OrderId") + .HasColumnType("uuid"); + + b.Property("Price") + .HasColumnType("numeric"); + + b.Property("Quantity") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("GoodId"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderDetails"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.OrderEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("TotalPrice") + .HasColumnType("numeric"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.AddressEntity", b => + { + b.HasOne("Topers.DataAccess.Postgres.Entities.CustomerEntity", "Customer") + .WithOne("Address") + .HasForeignKey("Topers.DataAccess.Postgres.Entities.AddressEntity", "CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.GoodEntity", b => + { + b.HasOne("Topers.DataAccess.Postgres.Entities.CategoryEntity", "Category") + .WithMany("Goods") + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Category"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.GoodScopeEntity", b => + { + b.HasOne("Topers.DataAccess.Postgres.Entities.GoodEntity", "Good") + .WithMany("Scopes") + .HasForeignKey("GoodId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Good"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.OrderDetailsEntity", b => + { + b.HasOne("Topers.DataAccess.Postgres.Entities.GoodEntity", "Good") + .WithMany("OrderDetails") + .HasForeignKey("GoodId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Topers.DataAccess.Postgres.Entities.OrderEntity", "Order") + .WithMany("OrderDetails") + .HasForeignKey("OrderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Good"); + + b.Navigation("Order"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.OrderEntity", b => + { + b.HasOne("Topers.DataAccess.Postgres.Entities.CustomerEntity", "Customer") + .WithMany("Orders") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.CategoryEntity", b => + { + b.Navigation("Goods"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.CustomerEntity", b => + { + b.Navigation("Address"); + + b.Navigation("Orders"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.GoodEntity", b => + { + b.Navigation("OrderDetails"); + + b.Navigation("Scopes"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.OrderEntity", b => + { + b.Navigation("OrderDetails"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Topers.DataAccess.Postgres/Migrations/20240709141734_UpdateCustomersRefFields.cs b/Topers.DataAccess.Postgres/Migrations/20240709141734_UpdateCustomersRefFields.cs new file mode 100644 index 0000000..7e3427e --- /dev/null +++ b/Topers.DataAccess.Postgres/Migrations/20240709141734_UpdateCustomersRefFields.cs @@ -0,0 +1,74 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Topers.DataAccess.Postgres.Migrations +{ + /// + public partial class UpdateCustomersRefFields : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_Customers_Addresses_AddressId", + table: "Customers"); + + migrationBuilder.DropIndex( + name: "IX_Customers_AddressId", + table: "Customers"); + + migrationBuilder.DropColumn( + name: "AddressId", + table: "Customers"); + + migrationBuilder.CreateIndex( + name: "IX_Addresses_CustomerId", + table: "Addresses", + column: "CustomerId", + unique: true); + + migrationBuilder.AddForeignKey( + name: "FK_Addresses_Customers_CustomerId", + table: "Addresses", + column: "CustomerId", + principalTable: "Customers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_Addresses_Customers_CustomerId", + table: "Addresses"); + + migrationBuilder.DropIndex( + name: "IX_Addresses_CustomerId", + table: "Addresses"); + + migrationBuilder.AddColumn( + name: "AddressId", + table: "Customers", + type: "uuid", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000")); + + migrationBuilder.CreateIndex( + name: "IX_Customers_AddressId", + table: "Customers", + column: "AddressId", + unique: true); + + migrationBuilder.AddForeignKey( + name: "FK_Customers_Addresses_AddressId", + table: "Customers", + column: "AddressId", + principalTable: "Addresses", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + } + } +} diff --git a/Topers.DataAccess.Postgres/Migrations/20240711155413_AddUserEntity.Designer.cs b/Topers.DataAccess.Postgres/Migrations/20240711155413_AddUserEntity.Designer.cs new file mode 100644 index 0000000..0a5f1f4 --- /dev/null +++ b/Topers.DataAccess.Postgres/Migrations/20240711155413_AddUserEntity.Designer.cs @@ -0,0 +1,315 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Topers.DataAccess.Postgres; + +#nullable disable + +namespace Topers.DataAccess.Postgres.Migrations +{ + [DbContext(typeof(TopersDbContext))] + [Migration("20240711155413_AddUserEntity")] + partial class AddUserEntity + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.6") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.AddressEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("City") + .IsRequired() + .HasColumnType("text"); + + b.Property("Country") + .IsRequired() + .HasColumnType("text"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("PostalCode") + .IsRequired() + .HasColumnType("text"); + + b.Property("State") + .IsRequired() + .HasColumnType("text"); + + b.Property("Street") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId") + .IsUnique(); + + b.ToTable("Addresses"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.CategoryEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Categories"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.CustomerEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Email") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Phone") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.GoodEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CategoryId") + .HasColumnType("uuid"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("ImageName") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("CategoryId"); + + b.ToTable("Goods"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.GoodScopeEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("GoodId") + .HasColumnType("uuid"); + + b.Property("Litre") + .HasColumnType("integer"); + + b.Property("Price") + .HasColumnType("numeric"); + + b.HasKey("Id"); + + b.HasIndex("GoodId"); + + b.ToTable("GoodScopes"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.OrderDetailsEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("GoodId") + .HasColumnType("uuid"); + + b.Property("OrderId") + .HasColumnType("uuid"); + + b.Property("Price") + .HasColumnType("numeric"); + + b.Property("Quantity") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("GoodId"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderDetails"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.OrderEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("TotalPrice") + .HasColumnType("numeric"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.UserEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Email") + .IsRequired() + .HasColumnType("text"); + + b.Property("PasswordHash") + .IsRequired() + .HasColumnType("text"); + + b.Property("Username") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.AddressEntity", b => + { + b.HasOne("Topers.DataAccess.Postgres.Entities.CustomerEntity", "Customer") + .WithOne("Address") + .HasForeignKey("Topers.DataAccess.Postgres.Entities.AddressEntity", "CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.GoodEntity", b => + { + b.HasOne("Topers.DataAccess.Postgres.Entities.CategoryEntity", "Category") + .WithMany("Goods") + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Category"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.GoodScopeEntity", b => + { + b.HasOne("Topers.DataAccess.Postgres.Entities.GoodEntity", "Good") + .WithMany("Scopes") + .HasForeignKey("GoodId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Good"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.OrderDetailsEntity", b => + { + b.HasOne("Topers.DataAccess.Postgres.Entities.GoodEntity", "Good") + .WithMany("OrderDetails") + .HasForeignKey("GoodId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Topers.DataAccess.Postgres.Entities.OrderEntity", "Order") + .WithMany("OrderDetails") + .HasForeignKey("OrderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Good"); + + b.Navigation("Order"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.OrderEntity", b => + { + b.HasOne("Topers.DataAccess.Postgres.Entities.CustomerEntity", "Customer") + .WithMany("Orders") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.CategoryEntity", b => + { + b.Navigation("Goods"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.CustomerEntity", b => + { + b.Navigation("Address"); + + b.Navigation("Orders"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.GoodEntity", b => + { + b.Navigation("OrderDetails"); + + b.Navigation("Scopes"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.OrderEntity", b => + { + b.Navigation("OrderDetails"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Topers.DataAccess.Postgres/Migrations/20240711155413_AddUserEntity.cs b/Topers.DataAccess.Postgres/Migrations/20240711155413_AddUserEntity.cs new file mode 100644 index 0000000..c68652c --- /dev/null +++ b/Topers.DataAccess.Postgres/Migrations/20240711155413_AddUserEntity.cs @@ -0,0 +1,36 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Topers.DataAccess.Postgres.Migrations +{ + /// + public partial class AddUserEntity : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Users", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + Username = table.Column(type: "text", nullable: false), + PasswordHash = table.Column(type: "text", nullable: false), + Email = table.Column(type: "text", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Users", x => x.Id); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Users"); + } + } +} diff --git a/Topers.DataAccess.Postgres/Migrations/20240712123931_UpdateGoodImageField.Designer.cs b/Topers.DataAccess.Postgres/Migrations/20240712123931_UpdateGoodImageField.Designer.cs new file mode 100644 index 0000000..d521402 --- /dev/null +++ b/Topers.DataAccess.Postgres/Migrations/20240712123931_UpdateGoodImageField.Designer.cs @@ -0,0 +1,315 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Topers.DataAccess.Postgres; + +#nullable disable + +namespace Topers.DataAccess.Postgres.Migrations +{ + [DbContext(typeof(TopersDbContext))] + [Migration("20240712123931_UpdateGoodImageField")] + partial class UpdateGoodImageField + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.6") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.AddressEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("City") + .IsRequired() + .HasColumnType("text"); + + b.Property("Country") + .IsRequired() + .HasColumnType("text"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("PostalCode") + .IsRequired() + .HasColumnType("text"); + + b.Property("State") + .IsRequired() + .HasColumnType("text"); + + b.Property("Street") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId") + .IsUnique(); + + b.ToTable("Addresses"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.CategoryEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Categories"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.CustomerEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Email") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Phone") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.GoodEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CategoryId") + .HasColumnType("uuid"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("CategoryId"); + + b.ToTable("Goods"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.GoodScopeEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("GoodId") + .HasColumnType("uuid"); + + b.Property("Image") + .HasColumnType("text"); + + b.Property("Litre") + .HasColumnType("integer"); + + b.Property("Price") + .HasColumnType("numeric"); + + b.HasKey("Id"); + + b.HasIndex("GoodId"); + + b.ToTable("GoodScopes"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.OrderDetailsEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("GoodId") + .HasColumnType("uuid"); + + b.Property("OrderId") + .HasColumnType("uuid"); + + b.Property("Price") + .HasColumnType("numeric"); + + b.Property("Quantity") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("GoodId"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderDetails"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.OrderEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("TotalPrice") + .HasColumnType("numeric"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.UserEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Email") + .IsRequired() + .HasColumnType("text"); + + b.Property("PasswordHash") + .IsRequired() + .HasColumnType("text"); + + b.Property("Username") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.AddressEntity", b => + { + b.HasOne("Topers.DataAccess.Postgres.Entities.CustomerEntity", "Customer") + .WithOne("Address") + .HasForeignKey("Topers.DataAccess.Postgres.Entities.AddressEntity", "CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.GoodEntity", b => + { + b.HasOne("Topers.DataAccess.Postgres.Entities.CategoryEntity", "Category") + .WithMany("Goods") + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Category"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.GoodScopeEntity", b => + { + b.HasOne("Topers.DataAccess.Postgres.Entities.GoodEntity", "Good") + .WithMany("Scopes") + .HasForeignKey("GoodId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Good"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.OrderDetailsEntity", b => + { + b.HasOne("Topers.DataAccess.Postgres.Entities.GoodEntity", "Good") + .WithMany("OrderDetails") + .HasForeignKey("GoodId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Topers.DataAccess.Postgres.Entities.OrderEntity", "Order") + .WithMany("OrderDetails") + .HasForeignKey("OrderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Good"); + + b.Navigation("Order"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.OrderEntity", b => + { + b.HasOne("Topers.DataAccess.Postgres.Entities.CustomerEntity", "Customer") + .WithMany("Orders") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.CategoryEntity", b => + { + b.Navigation("Goods"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.CustomerEntity", b => + { + b.Navigation("Address"); + + b.Navigation("Orders"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.GoodEntity", b => + { + b.Navigation("OrderDetails"); + + b.Navigation("Scopes"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.OrderEntity", b => + { + b.Navigation("OrderDetails"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Topers.DataAccess.Postgres/Migrations/20240712123931_UpdateGoodImageField.cs b/Topers.DataAccess.Postgres/Migrations/20240712123931_UpdateGoodImageField.cs new file mode 100644 index 0000000..609cf16 --- /dev/null +++ b/Topers.DataAccess.Postgres/Migrations/20240712123931_UpdateGoodImageField.cs @@ -0,0 +1,38 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Topers.DataAccess.Postgres.Migrations +{ + /// + public partial class UpdateGoodImageField : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "ImageName", + table: "Goods"); + + migrationBuilder.AddColumn( + name: "Image", + table: "GoodScopes", + type: "text", + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "Image", + table: "GoodScopes"); + + migrationBuilder.AddColumn( + name: "ImageName", + table: "Goods", + type: "text", + nullable: true); + } + } +} diff --git a/Topers.DataAccess.Postgres/Migrations/20240721171553_UpdateScopeRef.Designer.cs b/Topers.DataAccess.Postgres/Migrations/20240721171553_UpdateScopeRef.Designer.cs new file mode 100644 index 0000000..733e0a9 --- /dev/null +++ b/Topers.DataAccess.Postgres/Migrations/20240721171553_UpdateScopeRef.Designer.cs @@ -0,0 +1,318 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Topers.DataAccess.Postgres; + +#nullable disable + +namespace Topers.DataAccess.Postgres.Migrations +{ + [DbContext(typeof(TopersDbContext))] + [Migration("20240721171553_UpdateScopeRef")] + partial class UpdateScopeRef + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.6") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.AddressEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("City") + .IsRequired() + .HasColumnType("text"); + + b.Property("Country") + .IsRequired() + .HasColumnType("text"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("PostalCode") + .IsRequired() + .HasColumnType("text"); + + b.Property("State") + .IsRequired() + .HasColumnType("text"); + + b.Property("Street") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId") + .IsUnique(); + + b.ToTable("Addresses"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.CategoryEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Categories"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.CustomerEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Email") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Phone") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.GoodEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CategoryId") + .HasColumnType("uuid"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("CategoryId"); + + b.ToTable("Goods"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.GoodScopeEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("GoodId") + .HasColumnType("uuid"); + + b.Property("Image") + .HasColumnType("text"); + + b.Property("Litre") + .HasColumnType("integer"); + + b.Property("Price") + .HasColumnType("numeric"); + + b.HasKey("Id"); + + b.HasIndex("GoodId"); + + b.ToTable("GoodScopes"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.OrderDetailsEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("GoodId") + .HasColumnType("uuid"); + + b.Property("OrderId") + .HasColumnType("uuid"); + + b.Property("Price") + .HasColumnType("numeric"); + + b.Property("Quantity") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("GoodId"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderDetails"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.OrderEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("TotalPrice") + .HasColumnType("numeric"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.UserEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Email") + .IsRequired() + .HasColumnType("text"); + + b.Property("PasswordHash") + .IsRequired() + .HasColumnType("text"); + + b.Property("Username") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.AddressEntity", b => + { + b.HasOne("Topers.DataAccess.Postgres.Entities.CustomerEntity", "Customer") + .WithOne("Address") + .HasForeignKey("Topers.DataAccess.Postgres.Entities.AddressEntity", "CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.GoodEntity", b => + { + b.HasOne("Topers.DataAccess.Postgres.Entities.CategoryEntity", "Category") + .WithMany("Goods") + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Category"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.GoodScopeEntity", b => + { + b.HasOne("Topers.DataAccess.Postgres.Entities.GoodEntity", "Good") + .WithMany("Scopes") + .HasForeignKey("GoodId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Good"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.OrderDetailsEntity", b => + { + b.HasOne("Topers.DataAccess.Postgres.Entities.GoodScopeEntity", "Good") + .WithMany("OrderDetails") + .HasForeignKey("GoodId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Topers.DataAccess.Postgres.Entities.OrderEntity", "Order") + .WithMany("OrderDetails") + .HasForeignKey("OrderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Good"); + + b.Navigation("Order"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.OrderEntity", b => + { + b.HasOne("Topers.DataAccess.Postgres.Entities.CustomerEntity", "Customer") + .WithMany("Orders") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.CategoryEntity", b => + { + b.Navigation("Goods"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.CustomerEntity", b => + { + b.Navigation("Address"); + + b.Navigation("Orders"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.GoodEntity", b => + { + b.Navigation("Scopes"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.GoodScopeEntity", b => + { + b.Navigation("OrderDetails"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.OrderEntity", b => + { + b.Navigation("OrderDetails"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Topers.DataAccess.Postgres/Migrations/20240721171553_UpdateScopeRef.cs b/Topers.DataAccess.Postgres/Migrations/20240721171553_UpdateScopeRef.cs new file mode 100644 index 0000000..cdce613 --- /dev/null +++ b/Topers.DataAccess.Postgres/Migrations/20240721171553_UpdateScopeRef.cs @@ -0,0 +1,42 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Topers.DataAccess.Postgres.Migrations +{ + /// + public partial class UpdateScopeRef : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_OrderDetails_Goods_GoodId", + table: "OrderDetails"); + + migrationBuilder.AddForeignKey( + name: "FK_OrderDetails_GoodScopes_GoodId", + table: "OrderDetails", + column: "GoodId", + principalTable: "GoodScopes", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_OrderDetails_GoodScopes_GoodId", + table: "OrderDetails"); + + migrationBuilder.AddForeignKey( + name: "FK_OrderDetails_Goods_GoodId", + table: "OrderDetails", + column: "GoodId", + principalTable: "Goods", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + } + } +} diff --git a/Topers.DataAccess.Postgres/Migrations/20240728174427_AddCart.Designer.cs b/Topers.DataAccess.Postgres/Migrations/20240728174427_AddCart.Designer.cs new file mode 100644 index 0000000..dbb002e --- /dev/null +++ b/Topers.DataAccess.Postgres/Migrations/20240728174427_AddCart.Designer.cs @@ -0,0 +1,407 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Topers.DataAccess.Postgres; + +#nullable disable + +namespace Topers.DataAccess.Postgres.Migrations +{ + [DbContext(typeof(TopersDbContext))] + [Migration("20240728174427_AddCart")] + partial class AddCart + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.6") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.AddressEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("City") + .IsRequired() + .HasColumnType("text"); + + b.Property("Country") + .IsRequired() + .HasColumnType("text"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("PostalCode") + .IsRequired() + .HasColumnType("text"); + + b.Property("State") + .IsRequired() + .HasColumnType("text"); + + b.Property("Street") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId") + .IsUnique(); + + b.ToTable("Addresses"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.CartEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("UpdatedDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId") + .IsUnique(); + + b.ToTable("Cart"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.CartItemEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CartId") + .HasColumnType("uuid"); + + b.Property("GoodId") + .HasColumnType("uuid"); + + b.Property("Price") + .HasColumnType("numeric"); + + b.Property("Quantity") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CartId"); + + b.HasIndex("GoodId"); + + b.ToTable("CartDetails"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.CategoryEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Categories"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.CustomerEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Email") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Phone") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.GoodEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CategoryId") + .HasColumnType("uuid"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("CategoryId"); + + b.ToTable("Goods"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.GoodScopeEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("GoodId") + .HasColumnType("uuid"); + + b.Property("Image") + .HasColumnType("text"); + + b.Property("Litre") + .HasColumnType("integer"); + + b.Property("Price") + .HasColumnType("numeric"); + + b.HasKey("Id"); + + b.HasIndex("GoodId"); + + b.ToTable("GoodScopes"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.OrderDetailsEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("GoodId") + .HasColumnType("uuid"); + + b.Property("OrderId") + .HasColumnType("uuid"); + + b.Property("Price") + .HasColumnType("numeric"); + + b.Property("Quantity") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("GoodId"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderDetails"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.OrderEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("TotalPrice") + .HasColumnType("numeric"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.UserEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Email") + .IsRequired() + .HasColumnType("text"); + + b.Property("PasswordHash") + .IsRequired() + .HasColumnType("text"); + + b.Property("Username") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.AddressEntity", b => + { + b.HasOne("Topers.DataAccess.Postgres.Entities.CustomerEntity", "Customer") + .WithOne("Address") + .HasForeignKey("Topers.DataAccess.Postgres.Entities.AddressEntity", "CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.CartEntity", b => + { + b.HasOne("Topers.DataAccess.Postgres.Entities.CustomerEntity", "Customer") + .WithOne("Cart") + .HasForeignKey("Topers.DataAccess.Postgres.Entities.CartEntity", "CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.CartItemEntity", b => + { + b.HasOne("Topers.DataAccess.Postgres.Entities.CartEntity", "Cart") + .WithMany("CartItems") + .HasForeignKey("CartId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Topers.DataAccess.Postgres.Entities.GoodScopeEntity", "Good") + .WithMany("CartDetails") + .HasForeignKey("GoodId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cart"); + + b.Navigation("Good"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.GoodEntity", b => + { + b.HasOne("Topers.DataAccess.Postgres.Entities.CategoryEntity", "Category") + .WithMany("Goods") + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Category"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.GoodScopeEntity", b => + { + b.HasOne("Topers.DataAccess.Postgres.Entities.GoodEntity", "Good") + .WithMany("Scopes") + .HasForeignKey("GoodId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Good"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.OrderDetailsEntity", b => + { + b.HasOne("Topers.DataAccess.Postgres.Entities.GoodScopeEntity", "Good") + .WithMany("OrderDetails") + .HasForeignKey("GoodId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Topers.DataAccess.Postgres.Entities.OrderEntity", "Order") + .WithMany("OrderDetails") + .HasForeignKey("OrderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Good"); + + b.Navigation("Order"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.OrderEntity", b => + { + b.HasOne("Topers.DataAccess.Postgres.Entities.CustomerEntity", "Customer") + .WithMany("Orders") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.CartEntity", b => + { + b.Navigation("CartItems"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.CategoryEntity", b => + { + b.Navigation("Goods"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.CustomerEntity", b => + { + b.Navigation("Address"); + + b.Navigation("Cart"); + + b.Navigation("Orders"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.GoodEntity", b => + { + b.Navigation("Scopes"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.GoodScopeEntity", b => + { + b.Navigation("CartDetails"); + + b.Navigation("OrderDetails"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.OrderEntity", b => + { + b.Navigation("OrderDetails"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Topers.DataAccess.Postgres/Migrations/20240728174427_AddCart.cs b/Topers.DataAccess.Postgres/Migrations/20240728174427_AddCart.cs new file mode 100644 index 0000000..b0e4f50 --- /dev/null +++ b/Topers.DataAccess.Postgres/Migrations/20240728174427_AddCart.cs @@ -0,0 +1,88 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Topers.DataAccess.Postgres.Migrations +{ + /// + public partial class AddCart : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Cart", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + CustomerId = table.Column(type: "uuid", nullable: false), + CreatedDate = table.Column(type: "timestamp with time zone", nullable: false), + UpdatedDate = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Cart", x => x.Id); + table.ForeignKey( + name: "FK_Cart_Customers_CustomerId", + column: x => x.CustomerId, + principalTable: "Customers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "CartDetails", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + CartId = table.Column(type: "uuid", nullable: false), + GoodId = table.Column(type: "uuid", nullable: false), + Quantity = table.Column(type: "integer", nullable: false), + Price = table.Column(type: "numeric", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CartDetails", x => x.Id); + table.ForeignKey( + name: "FK_CartDetails_Cart_CartId", + column: x => x.CartId, + principalTable: "Cart", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_CartDetails_GoodScopes_GoodId", + column: x => x.GoodId, + principalTable: "GoodScopes", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_Cart_CustomerId", + table: "Cart", + column: "CustomerId", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_CartDetails_CartId", + table: "CartDetails", + column: "CartId"); + + migrationBuilder.CreateIndex( + name: "IX_CartDetails_GoodId", + table: "CartDetails", + column: "GoodId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "CartDetails"); + + migrationBuilder.DropTable( + name: "Cart"); + } + } +} diff --git a/Topers.DataAccess.Postgres/Migrations/TopersDbContextModelSnapshot.cs b/Topers.DataAccess.Postgres/Migrations/TopersDbContextModelSnapshot.cs new file mode 100644 index 0000000..df7c780 --- /dev/null +++ b/Topers.DataAccess.Postgres/Migrations/TopersDbContextModelSnapshot.cs @@ -0,0 +1,404 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Topers.DataAccess.Postgres; + +#nullable disable + +namespace Topers.DataAccess.Postgres.Migrations +{ + [DbContext(typeof(TopersDbContext))] + partial class TopersDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.6") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.AddressEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("City") + .IsRequired() + .HasColumnType("text"); + + b.Property("Country") + .IsRequired() + .HasColumnType("text"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("PostalCode") + .IsRequired() + .HasColumnType("text"); + + b.Property("State") + .IsRequired() + .HasColumnType("text"); + + b.Property("Street") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId") + .IsUnique(); + + b.ToTable("Addresses"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.CartEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("UpdatedDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId") + .IsUnique(); + + b.ToTable("Cart"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.CartItemEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CartId") + .HasColumnType("uuid"); + + b.Property("GoodId") + .HasColumnType("uuid"); + + b.Property("Price") + .HasColumnType("numeric"); + + b.Property("Quantity") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CartId"); + + b.HasIndex("GoodId"); + + b.ToTable("CartDetails"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.CategoryEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Categories"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.CustomerEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Email") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Phone") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.GoodEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CategoryId") + .HasColumnType("uuid"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("CategoryId"); + + b.ToTable("Goods"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.GoodScopeEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("GoodId") + .HasColumnType("uuid"); + + b.Property("Image") + .HasColumnType("text"); + + b.Property("Litre") + .HasColumnType("integer"); + + b.Property("Price") + .HasColumnType("numeric"); + + b.HasKey("Id"); + + b.HasIndex("GoodId"); + + b.ToTable("GoodScopes"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.OrderDetailsEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("GoodId") + .HasColumnType("uuid"); + + b.Property("OrderId") + .HasColumnType("uuid"); + + b.Property("Price") + .HasColumnType("numeric"); + + b.Property("Quantity") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("GoodId"); + + b.HasIndex("OrderId"); + + b.ToTable("OrderDetails"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.OrderEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("TotalPrice") + .HasColumnType("numeric"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.UserEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Email") + .IsRequired() + .HasColumnType("text"); + + b.Property("PasswordHash") + .IsRequired() + .HasColumnType("text"); + + b.Property("Username") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.AddressEntity", b => + { + b.HasOne("Topers.DataAccess.Postgres.Entities.CustomerEntity", "Customer") + .WithOne("Address") + .HasForeignKey("Topers.DataAccess.Postgres.Entities.AddressEntity", "CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.CartEntity", b => + { + b.HasOne("Topers.DataAccess.Postgres.Entities.CustomerEntity", "Customer") + .WithOne("Cart") + .HasForeignKey("Topers.DataAccess.Postgres.Entities.CartEntity", "CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.CartItemEntity", b => + { + b.HasOne("Topers.DataAccess.Postgres.Entities.CartEntity", "Cart") + .WithMany("CartItems") + .HasForeignKey("CartId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Topers.DataAccess.Postgres.Entities.GoodScopeEntity", "Good") + .WithMany("CartDetails") + .HasForeignKey("GoodId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cart"); + + b.Navigation("Good"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.GoodEntity", b => + { + b.HasOne("Topers.DataAccess.Postgres.Entities.CategoryEntity", "Category") + .WithMany("Goods") + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Category"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.GoodScopeEntity", b => + { + b.HasOne("Topers.DataAccess.Postgres.Entities.GoodEntity", "Good") + .WithMany("Scopes") + .HasForeignKey("GoodId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Good"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.OrderDetailsEntity", b => + { + b.HasOne("Topers.DataAccess.Postgres.Entities.GoodScopeEntity", "Good") + .WithMany("OrderDetails") + .HasForeignKey("GoodId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Topers.DataAccess.Postgres.Entities.OrderEntity", "Order") + .WithMany("OrderDetails") + .HasForeignKey("OrderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Good"); + + b.Navigation("Order"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.OrderEntity", b => + { + b.HasOne("Topers.DataAccess.Postgres.Entities.CustomerEntity", "Customer") + .WithMany("Orders") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.CartEntity", b => + { + b.Navigation("CartItems"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.CategoryEntity", b => + { + b.Navigation("Goods"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.CustomerEntity", b => + { + b.Navigation("Address"); + + b.Navigation("Cart"); + + b.Navigation("Orders"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.GoodEntity", b => + { + b.Navigation("Scopes"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.GoodScopeEntity", b => + { + b.Navigation("CartDetails"); + + b.Navigation("OrderDetails"); + }); + + modelBuilder.Entity("Topers.DataAccess.Postgres.Entities.OrderEntity", b => + { + b.Navigation("OrderDetails"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Topers.DataAccess.Postgres/Repositories/AddressesRepository.cs b/Topers.DataAccess.Postgres/Repositories/AddressesRepository.cs new file mode 100644 index 0000000..472ef65 --- /dev/null +++ b/Topers.DataAccess.Postgres/Repositories/AddressesRepository.cs @@ -0,0 +1,35 @@ +using AutoMapper; +using Topers.Core.Abstractions; +using Topers.Core.Models; +using Topers.DataAccess.Postgres.Entities; + +namespace Topers.DataAccess.Postgres.Repositories; + +public class AddressesRepository : IAddressesRepository +{ + private readonly TopersDbContext _context; + + public AddressesRepository(TopersDbContext context) + { + _context = context; + } + + public async Task CreateAsync(Address address, CancellationToken cancellationToken = default) + { + var addressEntity = new AddressEntity + { + Id = Guid.NewGuid(), + CustomerId = address.CustomerId, + Street = address.Street, + City = address.City, + State = address.State, + PostalCode = address.PostalCode, + Country = address.Country + }; + + await _context.Addresses.AddAsync(addressEntity); + await _context.SaveChangesAsync(); + + return addressEntity.Id; + } +} \ No newline at end of file diff --git a/Topers.DataAccess.Postgres/Repositories/CartsRepository.cs b/Topers.DataAccess.Postgres/Repositories/CartsRepository.cs new file mode 100644 index 0000000..592c472 --- /dev/null +++ b/Topers.DataAccess.Postgres/Repositories/CartsRepository.cs @@ -0,0 +1,89 @@ +using AutoMapper; +using Microsoft.EntityFrameworkCore; +using Topers.Core.Abstractions; +using Topers.Core.Models; +using Topers.DataAccess.Postgres.Entities; + +namespace Topers.DataAccess.Postgres.Repositories; + +public class CartsRepository : ICartsRepository +{ + private readonly TopersDbContext _context; + private readonly IMapper _mapper; + + public CartsRepository(TopersDbContext context, IMapper mapper) + { + _context = context; + _mapper = mapper; + } + + public async Task AddDetailAsync( + CartItems cartDetails, + CancellationToken cancellationToken = default + ) + { + var cartItemEntity = new CartItemEntity + { + Id = Guid.NewGuid(), + CartId = cartDetails.CartId, + GoodId = cartDetails.GoodId, + Quantity = cartDetails.Quantity, + Price = cartDetails.Price + }; + + await _context.CartDetails.AddAsync(cartItemEntity); + await _context.SaveChangesAsync(); + + return cartItemEntity.Id; + } + + public async Task CreateAsync(Cart cart, CancellationToken cancellationToken = default) + { + var cartEntity = new CartEntity + { + Id = Guid.NewGuid(), + CustomerId = cart.CustomerId, + CreatedDate = DateTime.UtcNow, + UpdatedDate = DateTime.UtcNow + }; + + await _context.Cart.AddAsync(cartEntity); + await _context.SaveChangesAsync(); + + return cartEntity.Id; + } + + public async Task DeleteAsync(Guid cartId, CancellationToken cancellationToken = default) + { + await _context.Cart.Where(c => c.Id == cartId).ExecuteDeleteAsync(); + + return cartId; + } + + public async Task GetByCustomerId( + Guid customerId, + CancellationToken cancellationToken = default + ) + { + var cartEntity = await _context + .Cart.Where(c => c.CustomerId == customerId) + .AsNoTracking() + .ToListAsync(); + + var cart = _mapper.Map(cartEntity); + + return cart; + } + + public async Task GetById(Guid cartId, CancellationToken cancellationToken = default) + { + var cartEntity = await _context + .Cart.Where(c => c.Id == cartId) + .AsNoTracking() + .ToListAsync(); + + var cart = _mapper.Map(cartEntity); + + return cart; + } +} diff --git a/Topers.DataAccess.Postgres/Repositories/CategoriesRepository.cs b/Topers.DataAccess.Postgres/Repositories/CategoriesRepository.cs new file mode 100644 index 0000000..1c1a8bc --- /dev/null +++ b/Topers.DataAccess.Postgres/Repositories/CategoriesRepository.cs @@ -0,0 +1,87 @@ +using AutoMapper; +using Microsoft.EntityFrameworkCore; +using Topers.Core.Abstractions; +using Topers.Core.Models; +using Topers.DataAccess.Postgres.Entities; + +namespace Topers.DataAccess.Postgres.Repositories; + +public class CategoriesRepository : ICategoriesRepository +{ + private readonly TopersDbContext _context; + private readonly IMapper _mapper; + + public CategoriesRepository(TopersDbContext context, IMapper mapper) + { + _context = context; + _mapper = mapper; + } + + public async Task CreateAsync(Category category, CancellationToken cancellationToken = default) + { + var categoryEntity = new CategoryEntity + { + Id = Guid.NewGuid(), + Name = category.Name, + Description = category.Description + }; + + await _context.Categories.AddAsync(categoryEntity); + await _context.SaveChangesAsync(); + + return categoryEntity.Id; + } + + public async Task DeleteAsync(Guid categoryId, CancellationToken cancellationToken = default) + { + await _context.Categories + .Where(c => c.Id == categoryId) + .ExecuteDeleteAsync(); + + return categoryId; + } + + public async Task> GetAllAsync(CancellationToken cancellationToken = default) + { + var categoryEntities = await _context.Categories + .AsNoTracking() + .ToListAsync(); + + var categoryEntitiesDto = _mapper.Map>(categoryEntities); + + return categoryEntitiesDto; + } + + public async Task GetByIdAsync(Guid categoryId, CancellationToken cancellationToken = default) + { + var categoryEntity = await _context.Categories + .AsNoTracking() + .FirstOrDefaultAsync(c => c.Id == categoryId); + + var categoryEntityDto = _mapper.Map(categoryEntity); + + return categoryEntityDto; + } + + public async Task> GetGoodsByIdAsync(Guid categoryId, CancellationToken cancellationToken = default) + { + var goodEntities = await _context.Goods + .Where(g => g.CategoryId == categoryId) + .ToListAsync(); + + var goodEntitiesDto = _mapper.Map>(goodEntities); + + return goodEntitiesDto; + } + + public async Task UpdateAsync(Category category, CancellationToken cancellationToken = default) + { + await _context.Categories + .Where(c => c.Id == category.Id) + .ExecuteUpdateAsync(cUpdate => cUpdate + .SetProperty(c => c.Name, category.Name) + .SetProperty(c => c.Description, category.Description)); + + return category.Id; + } +} \ No newline at end of file diff --git a/Topers.DataAccess.Postgres/Repositories/CustomersRepository.cs b/Topers.DataAccess.Postgres/Repositories/CustomersRepository.cs new file mode 100644 index 0000000..7d7e977 --- /dev/null +++ b/Topers.DataAccess.Postgres/Repositories/CustomersRepository.cs @@ -0,0 +1,58 @@ +using AutoMapper; +using Microsoft.EntityFrameworkCore; +using Topers.Core.Abstractions; +using Topers.Core.Models; +using Topers.DataAccess.Postgres.Entities; + +namespace Topers.DataAccess.Postgres.Repositories; + +public class CustomersRepository : ICustomersRepository +{ + private readonly TopersDbContext _context; + private readonly IMapper _mapper; + + public CustomersRepository(TopersDbContext context, IMapper mapper) + { + _context = context; + _mapper = mapper; + } + + public async Task CreateAsync(Customer customer, CancellationToken cancellationToken = default) + { + var customerEntity = new CustomerEntity + { + Id = Guid.NewGuid(), + Name = customer.Name, + Email = customer.Email, + Phone = customer.Phone + }; + + await _context.Customers.AddAsync(customerEntity); + await _context.SaveChangesAsync(); + + return customerEntity.Id; + } + + public async Task> GetAllAsync(CancellationToken cancellationToken = default) + { + var customerEntities = await _context.Customers + .Include(c => c.Address) + .AsNoTracking() + .ToListAsync(); + + var customerEntitiesDto = _mapper.Map>(customerEntities); + + return customerEntitiesDto; + } + + public async Task GetByIdAsync(Guid customerId, CancellationToken cancellationToken = default) + { + var customerEntity = await _context.Customers + .Include(c => c.Address) + .FirstOrDefaultAsync(c => c.Id == customerId); + + var customerEntityDto = _mapper.Map(customerEntity); + + return customerEntityDto; + } +} \ No newline at end of file diff --git a/Topers.DataAccess.Postgres/Repositories/GoodsRepository.cs b/Topers.DataAccess.Postgres/Repositories/GoodsRepository.cs new file mode 100644 index 0000000..c8263a9 --- /dev/null +++ b/Topers.DataAccess.Postgres/Repositories/GoodsRepository.cs @@ -0,0 +1,134 @@ +using AutoMapper; +using Microsoft.EntityFrameworkCore; +using Topers.Core.Abstractions; +using Topers.Core.Models; +using Topers.DataAccess.Postgres.Entities; + +namespace Topers.DataAccess.Postgres.Repositories; + +public class GoodsRepository : IGoodsRepository +{ + private readonly TopersDbContext _context; + private readonly IMapper _mapper; + + public GoodsRepository(TopersDbContext context, IMapper mapper) + { + _context = context; + _mapper = mapper; + } + + public async Task CreateAsync(Good good, CancellationToken cancellationToken = default) + { + var goodEntity = new GoodEntity + { + Id = Guid.NewGuid(), + CategoryId = good.CategoryId, + Name = good.Name, + Description = good.Description + }; + + await _context.Goods.AddAsync(goodEntity); + await _context.SaveChangesAsync(); + + return goodEntity.Id; + } + + public async Task DeleteAsync(Guid goodId, CancellationToken cancellationToken = default) + { + await _context.Goods + .Where(g => g.Id == goodId) + .ExecuteDeleteAsync(); + + return goodId; + } + + public async Task> GetAllAsync(CancellationToken cancellationToken = default) + { + var goodEntities = await _context.Goods + .Include(g => g.Scopes) + .AsNoTracking() + .ToListAsync(); + + var goodEntitiesDto = _mapper.Map>(goodEntities); + + return goodEntitiesDto; + } + + public async Task> GetByFilterAsync(string title, CancellationToken cancellationToken = default) + { + var query = _context.Goods.Include(g => g.Scopes).AsNoTracking(); + + if(!string.IsNullOrEmpty(title)) + { + query = query.Where(g => g.Name.Contains(title)); + } + + var goodEntitiesDto = _mapper.Map>(await query.ToListAsync()); + + return goodEntitiesDto; + } + + public async Task GetByIdAsync(Guid goodId, CancellationToken cancellationToken = default) + { + var goodEntity = await _context.Goods + .AsNoTracking() + .FirstOrDefaultAsync(g => g.Id == goodId); + + var goodEntityDto = _mapper.Map(goodEntity); + + return goodEntityDto; + } + + public async Task UpdateAsync(Good good, CancellationToken cancellationToken = default) + { + await _context.Goods + .Where(g => g.Id == good.Id) + .ExecuteUpdateAsync(s => s + .SetProperty(g => g.Name, good.Name) + .SetProperty(g => g.CategoryId, good.CategoryId) + .SetProperty(g => g.Description, good.Description)); + + return good.Id; + } + + public async Task GetScopeAsync(Guid goodId, int litre, CancellationToken cancellationToken = default) + { + var pExistsGoodScope = await _context.GoodScopes + .FirstOrDefaultAsync(gs => gs.GoodId == goodId && gs.Litre == litre); + + return _mapper.Map(pExistsGoodScope); + } + + public async Task AddScopeAsync(GoodScope goodScope, CancellationToken cancellationToken = default) + { + var scopeEntity = new GoodScopeEntity + { + Id = Guid.NewGuid(), + GoodId = goodScope.GoodId, + Litre = goodScope.Litre, + Price = goodScope.Price, + Image = goodScope.ImageName + }; + + await _context.GoodScopes.AddAsync(scopeEntity); + await _context.SaveChangesAsync(); + + return scopeEntity.Id; + } + + public async Task UpdateScopeAsync(GoodScope goodScope, CancellationToken cancellationToken = default) + { + var existingGoodScope = await _context.GoodScopes + .AsNoTracking() + .FirstOrDefaultAsync(gs => gs.GoodId == goodScope.GoodId && gs.Litre == goodScope.Litre); + + await _context.GoodScopes + .Where(gs => gs.GoodId == goodScope.GoodId && gs.Litre == goodScope.Litre) + .ExecuteUpdateAsync(s => s + .SetProperty(gs => gs.Image, goodScope.ImageName) + .SetProperty(gs => gs.Litre, goodScope.Litre) + .SetProperty(gs => gs.Price, goodScope.Price)); + + return existingGoodScope!.Id; + } +} \ No newline at end of file diff --git a/Topers.DataAccess.Postgres/Repositories/OrdersRepository.cs b/Topers.DataAccess.Postgres/Repositories/OrdersRepository.cs new file mode 100644 index 0000000..c75c7cc --- /dev/null +++ b/Topers.DataAccess.Postgres/Repositories/OrdersRepository.cs @@ -0,0 +1,114 @@ +using AutoMapper; +using Microsoft.EntityFrameworkCore; +using Topers.Core.Abstractions; +using Topers.Core.Models; +using Topers.DataAccess.Postgres.Entities; + +namespace Topers.DataAccess.Postgres.Repositories; + +public class OrdersRepository : IOrdersRepository +{ + private readonly TopersDbContext _context; + private readonly IMapper _mapper; + + public OrdersRepository(TopersDbContext context, IMapper mapper) + { + _context = context; + _mapper = mapper; + } + + public async Task CreateAsync(Order order, CancellationToken cancellationToken = default) + { + var orderEntity = new OrderEntity + { + Id = Guid.NewGuid(), + Date = DateTime.UtcNow, + CustomerId = order.CustomerId, + TotalPrice = order.TotalPrice + }; + + await _context.Orders.AddAsync(orderEntity); + await _context.SaveChangesAsync(); + + return orderEntity.Id; + } + + public async Task DeleteAsync(Guid orderId, CancellationToken cancellationToken = default) + { + await _context.Orders.Where(o => o.Id == orderId).ExecuteDeleteAsync(); + + return orderId; + } + + public async Task> GetAllAsync(CancellationToken cancellationToken = default) + { + var orderEntities = await _context + .Orders.Include(o => o.OrderDetails) + .AsNoTracking() + .ToListAsync(); + + var orders = _mapper.Map>(orderEntities); + + return orders; + } + + public async Task GetByIdAsync( + Guid orderId, + CancellationToken cancellationToken = default + ) + { + var orderEntity = await _context + .Orders.Include(o => o.OrderDetails) + .AsNoTracking() + .FirstOrDefaultAsync(o => o.Id == orderId); + + var orderEntityDto = _mapper.Map(orderEntity); + + return orderEntityDto; + } + + public async Task UpdateAsync(Order order, CancellationToken cancellationToken = default) + { + await _context + .Orders.Where(o => o.Id == order.Id) + .ExecuteUpdateAsync(oUpdate => + oUpdate.SetProperty(o => o.CustomerId, order.CustomerId) + ); + + return order.Id; + } + + public async Task AddDetailAsync( + OrderDetails detail, + CancellationToken cancellationToken = default + ) + { + var orderDetailEntity = new OrderDetailsEntity + { + Id = Guid.NewGuid(), + OrderId = detail.OrderId, + GoodId = detail.GoodId, + Quantity = detail.Quantity, + Price = detail.Price + }; + + await _context.OrderDetails.AddAsync(orderDetailEntity); + await _context.SaveChangesAsync(); + + var orderEntity = await GetByIdAsync(orderDetailEntity.OrderId, cancellationToken); + + await _context + .Orders.Where(o => o.Id == orderDetailEntity.OrderId) + .ExecuteUpdateAsync( + oUpdate => + oUpdate.SetProperty( + o => o.TotalPrice, + orderEntity.TotalPrice + + (orderDetailEntity.Price * orderDetailEntity.Quantity) + ), + cancellationToken + ); + + return orderDetailEntity.Id; + } +} diff --git a/Topers.DataAccess.Postgres/Repositories/UsersRepository.cs b/Topers.DataAccess.Postgres/Repositories/UsersRepository.cs new file mode 100644 index 0000000..2e8a3f3 --- /dev/null +++ b/Topers.DataAccess.Postgres/Repositories/UsersRepository.cs @@ -0,0 +1,43 @@ + +using AutoMapper; +using Microsoft.EntityFrameworkCore; +using Topers.Core.Abstractions; +using Topers.Core.Models; +using Topers.DataAccess.Postgres.Entities; + +namespace Topers.DataAccess.Postgres.Repositories; + +public class UsersRepository : IUsersRepository +{ + private readonly TopersDbContext _context; + private readonly IMapper _mapper; + + public UsersRepository(TopersDbContext context, IMapper mapper) + { + _context = context; + _mapper = mapper; + } + + public async Task Add(User user, CancellationToken cancellationToken = default) + { + var userEntity = new UserEntity() + { + Id = user.Id, + Username = user.Username, + PasswordHash = user.PasswordHash, + Email = user.Email + }; + + await _context.Users.AddAsync(userEntity); + await _context.SaveChangesAsync(); + } + + public async Task GetByName(string username, CancellationToken cancellationToken = default) + { + var userEntity = await _context.Users + .AsNoTracking() + .FirstOrDefaultAsync(u => u.Username == username) ?? throw new Exception(); + + return _mapper.Map(userEntity); + } +} \ No newline at end of file diff --git a/Topers.DataAccess.Postgres/Topers.DataAccess.Postgres.csproj b/Topers.DataAccess.Postgres/Topers.DataAccess.Postgres.csproj index bb23fb7..df4c70e 100644 --- a/Topers.DataAccess.Postgres/Topers.DataAccess.Postgres.csproj +++ b/Topers.DataAccess.Postgres/Topers.DataAccess.Postgres.csproj @@ -1,9 +1,28 @@ - - - - net8.0 - enable - enable - - - + + + + net8.0 + enable + enable + + + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + diff --git a/Topers.DataAccess.Postgres/TopersDbContext.cs b/Topers.DataAccess.Postgres/TopersDbContext.cs new file mode 100644 index 0000000..5f90cef --- /dev/null +++ b/Topers.DataAccess.Postgres/TopersDbContext.cs @@ -0,0 +1,32 @@ +namespace Topers.DataAccess.Postgres; + +using Microsoft.EntityFrameworkCore; +using Topers.DataAccess.Postgres.Configurations; +using Topers.DataAccess.Postgres.Entities; + +public class TopersDbContext(DbContextOptions options) : DbContext(options) +{ + public DbSet Users { get; set; } + public DbSet Categories { get; set; } + public DbSet Goods { get; set; } + public DbSet GoodScopes { get; set; } + public DbSet Addresses { get; set; } + public DbSet Customers { get; set; } + public DbSet Orders { get; set; } + public DbSet OrderDetails { get; set; } + public DbSet Cart { get; set; } + public DbSet CartDetails { get; set; } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.ApplyConfiguration(new CategoryConfiguration()); + modelBuilder.ApplyConfiguration(new GoodConfiguration()); + modelBuilder.ApplyConfiguration(new GoodScopeConfiguration()); + modelBuilder.ApplyConfiguration(new AddressConfiguration()); + modelBuilder.ApplyConfiguration(new CustomerConfiguration()); + modelBuilder.ApplyConfiguration(new OrderConfiguration()); + modelBuilder.ApplyConfiguration(new OrderDetailsConfiguration()); + + base.OnModelCreating(modelBuilder); + } +} diff --git a/Topers.Infrastructure/Features/CookiesOptions.cs b/Topers.Infrastructure/Features/CookiesOptions.cs new file mode 100644 index 0000000..7c0dd27 --- /dev/null +++ b/Topers.Infrastructure/Features/CookiesOptions.cs @@ -0,0 +1,6 @@ +namespace Topers.Infrastructure.Features; + +public class CookiesOptions +{ + public string Name { get; set; } = string.Empty; +} \ No newline at end of file diff --git a/Topers.Infrastructure/Features/FileService.cs b/Topers.Infrastructure/Features/FileService.cs new file mode 100644 index 0000000..ae6ec95 --- /dev/null +++ b/Topers.Infrastructure/Features/FileService.cs @@ -0,0 +1,64 @@ +namespace Topers.Infrastructure.Features; + +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Topers.Core.Abstractions; + +public class FileService : IFileService +{ + private readonly IWebHostEnvironment _environment; + + public FileService(IWebHostEnvironment environment) + { + _environment = environment; + } + + public void DeleteImage(string fullFileName) + { + var contentPath = _environment.ContentRootPath; + var path = Path.Combine(contentPath, $"Uploads", fullFileName); + + if (File.Exists(path)) + { + File.Delete(path); + } + } + + public Tuple SaveImage(IFormFile imageFile) + { + try + { + var contentPath = _environment.ContentRootPath; + + var path = Path.Combine(contentPath, "Uploads"); + + if (!Directory.Exists(path)) + { + Directory.CreateDirectory(path); + } + + var ext = Path.GetExtension(imageFile.FileName); + var allowedExtensions = new string[] { ".jpg", ".png", ".jpeg" }; + + if (!allowedExtensions.Contains(ext)) + { + string msg = string.Format("Only {0} extensions are allowed", string.Join(",", allowedExtensions)); + return new Tuple(0, msg); + } + + string uniqueString = Guid.NewGuid().ToString(); + + var newFileName = uniqueString + ext; + var fileWithPath = Path.Combine(path, newFileName); + var stream = new FileStream(fileWithPath, FileMode.Create); + imageFile.CopyTo(stream); + stream.Close(); + + return new Tuple(1, newFileName); + } + catch (Exception) + { + return new Tuple(0, "Error has occured"); + } + } +} \ No newline at end of file diff --git a/Topers.Infrastructure/Features/JwtOptions.cs b/Topers.Infrastructure/Features/JwtOptions.cs new file mode 100644 index 0000000..8d7acf9 --- /dev/null +++ b/Topers.Infrastructure/Features/JwtOptions.cs @@ -0,0 +1,7 @@ +namespace Topers.Infrastructure.Features; + +public class JwtOptions +{ + public string SecretKey { get; set; } = string.Empty; + public int ExpiresHours { get; set; } +} \ No newline at end of file diff --git a/Topers.Infrastructure/Features/JwtProvider.cs b/Topers.Infrastructure/Features/JwtProvider.cs new file mode 100644 index 0000000..b7aeb92 --- /dev/null +++ b/Topers.Infrastructure/Features/JwtProvider.cs @@ -0,0 +1,33 @@ +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Text; +using Microsoft.Extensions.Options; +using Microsoft.IdentityModel.Tokens; +using Topers.Core.Abstractions; +using Topers.Core.Models; + +namespace Topers.Infrastructure.Features; + +public class JwtProvider(IOptions options) : IJwtProvider +{ + private readonly JwtOptions _options = options.Value; + + public string GenerateToken(User user) + { + var claims = new List() + { + new("userId", user.Id.ToString()) + }; + + var signingCredentials = new SigningCredentials( + new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_options.SecretKey)), + SecurityAlgorithms.HmacSha256); + + var token = new JwtSecurityToken( + claims: claims, + signingCredentials: signingCredentials, + expires: DateTime.UtcNow.AddHours(_options.ExpiresHours)); + + return new JwtSecurityTokenHandler().WriteToken(token); + } +} \ No newline at end of file diff --git a/Topers.Infrastructure/Features/PasswordHasher.cs b/Topers.Infrastructure/Features/PasswordHasher.cs new file mode 100644 index 0000000..1fa4c4d --- /dev/null +++ b/Topers.Infrastructure/Features/PasswordHasher.cs @@ -0,0 +1,12 @@ +namespace Topers.Infrastructure.Features; + +using Topers.Core.Abstractions; + +public class PasswordHasher : IPasswordHasher +{ + public string Generate(string password) => + BCrypt.Net.BCrypt.EnhancedHashPassword(password); + + public bool Verify(string password, string hashedPassword) => + BCrypt.Net.BCrypt.EnhancedVerify(password, hashedPassword); +} \ No newline at end of file diff --git a/Topers.Infrastructure/Services/AddressesService.cs b/Topers.Infrastructure/Services/AddressesService.cs new file mode 100644 index 0000000..0859eac --- /dev/null +++ b/Topers.Infrastructure/Services/AddressesService.cs @@ -0,0 +1,33 @@ +using Topers.Core.Abstractions; +using Topers.Core.Dtos; +using Topers.Core.Models; + +namespace Topers.Infrastructure.Services; + +public class AddressesService : IAddressesService +{ + private readonly IAddressesRepository _addressesRepository; + + public AddressesService(IAddressesRepository addressesRepository) + { + _addressesRepository = addressesRepository; + } + + public async Task AddAddressToCustomerAsync(Address address, CancellationToken cancellationToken = default) + { + var addressEntityIdentifier = await _addressesRepository.CreateAsync(address); + + var newAddressEntity = new AddressResponseDto + ( + addressEntityIdentifier, + address.CustomerId, + address.Street, + address.City, + address.State, + address.PostalCode, + address.Country + ); + + return newAddressEntity; + } +} \ No newline at end of file diff --git a/Topers.Infrastructure/Services/CartsService.cs b/Topers.Infrastructure/Services/CartsService.cs new file mode 100644 index 0000000..4995435 --- /dev/null +++ b/Topers.Infrastructure/Services/CartsService.cs @@ -0,0 +1,84 @@ +using AutoMapper; +using Topers.Core.Abstractions; +using Topers.Core.Dtos; +using Topers.Core.Models; + +namespace Topers.Infrastructure.Services; + +public class CartsService : ICartsService +{ + private readonly ICartsRepository _cartsRepository; + private readonly IGoodsRepository _goodsRepository; + private readonly IMapper _mapper; + + public CartsService(ICartsRepository cartsRepository, IGoodsRepository goodsRepository, IMapper mapper) + { + _cartsRepository = cartsRepository; + _goodsRepository = goodsRepository; + _mapper = mapper; + } + + public async Task AddGoodToCartAsync( + CartItems cartDetails, + GoodScope good, + CancellationToken cancellationToken = default + ) + { + var goodScope = await _goodsRepository.GetScopeAsync(cartDetails.GoodId, good.Litre); + + var newCartItemsEntity = new CartItems( + Guid.Empty, + cartDetails.CartId, + goodScope.GoodId, + cartDetails.Quantity, + goodScope.Price + ); + + return await _cartsRepository.AddDetailAsync(newCartItemsEntity, cancellationToken); + } + + public async Task CreateCartAsync( + Cart cart, + CancellationToken cancellationToken = default + ) + { + var newCartIdentifier = await _cartsRepository.CreateAsync(cart, cancellationToken); + + var newCart = new CartResponseDto( + newCartIdentifier, + cart.CustomerId, + cart.CreatedDate, + cart.UpdatedDate + ); + + return newCart; + } + + public async Task DeleteCartAsync( + Guid cartId, + CancellationToken cancellationToken = default + ) + { + return await _cartsRepository.DeleteAsync(cartId); + } + + public async Task GetCartByCustomerId( + Guid customerId, + CancellationToken cancellationToken = default + ) + { + return _mapper.Map( + await _cartsRepository.GetByCustomerId(customerId, cancellationToken) + ); + } + + public async Task GetCartById( + Guid cartId, + CancellationToken cancellationToken = default + ) + { + return _mapper.Map( + await _cartsRepository.GetById(cartId, cancellationToken) + ); + } +} diff --git a/Topers.Infrastructure/Services/CategoriesService.cs b/Topers.Infrastructure/Services/CategoriesService.cs new file mode 100644 index 0000000..060b22a --- /dev/null +++ b/Topers.Infrastructure/Services/CategoriesService.cs @@ -0,0 +1,57 @@ +using AutoMapper; +using Topers.Core.Abstractions; +using Topers.Core.Dtos; +using Topers.Core.Models; + +namespace Topers.Infrastructure.Services; + +public class CategoriesService : ICategoriesService +{ + private readonly ICategoriesRepository _categoriesRepository; + private readonly IMapper _mapper; + + public CategoriesService(ICategoriesRepository categoriesRepository, IMapper mapper) + { + _categoriesRepository = categoriesRepository; + _mapper = mapper; + } + + public async Task CreateCategoryAsync(Category category, CancellationToken cancellationToken = default) + { + var newCategoryIdentifier = await _categoriesRepository.CreateAsync(category); + + var newCategory = new CategoryResponseDto + ( + newCategoryIdentifier, + category.Name, + category.Description + ); + + return newCategory; + } + + public async Task DeleteCategoryAsync(Guid categoryId, CancellationToken cancellationToken = default) + { + return await _categoriesRepository.DeleteAsync(categoryId); + } + + public async Task> GetAllCategoriesAsync(CancellationToken cancellationToken = default) + { + return _mapper.Map>(await _categoriesRepository.GetAllAsync()); + } + + public async Task GetCategoryByIdAsync(Guid categoryId, CancellationToken cancellationToken = default) + { + return _mapper.Map(await _categoriesRepository.GetByIdAsync(categoryId)); + } + + public async Task> GetGoodsByCategoryIdAsync(Guid categoryId, CancellationToken cancellationToken = default) + { + return _mapper.Map>(await _categoriesRepository.GetGoodsByIdAsync(categoryId)); + } + + public async Task UpdateCategoryAsync(Category category, CancellationToken cancellationToken = default) + { + return await _categoriesRepository.UpdateAsync(category); + } +} \ No newline at end of file diff --git a/Topers.Infrastructure/Services/CustomersService.cs b/Topers.Infrastructure/Services/CustomersService.cs new file mode 100644 index 0000000..c964b57 --- /dev/null +++ b/Topers.Infrastructure/Services/CustomersService.cs @@ -0,0 +1,43 @@ +using AutoMapper; +using Topers.Core.Abstractions; +using Topers.Core.Dtos; +using Topers.Core.Models; + +namespace Topers.Infrastructure.Services; + +public class CustomersService : ICustomersService +{ + private readonly ICustomersRepository _customersRepository; + private readonly IMapper _mapper; + + public CustomersService(ICustomersRepository customersRepository, IMapper mapper) + { + _customersRepository = customersRepository; + _mapper = mapper; + } + + public async Task CreateCustomerAsync(Customer customer, CancellationToken cancellationToken = default) + { + var newCustomerIdentifier = await _customersRepository.CreateAsync(customer); + + var newCustomer = new CustomerResponseDto + ( + newCustomerIdentifier, + customer.Name, + customer.Email, + customer.Phone + ); + + return newCustomer; + } + + public async Task> GetAllCustomersAsync(CancellationToken cancellationToken = default) + { + return _mapper.Map>(await _customersRepository.GetAllAsync()); + } + + public async Task GetCustomerByIdAsync(Guid customerId, CancellationToken cancellationToken = default) + { + return _mapper.Map(await _customersRepository.GetByIdAsync(customerId)); + } +} \ No newline at end of file diff --git a/Topers.Infrastructure/Services/GoodsService.cs b/Topers.Infrastructure/Services/GoodsService.cs new file mode 100644 index 0000000..1c79b8d --- /dev/null +++ b/Topers.Infrastructure/Services/GoodsService.cs @@ -0,0 +1,92 @@ +using AutoMapper; +using Topers.Core.Abstractions; +using Topers.Core.Dtos; +using Topers.Core.Models; + +namespace Topers.Infrastructure.Services; + +public class GoodsService : IGoodsService +{ + private readonly IGoodsRepository _goodsRepository; + private readonly IMapper _mapper; + + public GoodsService(IGoodsRepository goodsRepository, IMapper mapper) + { + _goodsRepository = goodsRepository; + _mapper = mapper; + } + + public async Task CreateGoodAsync( + Good good, + CancellationToken cancellationToken = default + ) + { + var newGoodIdentifier = await _goodsRepository.CreateAsync(good, cancellationToken); + + var newGood = new GoodResponseDto(newGoodIdentifier, good.Name, good.Description, null); + + return newGood; + } + + public async Task DeleteGoodAsync( + Guid goodId, + CancellationToken cancellationToken = default + ) + { + return await _goodsRepository.DeleteAsync(goodId, cancellationToken); + } + + public async Task> GetAllGoodsAsync( + CancellationToken cancellationToken = default + ) + { + return _mapper.Map>( + await _goodsRepository.GetAllAsync(cancellationToken) + ); + } + + public async Task GetGoodByIdAsync( + Guid goodId, + CancellationToken cancellationToken = default + ) + { + return _mapper.Map( + await _goodsRepository.GetByIdAsync(goodId, cancellationToken) + ); + } + + public async Task UpdateGoodAsync( + Good good, + CancellationToken cancellationToken = default + ) + { + return await _goodsRepository.UpdateAsync(good, cancellationToken); + } + + public async Task AddGoodScopeAsync( + GoodScope scope, + CancellationToken cancellationToken = default + ) + { + return await _goodsRepository.AddScopeAsync(scope, cancellationToken); + } + + public async Task UpdateGoodScopeAsync( + GoodScope scope, + CancellationToken cancellationToken = default + ) + { + return await _goodsRepository.UpdateScopeAsync(scope, cancellationToken); + } + + public async Task IsGoodScopeExistsAsync( + Guid goodId, + int litre, + CancellationToken cancellationToken = default + ) + { + var existingScope = await _goodsRepository.GetScopeAsync(goodId, litre, cancellationToken); + + return existingScope != null; + } +} diff --git a/Topers.Infrastructure/Services/OrdersService.cs b/Topers.Infrastructure/Services/OrdersService.cs new file mode 100644 index 0000000..783858d --- /dev/null +++ b/Topers.Infrastructure/Services/OrdersService.cs @@ -0,0 +1,93 @@ +using AutoMapper; +using Topers.Core.Abstractions; +using Topers.Core.Dtos; +using Topers.Core.Models; + +namespace Topers.Infrastructure.Services; + +public class OrdersService : IOrdersService +{ + private readonly IOrdersRepository _ordersRepository; + private readonly IGoodsRepository _goodsRepository; + private readonly IMapper _mapper; + + public OrdersService( + IOrdersRepository ordersRepository, + IGoodsRepository goodsRepository, + IMapper mapper + ) + { + _ordersRepository = ordersRepository; + _goodsRepository = goodsRepository; + _mapper = mapper; + } + + public async Task CreateOrderAsync( + Order order, + CancellationToken cancellationToken = default + ) + { + var newOrderIdentifier = await _ordersRepository.CreateAsync(order, cancellationToken); + + var newOrder = new OrderResponseDto( + newOrderIdentifier, + order.Date, + order.CustomerId, + order.TotalPrice + ); + + return newOrder; + } + + public async Task DeleteOrderAsync( + Guid orderId, + CancellationToken cancellationToken = default + ) + { + return await _ordersRepository.DeleteAsync(orderId, cancellationToken); + } + + public async Task> GetAllOrdersAsync( + CancellationToken cancellationToken = default + ) + { + return _mapper.Map>(await _ordersRepository.GetAllAsync()); + } + + public async Task GetOrderByIdAsync( + Guid orderId, + CancellationToken cancellationToken = default + ) + { + return _mapper.Map( + await _ordersRepository.GetByIdAsync(orderId, cancellationToken) + ); + } + + public async Task UpdateOrderAsync( + Order order, + CancellationToken cancellationToken = default + ) + { + return await _ordersRepository.UpdateAsync(order); + } + + public async Task AddGoodToOrderAsync( + OrderDetails detail, + GoodScope good, + CancellationToken cancellationToken = default + ) + { + var goodScope = await _goodsRepository.GetScopeAsync(detail.GoodId, good.Litre); + + var newGoodDetailEntity = new OrderDetails( + Guid.Empty, + detail.OrderId, + goodScope.Id, + detail.Quantity, + goodScope.Price + ); + + return await _ordersRepository.AddDetailAsync(newGoodDetailEntity, cancellationToken); + } +} diff --git a/Topers.Infrastructure/Services/UsersService.cs b/Topers.Infrastructure/Services/UsersService.cs new file mode 100644 index 0000000..4a5a483 --- /dev/null +++ b/Topers.Infrastructure/Services/UsersService.cs @@ -0,0 +1,47 @@ +using Topers.Core.Abstractions; +using Topers.Core.Dtos; +using Topers.Core.Models; + +namespace Topers.Infrastructure.Services; + +public class UsersService : IUsersService +{ + private readonly IUsersRepository _usersRepository; + private readonly IPasswordHasher _passwordHasher; + private readonly IJwtProvider _jwtProvider; + + public UsersService( + IUsersRepository usersRepository, + IPasswordHasher passwordHasher, + IJwtProvider jwtProvider) + { + _usersRepository = usersRepository; + _passwordHasher = passwordHasher; + _jwtProvider = jwtProvider; + } + + public async Task Register(string username, string email, string password, CancellationToken cancellationToken = default) + { + var hashedPassword = _passwordHasher.Generate(password); + + var newUser = User.Create(Guid.NewGuid(), username, hashedPassword, email); + + await _usersRepository.Add(newUser); + } + + public async Task Login(string username, string password, CancellationToken cancellationToken = default) + { + var user = await _usersRepository.GetByName(username); + + var result = _passwordHasher.Verify(password, user.PasswordHash); + + if (!result) + { + throw new Exception("Failed to log in!"); + } + + var token = _jwtProvider.GenerateToken(user); + + return token; + } +} \ No newline at end of file diff --git a/Topers.Infrastructure/Topers.Infrastructure.csproj b/Topers.Infrastructure/Topers.Infrastructure.csproj index bb23fb7..d0e0168 100644 --- a/Topers.Infrastructure/Topers.Infrastructure.csproj +++ b/Topers.Infrastructure/Topers.Infrastructure.csproj @@ -6,4 +6,13 @@ enable + + + + + + + + +