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
}
- }
-
-
-
-
-
-
- @foreach (var mi in _allMenuItems)
- {
- @mi.Name
+
+
+
+
+ @{
+ if (context.Item.IsPerk)
+ {
+
+ }
+ else
+ {
+
+ }
}
-
-
-
-
-
+
+
+
+
+
+
+ @foreach (var mi in _allMenuItems)
+ {
+ @mi.Name
+ }
+
+
+
+
+
-
-
-
- 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"],