Enhance metadata fetching and UI improvements
Build and Push Docker Image / build-and-push (push) Successful in 1m48s
Build and Push Docker Image / build-and-push (push) Successful in 1m48s
- Added `MetadataFetchSucceeded` and `MetadataFetchFailed` enums. - Improved metadata fetch logging with detailed outcomes. - Enhanced toolbar formatting options in `RegistryAdmin`. - Made `LanguageOptions` dynamic based on the environment. - Styled Quill text-size classes in public header content. - Improved error handling in `RegistryMetadataService`. - Adjusted supported cultures for development environments. - Updated localization for new metadata fetch statuses.
This commit is contained in:
@@ -22,5 +22,7 @@ public enum UserActionType
|
||||
MarkPurchased = 3,
|
||||
UnmarkPurchased = 4,
|
||||
MarkPartialPurchase = 5,
|
||||
LogContribution = 6
|
||||
LogContribution = 6,
|
||||
MetadataFetchSucceeded = 7,
|
||||
MetadataFetchFailed = 8
|
||||
}
|
||||
|
||||
@@ -2,12 +2,14 @@
|
||||
@using BirthList.Web.Features.Localization
|
||||
@using BirthList.Web.Features.Registries
|
||||
@using BirthList.Web.Services
|
||||
@using Microsoft.Extensions.Hosting
|
||||
@implements IDisposable
|
||||
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject AuthenticationStateProvider AuthenticationStateProvider
|
||||
@inject RegistryUserContext RegistryUserContext
|
||||
@inject ProfileCompletionService ProfileCompletionService
|
||||
@inject IHostEnvironment HostEnvironment
|
||||
|
||||
@if (ShowProfileCompletionPrompt)
|
||||
{
|
||||
@@ -70,12 +72,21 @@
|
||||
private bool ShowProfileCompletionPrompt { get; set; }
|
||||
private string CurrentCulture { get; set; } = "en";
|
||||
|
||||
private static readonly IReadOnlyList<LanguageOption> LanguageOptions =
|
||||
private IReadOnlyList<LanguageOption> LanguageOptions => HostEnvironment.IsDevelopment()
|
||||
? DevelopmentLanguageOptions
|
||||
: ProductionLanguageOptions;
|
||||
|
||||
private static readonly IReadOnlyList<LanguageOption> ProductionLanguageOptions =
|
||||
[
|
||||
new("en", "English"),
|
||||
new("nl-NL", "Nederlands (NL)"),
|
||||
new("nl-BE", "Nederlands (BE)"),
|
||||
new("fr-FR", "Français"),
|
||||
new("fr-FR", "Français")
|
||||
];
|
||||
|
||||
private static readonly IReadOnlyList<LanguageOption> DevelopmentLanguageOptions =
|
||||
[
|
||||
..ProductionLanguageOptions,
|
||||
new("qps-Ploc", "Pseudo")
|
||||
];
|
||||
|
||||
|
||||
@@ -65,6 +65,12 @@ else
|
||||
case "LogContribution":
|
||||
<span class="badge bg-secondary">@L["RegistryActionLog.Badge.ContributionLogged"]</span>
|
||||
break;
|
||||
case "MetadataFetchSucceeded":
|
||||
<span class="badge bg-success">@L["RegistryActionLog.Badge.MetadataFetchSucceeded"]</span>
|
||||
break;
|
||||
case "MetadataFetchFailed":
|
||||
<span class="badge bg-danger">@L["RegistryActionLog.Badge.MetadataFetchFailed"]</span>
|
||||
break;
|
||||
default:
|
||||
<span class="badge bg-dark">@log.ActionType</span>
|
||||
break;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using BirthList.Domain.Entities;
|
||||
using BirthList.Web.Authorization;
|
||||
using BirthList.Web.Features.Registries;
|
||||
using BirthList.Web.Services;
|
||||
@@ -41,10 +42,11 @@ public partial class RegistryAdmin : ComponentBase
|
||||
protected string ActiveTab { get; set; } = "items";
|
||||
protected RenderFragment ToolbarContent => builder =>
|
||||
{
|
||||
builder.AddMarkupContent(0, "<span class='ql-formats'><select class='ql-header'><option selected></option><option value='1'></option><option value='2'></option></select></span>");
|
||||
builder.AddMarkupContent(1, "<span class='ql-formats'><button class='ql-bold'></button><button class='ql-italic'></button><button class='ql-underline'></button></span>");
|
||||
builder.AddMarkupContent(2, "<span class='ql-formats'><button class='ql-list' value='ordered'></button><button class='ql-list' value='bullet'></button></span>");
|
||||
builder.AddMarkupContent(3, "<span class='ql-formats'><button class='ql-link'></button><button class='ql-clean'></button></span>");
|
||||
builder.AddMarkupContent(0, @"<span class='ql-formats'><select class='ql-size'><option value='small'></option><option selected></option><option value='large'></option><option value='huge'></option></select><select class='ql-header'><option selected></option><option value='1'></option><option value='2'></option><option value='3'></option><option value='4'></option><option value='5'></option></select></span>");
|
||||
builder.AddMarkupContent(1, @"<span class='ql-formats'><button class='ql-bold'></button><button class='ql-italic'></button><button class='ql-underline'></button><button class='ql-strike'></button></span>");
|
||||
builder.AddMarkupContent(2, @"<span class='ql-formats'><select class=""ql-color""></select><select class=""ql-background""></select></span>");
|
||||
builder.AddMarkupContent(3, @"<span class='ql-formats'><button class='ql-list' value='ordered'></button><button class='ql-list' value='bullet'></button></span>");
|
||||
builder.AddMarkupContent(4, @"<span class='ql-formats'><button class='ql-link'></button><button class='ql-clean'></button></span>");
|
||||
};
|
||||
|
||||
private bool _pendingEditorLoad;
|
||||
@@ -583,41 +585,117 @@ public partial class RegistryAdmin : ComponentBase
|
||||
return;
|
||||
}
|
||||
|
||||
var metadata = await RegistryMetadataService.FetchAsync(ItemModel.ProductUrl, CancellationToken.None).ConfigureAwait(false);
|
||||
if (metadata is null)
|
||||
var userId = await RegistryUserContext.GetUserIdAsync(CancellationToken.None).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
return;
|
||||
var metadata = await RegistryMetadataService.FetchAsync(ItemModel.ProductUrl, CancellationToken.None).ConfigureAwait(false);
|
||||
if (metadata is null)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(userId))
|
||||
{
|
||||
await RegistryService.LogUserActionAsync(
|
||||
RegistryId,
|
||||
userId,
|
||||
UserActionType.MetadataFetchFailed,
|
||||
ItemModel.Id,
|
||||
TruncateLogDetails($"metadata-fetch failed; url={ItemModel.ProductUrl}; reason=no-metadata"),
|
||||
CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(metadata.NormalizedUrl))
|
||||
{
|
||||
ItemModel.ProductUrl = metadata.NormalizedUrl;
|
||||
}
|
||||
|
||||
if ((string.IsNullOrWhiteSpace(ItemModel.Name) || string.Equals(ItemModel.Name, "Amazon", StringComparison.OrdinalIgnoreCase)) && !string.IsNullOrWhiteSpace(metadata.Title))
|
||||
{
|
||||
ItemModel.Name = metadata.Title;
|
||||
}
|
||||
|
||||
if ((string.IsNullOrWhiteSpace(ItemModel.Description) || string.Equals(ItemModel.Description, "Amazon", StringComparison.OrdinalIgnoreCase)) && !string.IsNullOrWhiteSpace(metadata.Description))
|
||||
{
|
||||
ItemModel.Description = metadata.Description;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(ItemModel.PictureUrl) && !string.IsNullOrWhiteSpace(metadata.ImageUrl))
|
||||
{
|
||||
ItemModel.PictureUrl = metadata.ImageUrl;
|
||||
}
|
||||
|
||||
if (!ItemModel.PriceAmount.HasValue && metadata.PriceAmount.HasValue)
|
||||
{
|
||||
ItemModel.PriceAmount = metadata.PriceAmount;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(metadata.CurrencyCode))
|
||||
{
|
||||
ItemModel.CurrencyCode = metadata.CurrencyCode;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(userId))
|
||||
{
|
||||
var summary = $"metadata-fetch success; url={ItemModel.ProductUrl}; title={(string.IsNullOrWhiteSpace(metadata.Title) ? "-" : metadata.Title)}; image={(string.IsNullOrWhiteSpace(metadata.ImageUrl) ? "no" : "yes")}; price={(metadata.PriceAmount.HasValue ? metadata.PriceAmount.Value.ToString("0.00") : "-")}; currency={(string.IsNullOrWhiteSpace(metadata.CurrencyCode) ? "-" : metadata.CurrencyCode)}";
|
||||
await RegistryService.LogUserActionAsync(
|
||||
RegistryId,
|
||||
userId,
|
||||
UserActionType.MetadataFetchSucceeded,
|
||||
ItemModel.Id,
|
||||
TruncateLogDetails(summary),
|
||||
CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(userId))
|
||||
{
|
||||
await RegistryService.LogUserActionAsync(
|
||||
RegistryId,
|
||||
userId,
|
||||
UserActionType.MetadataFetchFailed,
|
||||
ItemModel.Id,
|
||||
TruncateLogDetails($"metadata-fetch failed; url={ItemModel.ProductUrl}; reason=http; message={ex.Message}"),
|
||||
CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (TaskCanceledException ex)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(userId))
|
||||
{
|
||||
await RegistryService.LogUserActionAsync(
|
||||
RegistryId,
|
||||
userId,
|
||||
UserActionType.MetadataFetchFailed,
|
||||
ItemModel.Id,
|
||||
TruncateLogDetails($"metadata-fetch failed; url={ItemModel.ProductUrl}; reason=timeout-or-cancel; message={ex.Message}"),
|
||||
CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(userId))
|
||||
{
|
||||
await RegistryService.LogUserActionAsync(
|
||||
RegistryId,
|
||||
userId,
|
||||
UserActionType.MetadataFetchFailed,
|
||||
ItemModel.Id,
|
||||
TruncateLogDetails($"metadata-fetch failed; url={ItemModel.ProductUrl}; reason=invalid-operation; message={ex.Message}"),
|
||||
CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string TruncateLogDetails(string details)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(details))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(metadata.NormalizedUrl))
|
||||
{
|
||||
ItemModel.ProductUrl = metadata.NormalizedUrl;
|
||||
}
|
||||
|
||||
if ((string.IsNullOrWhiteSpace(ItemModel.Name) || string.Equals(ItemModel.Name, "Amazon", StringComparison.OrdinalIgnoreCase)) && !string.IsNullOrWhiteSpace(metadata.Title))
|
||||
{
|
||||
ItemModel.Name = metadata.Title;
|
||||
}
|
||||
|
||||
if ((string.IsNullOrWhiteSpace(ItemModel.Description) || string.Equals(ItemModel.Description, "Amazon", StringComparison.OrdinalIgnoreCase)) && !string.IsNullOrWhiteSpace(metadata.Description))
|
||||
{
|
||||
ItemModel.Description = metadata.Description;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(ItemModel.PictureUrl) && !string.IsNullOrWhiteSpace(metadata.ImageUrl))
|
||||
{
|
||||
ItemModel.PictureUrl = metadata.ImageUrl;
|
||||
}
|
||||
|
||||
if (!ItemModel.PriceAmount.HasValue && metadata.PriceAmount.HasValue)
|
||||
{
|
||||
ItemModel.PriceAmount = metadata.PriceAmount;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(metadata.CurrencyCode))
|
||||
{
|
||||
ItemModel.CurrencyCode = metadata.CurrencyCode;
|
||||
}
|
||||
return details.Length <= 500 ? details : details[..500];
|
||||
}
|
||||
|
||||
protected async Task CreateInviteAsync()
|
||||
|
||||
@@ -168,3 +168,16 @@
|
||||
.category-toggle span.bi {
|
||||
margin-right: 0.25rem;
|
||||
}
|
||||
|
||||
/* Render Quill text-size classes in public header content */
|
||||
.header-content ::deep .ql-size-small {
|
||||
font-size: 0.75em;
|
||||
}
|
||||
|
||||
.header-content ::deep .ql-size-large {
|
||||
font-size: 1.5em;
|
||||
}
|
||||
|
||||
.header-content ::deep .ql-size-huge {
|
||||
font-size: 2.5em;
|
||||
}
|
||||
|
||||
@@ -23,15 +23,12 @@ internal sealed class RegistryMetadataService(IHttpClientFactory httpClientFacto
|
||||
request.Headers.AcceptLanguage.ParseAdd("en-US,en;q=0.9");
|
||||
|
||||
using var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
var html = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
|
||||
if (string.IsNullOrWhiteSpace(html))
|
||||
{
|
||||
return null;
|
||||
throw new InvalidOperationException("Metadata response content was empty.");
|
||||
}
|
||||
|
||||
var meta = ParseMeta(html);
|
||||
|
||||
@@ -13,10 +13,10 @@ using Microsoft.AspNetCore.Components.Authorization;
|
||||
using Microsoft.AspNetCore.HttpOverrides;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Localization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Storage;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
@@ -136,15 +136,18 @@ builder.Services.AddLocalization(options =>
|
||||
options.ResourcesPath = "Resources";
|
||||
});
|
||||
|
||||
var supportedCultures = new[]
|
||||
var supportedCultures = new List<CultureInfo>
|
||||
{
|
||||
new CultureInfo("en"),
|
||||
new CultureInfo("nl-NL"),
|
||||
new CultureInfo("nl-BE"),
|
||||
new CultureInfo("fr-FR"),
|
||||
new CultureInfo("qps-Ploc")
|
||||
new("en"),
|
||||
new("nl-BE"),
|
||||
new("fr-FR")
|
||||
};
|
||||
|
||||
if (builder.Environment.IsDevelopment())
|
||||
{
|
||||
supportedCultures.Add(new CultureInfo("qps-Ploc"));
|
||||
}
|
||||
|
||||
builder.Services.Configure<RequestLocalizationOptions>(options =>
|
||||
{
|
||||
options.DefaultRequestCulture = new RequestCulture("en");
|
||||
|
||||
@@ -216,6 +216,8 @@
|
||||
<data name="RegistryActionLog.Badge.PurchaseUnmarked" xml:space="preserve"><value>Achat annulé</value></data>
|
||||
<data name="RegistryActionLog.Badge.PartialPurchase" xml:space="preserve"><value>Achat partiel</value></data>
|
||||
<data name="RegistryActionLog.Badge.ContributionLogged" xml:space="preserve"><value>Participation enregistrée</value></data>
|
||||
<data name="RegistryActionLog.Badge.MetadataFetchSucceeded" xml:space="preserve"><value>Remplissage auto réussi</value></data>
|
||||
<data name="RegistryActionLog.Badge.MetadataFetchFailed" xml:space="preserve"><value>Remplissage auto échoué</value></data>
|
||||
|
||||
<data name="Error.PageTitle" xml:space="preserve"><value>Erreur</value></data>
|
||||
<data name="Error.Title" xml:space="preserve"><value>Erreur.</value></data>
|
||||
|
||||
@@ -216,6 +216,8 @@
|
||||
<data name="RegistryActionLog.Badge.PurchaseUnmarked" xml:space="preserve"><value>Aankoop verwijderd</value></data>
|
||||
<data name="RegistryActionLog.Badge.PartialPurchase" xml:space="preserve"><value>Gedeeltelijke aankoop</value></data>
|
||||
<data name="RegistryActionLog.Badge.ContributionLogged" xml:space="preserve"><value>Deelname geregistreerd</value></data>
|
||||
<data name="RegistryActionLog.Badge.MetadataFetchSucceeded" xml:space="preserve"><value>Automatisch ophalen gelukt</value></data>
|
||||
<data name="RegistryActionLog.Badge.MetadataFetchFailed" xml:space="preserve"><value>Automatisch ophalen mislukt</value></data>
|
||||
|
||||
<data name="Error.PageTitle" xml:space="preserve"><value>Fout</value></data>
|
||||
<data name="Error.Title" xml:space="preserve"><value>Fout.</value></data>
|
||||
|
||||
@@ -621,6 +621,12 @@
|
||||
<data name="RegistryActionLog.Badge.ContributionLogged" xml:space="preserve">
|
||||
<value>[[Contribution logged]]</value>
|
||||
</data>
|
||||
<data name="RegistryActionLog.Badge.MetadataFetchSucceeded" xml:space="preserve">
|
||||
<value>[[Auto fetch succeeded]]</value>
|
||||
</data>
|
||||
<data name="RegistryActionLog.Badge.MetadataFetchFailed" xml:space="preserve">
|
||||
<value>[[Auto fetch failed]]</value>
|
||||
</data>
|
||||
<data name="Error.PageTitle" xml:space="preserve">
|
||||
<value>[[Error]]</value>
|
||||
</data>
|
||||
|
||||
@@ -223,6 +223,8 @@
|
||||
<data name="RegistryActionLog.Badge.PurchaseUnmarked" xml:space="preserve"><value>Purchase unmarked</value></data>
|
||||
<data name="RegistryActionLog.Badge.PartialPurchase" xml:space="preserve"><value>Partial purchase</value></data>
|
||||
<data name="RegistryActionLog.Badge.ContributionLogged" xml:space="preserve"><value>Contribution logged</value></data>
|
||||
<data name="RegistryActionLog.Badge.MetadataFetchSucceeded" xml:space="preserve"><value>Auto fetch succeeded</value></data>
|
||||
<data name="RegistryActionLog.Badge.MetadataFetchFailed" xml:space="preserve"><value>Auto fetch failed</value></data>
|
||||
|
||||
<data name="Error.PageTitle" xml:space="preserve"><value>Error</value></data>
|
||||
<data name="Error.Title" xml:space="preserve"><value>Error.</value></data>
|
||||
|
||||
Reference in New Issue
Block a user