From 181b0bb4d1c3125ee4fa070eaa8846a1ebb94642 Mon Sep 17 00:00:00 2001 From: Frederik Petersen <43568735+fredpetersen@users.noreply.github.com> Date: Tue, 22 Oct 2024 18:25:57 +0200 Subject: [PATCH] Add purchase datagrid and fetch purchases from db (#44) * Add purchase datagrid and fetch purchases from db * Revert appsettings * Oops i reverted the wrong appsettings * PR comments * Update apihost * Remove cosole writeline --- Shifty.App/Components/ProductManager.razor | 199 +++++++++--------- Shifty.App/Components/Refunds.razor | 103 +++++++++ Shifty.App/DomainModels/Purchase.cs | 33 +++ Shifty.App/Pages/Refund.razor | 24 +++ Shifty.App/Program.cs | 2 + .../Repositories/IPurchaseRepository.cs | 14 ++ Shifty.App/Repositories/PurchaseRepository.cs | 33 +++ Shifty.App/Services/IPurchaseService.cs | 15 ++ Shifty.App/Services/PurchaseService.cs | 32 +++ Shifty.App/Shared/NavMenu.razor | 1 + .../OpenApiSpecs/AnalogCoreV2.json | 109 +++++++++- 11 files changed, 464 insertions(+), 101 deletions(-) create mode 100644 Shifty.App/Components/Refunds.razor create mode 100644 Shifty.App/DomainModels/Purchase.cs create mode 100644 Shifty.App/Pages/Refund.razor create mode 100644 Shifty.App/Repositories/IPurchaseRepository.cs create mode 100644 Shifty.App/Repositories/PurchaseRepository.cs create mode 100644 Shifty.App/Services/IPurchaseService.cs create mode 100644 Shifty.App/Services/PurchaseService.cs diff --git a/Shifty.App/Components/ProductManager.razor b/Shifty.App/Components/ProductManager.razor index 01a538e..578dd74 100644 --- a/Shifty.App/Components/ProductManager.razor +++ b/Shifty.App/Components/ProductManager.razor @@ -26,109 +26,112 @@ } - - - - - - - - - - @{ - if (context.Item.Visible) - { - - } - else - { - - } - } - - - - - - - - - - - - Visible to: - @foreach (var group in Enum.GetValues()) - { - - @group - } - - - - - @{ - if (context.Item.IsPerk) - { - + else + { + + + + + + + + + + @{ + if (context.Item.Visible) + { + + } + else + { + + } } - else + + + + + + + + + + + + Visible to: + @foreach (var group in Enum.GetValues()) { - + + @group } - } - - - - + + + @{ + if (context.Item.IsPerk) + { + + } + else + { + + } } - - - - - + + + + + + - - - - Add Product - - + + + + Add Product + + + } @code { diff --git a/Shifty.App/Components/Refunds.razor b/Shifty.App/Components/Refunds.razor new file mode 100644 index 0000000..0c8ceaa --- /dev/null +++ b/Shifty.App/Components/Refunds.razor @@ -0,0 +1,103 @@ +@namespace Components +@using System.ComponentModel.DataAnnotations +@using MudExtensions +@using Shifty.App.Services +@using Shifty.Api.Generated.AnalogCoreV1 +@using Shifty.Api.Generated.AnalogCoreV2 +@using Shared +@using LanguageExt.UnsafeValueAccess +@using Components +@using Shifty.App.DomainModels +@using System.Collections.ObjectModel +@inject IPurchaseService PurchaseService +@inject ISnackbar Snackbar + + + @if(_purchasesFetched) + { + + + + + + + + + + + @{ + if(@context.Item.PurchaseStatus == PurchaseStatus.Completed) + { + + Refund + + } + else + { + @context.Item.PurchaseStatus + } + } + + + + + } + else + { + + Find purchases for user + + + } + + +@code { + private int UserId; + private bool _loading = true; + private bool _purchasesFetched = false; + private List _purchases; + + private async Task GetPurchases() + { + var result = await PurchaseService.GetPurchases(UserId); + result.Match( + purchases => + { + _purchases = purchases.OrderByDescending(x => x.DateCreated).ToList(); + _purchasesFetched = true; + }, + error => Snackbar.Add(error.Message, Severity.Error) + ); + _loading = false; + } + + async void OnKeyDown(KeyboardEventArgs args) + { + if (args.Key=="Enter") + { + _loading = true; + await GetPurchases(); + StateHasChanged(); + } + + } + + private async Task RefundPurchase(Purchase purchase) + { + var result = await PurchaseService.RefundPurchase(purchase.Id); + result.Match( + _ => Snackbar.Add("Purchase refunded", Severity.Success), + error => Snackbar.Add(error.Message, Severity.Error) + ); + _loading = true; + await GetPurchases(); + } +} \ No newline at end of file diff --git a/Shifty.App/DomainModels/Purchase.cs b/Shifty.App/DomainModels/Purchase.cs new file mode 100644 index 0000000..383b390 --- /dev/null +++ b/Shifty.App/DomainModels/Purchase.cs @@ -0,0 +1,33 @@ +using System; +using Microsoft.VisualBasic; +using Shifty.Api.Generated.AnalogCoreV1; +using Shifty.Api.Generated.AnalogCoreV2; + +namespace Shifty.App.DomainModels +{ + public record Purchase { + public int Id { get; init; } + public int NumberOfTickets { get; set; } + public int Price { get; set; } + public string ProductName { get; set; } + public int ProductId { get; set; } + public PurchaseStatus PurchaseStatus { get; set; } + + public DateTimeOffset DateCreated { get; set; } + + public static Purchase FromDto(SimplePurchaseResponse dto) + { + return new Purchase() + { + Id = dto.Id, + DateCreated = dto.DateCreated, + NumberOfTickets = dto.NumberOfTickets, + Price = dto.TotalAmount, + PurchaseStatus = dto.PurchaseStatus, + ProductName = dto.ProductName, + ProductId = dto.ProductId + }; + } + + } +} \ No newline at end of file diff --git a/Shifty.App/Pages/Refund.razor b/Shifty.App/Pages/Refund.razor new file mode 100644 index 0000000..4632dff --- /dev/null +++ b/Shifty.App/Pages/Refund.razor @@ -0,0 +1,24 @@ +@page "/Refunds" +@using Components +@inject NavigationManager NavManager + +@if (_user is not null && _user.IsInRole("Board")) +{ + +} + +@code { + [CascadingParameter] public Task AuthTask { get; set; } + private System.Security.Claims.ClaimsPrincipal _user; + + protected override async Task OnInitializedAsync() + { + var authState = await AuthTask; + _user = authState.User; + + if (_user is null || !_user.IsInRole("Board")) + { + NavManager.NavigateTo("/"); + } + } +} \ No newline at end of file diff --git a/Shifty.App/Program.cs b/Shifty.App/Program.cs index 9798e67..8f2f5a9 100644 --- a/Shifty.App/Program.cs +++ b/Shifty.App/Program.cs @@ -57,6 +57,7 @@ public static void ConfigureServices(IServiceCollection services, IConfiguration services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); @@ -64,6 +65,7 @@ public static void ConfigureServices(IServiceCollection services, IConfiguration services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); diff --git a/Shifty.App/Repositories/IPurchaseRepository.cs b/Shifty.App/Repositories/IPurchaseRepository.cs new file mode 100644 index 0000000..ce61cc9 --- /dev/null +++ b/Shifty.App/Repositories/IPurchaseRepository.cs @@ -0,0 +1,14 @@ +using System.Threading.Tasks; +using LanguageExt; +using LanguageExt.Common; +using Shifty.Api.Generated.AnalogCoreV1; +using Shifty.Api.Generated.AnalogCoreV2; + +namespace Shifty.App.Repositories +{ + public interface IPurchaseRepository + { + public Task>> GetPurchases(int userId); + public Task> RefundPurchase(int purchaseId); + } +} \ No newline at end of file diff --git a/Shifty.App/Repositories/PurchaseRepository.cs b/Shifty.App/Repositories/PurchaseRepository.cs new file mode 100644 index 0000000..f2c2d0e --- /dev/null +++ b/Shifty.App/Repositories/PurchaseRepository.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using LanguageExt; +using LanguageExt.Common; +using Shifty.Api.Generated.AnalogCoreV1; +using Shifty.Api.Generated.AnalogCoreV2; +using Shifty.App.Services; +using static LanguageExt.Prelude; + +namespace Shifty.App.Repositories +{ + public class PurchaseRepository : IPurchaseRepository + { + private readonly AnalogCoreV2 _client; + + public PurchaseRepository(AnalogCoreV2 client) + { + _client = client; + } + + async Task>> IPurchaseRepository.GetPurchases(int userId) + { + return await TryAsync(async () => (await _client.ApiV2PurchasesUserAsync(userId)).AsEnumerable()); + } + + async Task> IPurchaseRepository.RefundPurchase(int purchaseId) + { + return await TryAsync(async () => await _client.ApiV2PurchasesRefundAsync(purchaseId)); + } + } +} \ No newline at end of file diff --git a/Shifty.App/Services/IPurchaseService.cs b/Shifty.App/Services/IPurchaseService.cs new file mode 100644 index 0000000..7a91678 --- /dev/null +++ b/Shifty.App/Services/IPurchaseService.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using LanguageExt; +using LanguageExt.Common; +using Shifty.Api.Generated.AnalogCoreV2; +using Shifty.App.DomainModels; + +namespace Shifty.App.Services +{ + public interface IPurchaseService + { + Task>> GetPurchases(int userId); + Task> RefundPurchase(int purchaseId); + } +} \ No newline at end of file diff --git a/Shifty.App/Services/PurchaseService.cs b/Shifty.App/Services/PurchaseService.cs new file mode 100644 index 0000000..f49466c --- /dev/null +++ b/Shifty.App/Services/PurchaseService.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using LanguageExt; +using Shifty.App.DomainModels; +using Shifty.App.Repositories; + +namespace Shifty.App.Services +{ + public class PurchaseService : IPurchaseService + { + private readonly IPurchaseRepository _purchaseRepository; + + public PurchaseService(IPurchaseRepository purchaseRepository) + { + _purchaseRepository = purchaseRepository; + } + + public async Task>> GetPurchases(int userId) + { + return await _purchaseRepository + .GetPurchases(userId) + .Map(x => x.Map(p => Purchase.FromDto(p))); + } + + public async Task> RefundPurchase(int purchaseId) + { + return await _purchaseRepository + .RefundPurchase(purchaseId) + .Map(x => x.Map(p => Purchase.FromDto(p))); + } + } +} \ No newline at end of file diff --git a/Shifty.App/Shared/NavMenu.razor b/Shifty.App/Shared/NavMenu.razor index 628842d..3d0457d 100644 --- a/Shifty.App/Shared/NavMenu.razor +++ b/Shifty.App/Shared/NavMenu.razor @@ -12,6 +12,7 @@ Product Management Menu Item Management Manage users + Issue refunds Statistics } diff --git a/Shifty.Generated.ApiClient/OpenApiSpecs/AnalogCoreV2.json b/Shifty.Generated.ApiClient/OpenApiSpecs/AnalogCoreV2.json index 53463f8..f8ba204 100644 --- a/Shifty.Generated.ApiClient/OpenApiSpecs/AnalogCoreV2.json +++ b/Shifty.Generated.ApiClient/OpenApiSpecs/AnalogCoreV2.json @@ -386,9 +386,7 @@ }, "/api/v2/statistics/unused-clips": { "post": { - "tags": [ - "AdminStatistics" - ], + "tags": ["AdminStatistics"], "summary": "Sum unused clip cards within a given period per productId", "operationId": "AdminStatistics_GetUnusedClips", "requestBody": { @@ -1079,6 +1077,65 @@ ] } }, + "/api/v2/purchases/user/{userId}": { + "get": { + "tags": ["Purchases"], + "summary": "Get all purchases for a user", + "operationId": "Purchases_GetAllPurchasesForUser", + "parameters": [ + { + "name": "userId", + "in": "path", + "required": true, + "description": "User Id", + "schema": { + "type": "integer", + "format": "int32" + }, + "x-position": 1 + } + ], + "responses": { + "200": { + "description": "Purchases", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SimplePurchaseResponse" + } + } + } + } + }, + "401": { + "description": "Invalid credentials" + }, + "403": { + "description": "User not allowed to view purchases for given user" + }, + "404": { + "description": "User with userId not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiError" + } + } + } + } + }, + "security": [ + { + "jwt": [] + }, + { + "apikey": [] + } + ] + } + }, "/api/v2/purchases/{id}": { "get": { "tags": ["Purchases"], @@ -1132,6 +1189,52 @@ ] } }, + "/api/v2/purchases/{id}/refund": { + "put": { + "tags": ["Purchases"], + "summary": "Refunds a payment", + "operationId": "Purchases_RefundPurchase", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "description": "database id of purchase", + "schema": { + "type": "integer", + "format": "int32" + }, + "x-position": 1 + } + ], + "responses": { + "200": { + "description": "Purchase after being refunded", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SimplePurchaseResponse" + } + } + } + }, + "401": { + "description": "" + }, + "403": { + "description": "" + } + }, + "security": [ + { + "jwt": [] + }, + { + "apikey": [] + } + ] + } + }, "/api/v2/tickets": { "get": { "tags": ["Tickets"],