Updated delete logic for voice works.
This commit is contained in:
@@ -53,6 +53,23 @@ public static class WebApplicationExtensions
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
app.UseExceptionHandler(errorApp =>
|
||||||
|
{
|
||||||
|
errorApp.Run(async context =>
|
||||||
|
{
|
||||||
|
context.Response.StatusCode = StatusCodes.Status500InternalServerError;
|
||||||
|
context.Response.ContentType = "application/problem+json";
|
||||||
|
|
||||||
|
await Results.Problem(
|
||||||
|
title: "An unexpected error occurred.",
|
||||||
|
detail: app.Environment.IsDevelopment()
|
||||||
|
? "Check the API logs for details."
|
||||||
|
: null,
|
||||||
|
statusCode: StatusCodes.Status500InternalServerError
|
||||||
|
).ExecuteAsync(context);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
return app;
|
return app;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
namespace JSMR.Application.VoiceWorks.Commands.Delete;
|
namespace JSMR.Application.VoiceWorks.Commands.Delete;
|
||||||
|
|
||||||
public sealed record DeleteVoiceWorkResponse(Dictionary<int, bool> IsSuccess);
|
public sealed record DeleteVoiceWorkResponse(Dictionary<int, DeleteVoiceWorkStatus> Results);
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
namespace JSMR.Application.VoiceWorks.Commands.Delete;
|
||||||
|
|
||||||
|
public enum DeleteVoiceWorkStatus
|
||||||
|
{
|
||||||
|
Deleted,
|
||||||
|
NotFound,
|
||||||
|
NotAllowed,
|
||||||
|
Failed
|
||||||
|
}
|
||||||
@@ -20,28 +20,39 @@ public class VoiceWorkWriter(AppDbContext dbContext) : IVoiceWorkWriter
|
|||||||
|
|
||||||
public async Task<DeleteVoiceWorkResponse> DeleteAsync(DeleteVoiceWorkRequest request, CancellationToken cancellationToken)
|
public async Task<DeleteVoiceWorkResponse> DeleteAsync(DeleteVoiceWorkRequest request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
Dictionary<int, bool> isSuccess = [];
|
Dictionary<int, DeleteVoiceWorkStatus> results = request.VoiceWorkIds.Select(x => x)
|
||||||
|
.ToDictionary(x => x, x => DeleteVoiceWorkStatus.NotFound);
|
||||||
|
|
||||||
VoiceWork[] voiceWorks = [.. dbContext.VoiceWorks.Where(voiceWork => request.VoiceWorkIds.Contains(voiceWork.VoiceWorkId))
|
VoiceWork[] voiceWorks = [.. dbContext.VoiceWorks.Where(voiceWork => request.VoiceWorkIds.Contains(voiceWork.VoiceWorkId))
|
||||||
.Include(x => x.Circle)];
|
.Include(x => x.Circle)];
|
||||||
|
|
||||||
foreach (VoiceWork voiceWork in voiceWorks)
|
foreach (VoiceWork voiceWork in voiceWorks)
|
||||||
{
|
{
|
||||||
isSuccess.Add(voiceWork.VoiceWorkId, false);
|
if (results.ContainsKey(voiceWork.VoiceWorkId) == false)
|
||||||
|
{
|
||||||
if (voiceWork.Circle is null)
|
results[voiceWork.VoiceWorkId] = DeleteVoiceWorkStatus.NotFound;
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (voiceWork.IsValid == true && voiceWork.Circle.Spam == false)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
dbContext.Remove(voiceWork);
|
|
||||||
isSuccess[voiceWork.VoiceWorkId] = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dbContext.SaveChanges();
|
if (voiceWork.Circle is null)
|
||||||
|
{
|
||||||
|
results[voiceWork.VoiceWorkId] = DeleteVoiceWorkStatus.NotFound;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
return new DeleteVoiceWorkResponse(isSuccess);
|
if (voiceWork.IsValid == true && voiceWork.Circle.Spam == false)
|
||||||
|
{
|
||||||
|
results[voiceWork.VoiceWorkId] = DeleteVoiceWorkStatus.NotAllowed;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
dbContext.Remove(voiceWork);
|
||||||
|
results[voiceWork.VoiceWorkId] = DeleteVoiceWorkStatus.Deleted;
|
||||||
|
}
|
||||||
|
|
||||||
|
await dbContext.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
|
return new DeleteVoiceWorkResponse(results);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<VoiceWork> GetVoiceWorkAsync(int voiceWorkId, CancellationToken cancellationToken)
|
private async Task<VoiceWork> GetVoiceWorkAsync(int voiceWorkId, CancellationToken cancellationToken)
|
||||||
|
|||||||
@@ -18,9 +18,9 @@ public class Delete_Voice_Work_Tests(MariaDbContainerFixture container) : VoiceW
|
|||||||
DeleteVoiceWorkRequest request = new([voiceWorkId]);
|
DeleteVoiceWorkRequest request = new([voiceWorkId]);
|
||||||
|
|
||||||
DeleteVoiceWorkResponse response = await writer.DeleteAsync(request, TestContext.Current.CancellationToken);
|
DeleteVoiceWorkResponse response = await writer.DeleteAsync(request, TestContext.Current.CancellationToken);
|
||||||
response.IsSuccess.Count.ShouldBe(1);
|
response.Results.Count.ShouldBe(1);
|
||||||
response.IsSuccess.ShouldContainKey(voiceWorkId);
|
response.Results.ShouldContainKey(voiceWorkId);
|
||||||
response.IsSuccess[voiceWorkId].ShouldBeTrue();
|
response.Results[voiceWorkId].ShouldBe(DeleteVoiceWorkStatus.Deleted);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -33,8 +33,8 @@ public class Delete_Voice_Work_Tests(MariaDbContainerFixture container) : VoiceW
|
|||||||
DeleteVoiceWorkRequest request = new([voiceWorkId]);
|
DeleteVoiceWorkRequest request = new([voiceWorkId]);
|
||||||
|
|
||||||
DeleteVoiceWorkResponse response = await writer.DeleteAsync(request, TestContext.Current.CancellationToken);
|
DeleteVoiceWorkResponse response = await writer.DeleteAsync(request, TestContext.Current.CancellationToken);
|
||||||
response.IsSuccess.Count.ShouldBe(1);
|
response.Results.Count.ShouldBe(1);
|
||||||
response.IsSuccess.ShouldContainKey(voiceWorkId);
|
response.Results.ShouldContainKey(voiceWorkId);
|
||||||
response.IsSuccess[voiceWorkId].ShouldBeFalse();
|
response.Results[voiceWorkId].ShouldBe(DeleteVoiceWorkStatus.NotAllowed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
@using AntDesign
|
@using AntDesign
|
||||||
|
@using JSMR.Application.VoiceWorks.Commands.Delete
|
||||||
@using JSMR.Application.VoiceWorks.Commands.SetFavorite
|
@using JSMR.Application.VoiceWorks.Commands.SetFavorite
|
||||||
@using JSMR.Application.VoiceWorks.Queries.Search
|
@using JSMR.Application.VoiceWorks.Queries.Search
|
||||||
@using JSMR.Domain.Enums
|
@using JSMR.Domain.Enums
|
||||||
@@ -136,6 +137,9 @@
|
|||||||
[Parameter]
|
[Parameter]
|
||||||
public required VoiceWorkSearchResult Product { get; set; }
|
public required VoiceWorkSearchResult Product { get; set; }
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public EventCallback ProductDeleted { get; set; }
|
||||||
|
|
||||||
private string GetCardClasses(VoiceWorkSearchResult voiceWork)
|
private string GetCardClasses(VoiceWorkSearchResult voiceWork)
|
||||||
{
|
{
|
||||||
List<string> classNames = ["j-card", "j-voice-work-card"];
|
List<string> classNames = ["j-card", "j-voice-work-card"];
|
||||||
@@ -240,7 +244,38 @@
|
|||||||
{
|
{
|
||||||
Title = "Are you sure you want to delete the following product?",
|
Title = "Are you sure you want to delete the following product?",
|
||||||
Icon = icon,
|
Icon = icon,
|
||||||
Content = Product.ProductName
|
Content = Product.ProductName,
|
||||||
|
Centered = true,
|
||||||
|
OnOk = async (e) =>
|
||||||
|
{
|
||||||
|
DeleteVoiceWorkRequest request = new(
|
||||||
|
VoiceWorkIds: [Product.VoiceWorkId]
|
||||||
|
);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
DeleteVoiceWorkResponse? response = await Client.DeleteVoiceWorkAsync(request);
|
||||||
|
|
||||||
|
if (response is null || response.Results[Product.VoiceWorkId] != DeleteVoiceWorkStatus.Deleted)
|
||||||
|
return;
|
||||||
|
|
||||||
|
await ProductDeleted.InvokeAsync();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
AntDesign.ConfirmOptions errorOptions = new()
|
||||||
|
{
|
||||||
|
Title = "Unable to delete product",
|
||||||
|
Content = "Something went wrong while deleting this product. The product was not deleted. Check the API logs for details.",
|
||||||
|
Centered = true,
|
||||||
|
//Width = "70vw",
|
||||||
|
};
|
||||||
|
|
||||||
|
await ModalService.ErrorAsync(errorOptions);
|
||||||
|
|
||||||
|
e.Cancel = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
await ModalService.ConfirmAsync(options);
|
await ModalService.ConfirmAsync(options);
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ else
|
|||||||
<div class="j-product-items-container">
|
<div class="j-product-items-container">
|
||||||
@foreach (var product in Products)
|
@foreach (var product in Products)
|
||||||
{
|
{
|
||||||
<JProduct Product="@product"></JProduct>
|
<JProduct Product="@product" ProductDeleted="OnProductDeleted"></JProduct>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@@ -21,4 +21,12 @@ else
|
|||||||
@code {
|
@code {
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public VoiceWorkSearchResult[]? Products { get; set; }
|
public VoiceWorkSearchResult[]? Products { get; set; }
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public EventCallback ProductDeleted { get; set; }
|
||||||
|
|
||||||
|
private async Task OnProductDeleted()
|
||||||
|
{
|
||||||
|
await ProductDeleted.InvokeAsync();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
9
JSMR.UI.Blazor/Exceptions/ApiException.cs
Normal file
9
JSMR.UI.Blazor/Exceptions/ApiException.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
using System.Net;
|
||||||
|
|
||||||
|
namespace JSMR.UI.Blazor.Exceptions;
|
||||||
|
|
||||||
|
public sealed class ApiException(HttpStatusCode statusCode, string message, string? responseBody = null) : Exception(message)
|
||||||
|
{
|
||||||
|
public HttpStatusCode StatusCode { get; } = statusCode;
|
||||||
|
public string? ResponseBody { get; } = responseBody;
|
||||||
|
}
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
<JProductCollection Products="upcomingVoiceWorks"></JProductCollection>
|
<JProductCollection Products="upcomingVoiceWorks"></JProductCollection>
|
||||||
</BitPivotItem>
|
</BitPivotItem>
|
||||||
<BitPivotItem HeaderText="@($"Announcements ({announcedVoiceWorks?.Length ?? 0})")">
|
<BitPivotItem HeaderText="@($"Announcements ({announcedVoiceWorks?.Length ?? 0})")">
|
||||||
<JProductCollection Products="announcedVoiceWorks"></JProductCollection>
|
<JProductCollection Products="announcedVoiceWorks" ProductDeleted="OnAnnouncedProductDeleted"></JProductCollection>
|
||||||
</BitPivotItem>
|
</BitPivotItem>
|
||||||
</BitPivot>
|
</BitPivot>
|
||||||
|
|
||||||
@@ -105,4 +105,9 @@
|
|||||||
|
|
||||||
await InvokeAsync(StateHasChanged);
|
await InvokeAsync(StateHasChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task OnAnnouncedProductDeleted()
|
||||||
|
{
|
||||||
|
_ = LoadAnnouncedVoiceWorksAsync();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -7,6 +7,7 @@ using JSMR.Application.Tags.Queries.Search;
|
|||||||
using JSMR.Application.VoiceWorks.Commands.Delete;
|
using JSMR.Application.VoiceWorks.Commands.Delete;
|
||||||
using JSMR.Application.VoiceWorks.Commands.SetFavorite;
|
using JSMR.Application.VoiceWorks.Commands.SetFavorite;
|
||||||
using JSMR.Application.VoiceWorks.Queries.Search;
|
using JSMR.Application.VoiceWorks.Queries.Search;
|
||||||
|
using JSMR.UI.Blazor.Exceptions;
|
||||||
using System.Net.Http.Json;
|
using System.Net.Http.Json;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
@@ -23,6 +24,18 @@ public class VoiceWorksClient(HttpClient http)
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private static async Task<T?> ReadJsonOrThrowAsync<T>(HttpResponseMessage response, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (!response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
string body = await response.Content.ReadAsStringAsync(cancellationToken);
|
||||||
|
|
||||||
|
throw new ApiException(response.StatusCode, $"Request failed: {(int)response.StatusCode}", body);
|
||||||
|
}
|
||||||
|
|
||||||
|
return await response.Content.ReadFromJsonAsync<T>(JsonOptions, cancellationToken: cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<SearchVoiceWorksResponse?> SearchAsync(SearchVoiceWorksRequest request, CancellationToken ct = default)
|
public async Task<SearchVoiceWorksResponse?> SearchAsync(SearchVoiceWorksRequest request, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
using var resp = await http.PostAsJsonAsync("/api/voiceworks/search", request, ct);
|
using var resp = await http.PostAsJsonAsync("/api/voiceworks/search", request, ct);
|
||||||
@@ -56,7 +69,8 @@ public class VoiceWorksClient(HttpClient http)
|
|||||||
public async Task<DeleteVoiceWorkResponse?> DeleteVoiceWorkAsync(DeleteVoiceWorkRequest request, CancellationToken ct = default)
|
public async Task<DeleteVoiceWorkResponse?> DeleteVoiceWorkAsync(DeleteVoiceWorkRequest request, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
using var resp = await http.PostAsJsonAsync("/api/voicework/delete", request, ct);
|
using var resp = await http.PostAsJsonAsync("/api/voicework/delete", request, ct);
|
||||||
return await resp.Content.ReadFromJsonAsync<DeleteVoiceWorkResponse>(JsonOptions, cancellationToken: ct);
|
//return await resp.Content.ReadFromJsonAsync<DeleteVoiceWorkResponse>(JsonOptions, cancellationToken: ct);
|
||||||
|
return await ReadJsonOrThrowAsync<DeleteVoiceWorkResponse>(resp, ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<UpdateTagStatusResponse?> UpdateTagStatusAsync(UpdateTagStatusRequest request, CancellationToken ct = default)
|
public async Task<UpdateTagStatusResponse?> UpdateTagStatusAsync(UpdateTagStatusRequest request, CancellationToken ct = default)
|
||||||
|
|||||||
Reference in New Issue
Block a user