Harden request validation and admin UX cues
This commit is contained in:
@@ -155,10 +155,17 @@ public static class AdminSiteSettingsEndpoints
|
||||
|
||||
private static async Task<IResult> UpdateSiteSettings(
|
||||
HttpContext context,
|
||||
UpdateSiteSettingsRequest request,
|
||||
UpdateSiteSettingsRequest? request,
|
||||
AwardsDbContext db,
|
||||
IAdminAuditService adminAuditService)
|
||||
{
|
||||
var requestValidationError = ValidateSiteSettingsRequest(request);
|
||||
if (requestValidationError is not null)
|
||||
{
|
||||
return requestValidationError;
|
||||
}
|
||||
|
||||
var validatedRequest = request!;
|
||||
var session = AdminEndpointConventions.CurrentSession(context);
|
||||
|
||||
var settings = await db.SiteSettings.FirstOrDefaultAsync(item => item.Id == 1);
|
||||
@@ -167,20 +174,20 @@ public static class AdminSiteSettingsEndpoints
|
||||
return Results.NotFound();
|
||||
}
|
||||
|
||||
var urlValidationError = NormalizeSiteSettingsUrls(request, out var normalizedUrls, out var socialLinks);
|
||||
var urlValidationError = NormalizeSiteSettingsUrls(validatedRequest, out var normalizedUrls, out var socialLinks);
|
||||
if (urlValidationError is not null)
|
||||
{
|
||||
return urlValidationError;
|
||||
}
|
||||
|
||||
settings.HostDisplayName = request.HostDisplayName.Trim();
|
||||
settings.HostTagline = request.HostTagline.Trim();
|
||||
settings.HostArtistName = request.HostArtistName.Trim();
|
||||
settings.HostDisplayName = validatedRequest.HostDisplayName.Trim();
|
||||
settings.HostTagline = validatedRequest.HostTagline.Trim();
|
||||
settings.HostArtistName = validatedRequest.HostArtistName.Trim();
|
||||
settings.NewsletterUrl = normalizedUrls.NewsletterUrl;
|
||||
settings.ShareXUrl = normalizedUrls.ShareXUrl;
|
||||
settings.ShareDiscordUrl = normalizedUrls.ShareDiscordUrl;
|
||||
settings.PrivacyEmail = request.PrivacyEmail.Trim();
|
||||
var trimmedPrivacyContent = request.PrivacyPolicyContent.Trim();
|
||||
settings.PrivacyEmail = validatedRequest.PrivacyEmail.Trim();
|
||||
var trimmedPrivacyContent = validatedRequest.PrivacyPolicyContent.Trim();
|
||||
var privacyChanged = !string.Equals(settings.PrivacyPolicyContent, trimmedPrivacyContent, StringComparison.Ordinal);
|
||||
settings.PrivacyPolicyContent = trimmedPrivacyContent;
|
||||
if (privacyChanged)
|
||||
@@ -190,32 +197,32 @@ public static class AdminSiteSettingsEndpoints
|
||||
}
|
||||
|
||||
settings.ImprintUrl = normalizedUrls.ImprintUrl;
|
||||
settings.ImprintContent = request.ImprintContent.Trim();
|
||||
settings.ImprintContent = validatedRequest.ImprintContent.Trim();
|
||||
settings.ContactUrl = normalizedUrls.ContactUrl;
|
||||
settings.ContactContent = request.ContactContent.Trim();
|
||||
settings.ContactContent = validatedRequest.ContactContent.Trim();
|
||||
settings.SponsorsUrl = normalizedUrls.SponsorsUrl;
|
||||
settings.SponsorsContent = request.SponsorsContent.Trim();
|
||||
settings.SponsorsContent = validatedRequest.SponsorsContent.Trim();
|
||||
settings.ShowactsUrl = normalizedUrls.ShowactsUrl;
|
||||
settings.ShowactsContent = request.ShowactsContent.Trim();
|
||||
settings.StreamBannerEyebrow = SeasonMappings.NormalizePlainTextContent(request.StreamBannerEyebrow);
|
||||
settings.StreamBannerTitle = SeasonMappings.NormalizePlainTextContent(request.StreamBannerTitle);
|
||||
settings.StreamBannerText = SeasonMappings.NormalizePlainTextContent(request.StreamBannerText);
|
||||
settings.StreamBannerLiveButtonLabel = SeasonMappings.NormalizePlainTextContent(request.StreamBannerLiveButtonLabel);
|
||||
settings.ShowactsContent = validatedRequest.ShowactsContent.Trim();
|
||||
settings.StreamBannerEyebrow = SeasonMappings.NormalizePlainTextContent(validatedRequest.StreamBannerEyebrow);
|
||||
settings.StreamBannerTitle = SeasonMappings.NormalizePlainTextContent(validatedRequest.StreamBannerTitle);
|
||||
settings.StreamBannerText = SeasonMappings.NormalizePlainTextContent(validatedRequest.StreamBannerText);
|
||||
settings.StreamBannerLiveButtonLabel = SeasonMappings.NormalizePlainTextContent(validatedRequest.StreamBannerLiveButtonLabel);
|
||||
settings.StreamBannerLiveButtonUrl = normalizedUrls.StreamBannerLiveButtonUrl;
|
||||
settings.StreamBannerLockedButtonLabel = SeasonMappings.NormalizePlainTextContent(request.StreamBannerLockedButtonLabel);
|
||||
settings.StreamBannerUseCompletedContent = request.StreamBannerUseCompletedContent;
|
||||
settings.StreamBannerCompletedEyebrow = SeasonMappings.NormalizePlainTextContent(request.StreamBannerCompletedEyebrow);
|
||||
settings.StreamBannerCompletedTitle = SeasonMappings.NormalizePlainTextContent(request.StreamBannerCompletedTitle);
|
||||
settings.StreamBannerCompletedText = SeasonMappings.NormalizePlainTextContent(request.StreamBannerCompletedText);
|
||||
settings.StreamBannerCompletedButtonLabel = SeasonMappings.NormalizePlainTextContent(request.StreamBannerCompletedButtonLabel);
|
||||
settings.StreamBannerLockedButtonLabel = SeasonMappings.NormalizePlainTextContent(validatedRequest.StreamBannerLockedButtonLabel);
|
||||
settings.StreamBannerUseCompletedContent = validatedRequest.StreamBannerUseCompletedContent;
|
||||
settings.StreamBannerCompletedEyebrow = SeasonMappings.NormalizePlainTextContent(validatedRequest.StreamBannerCompletedEyebrow);
|
||||
settings.StreamBannerCompletedTitle = SeasonMappings.NormalizePlainTextContent(validatedRequest.StreamBannerCompletedTitle);
|
||||
settings.StreamBannerCompletedText = SeasonMappings.NormalizePlainTextContent(validatedRequest.StreamBannerCompletedText);
|
||||
settings.StreamBannerCompletedButtonLabel = SeasonMappings.NormalizePlainTextContent(validatedRequest.StreamBannerCompletedButtonLabel);
|
||||
settings.StreamBannerCompletedButtonUrl = normalizedUrls.StreamBannerCompletedButtonUrl;
|
||||
settings.AwardsSectionTitle = SeasonMappings.NormalizePlainTextContent(request.AwardsSectionTitle);
|
||||
settings.AwardsSectionDescription = SeasonMappings.NormalizePlainTextContent(request.AwardsSectionDescription);
|
||||
settings.SubcategoriesSectionTitle = SeasonMappings.NormalizePlainTextContent(request.SubcategoriesSectionTitle);
|
||||
settings.SubcategoriesSectionDescription = SeasonMappings.NormalizePlainTextContent(request.SubcategoriesSectionDescription);
|
||||
settings.AwardsSectionTitle = SeasonMappings.NormalizePlainTextContent(validatedRequest.AwardsSectionTitle);
|
||||
settings.AwardsSectionDescription = SeasonMappings.NormalizePlainTextContent(validatedRequest.AwardsSectionDescription);
|
||||
settings.SubcategoriesSectionTitle = SeasonMappings.NormalizePlainTextContent(validatedRequest.SubcategoriesSectionTitle);
|
||||
settings.SubcategoriesSectionDescription = SeasonMappings.NormalizePlainTextContent(validatedRequest.SubcategoriesSectionDescription);
|
||||
settings.SocialLinksJson = JsonSerializer.Serialize(socialLinks);
|
||||
settings.FaqJson = JsonSerializer.Serialize(request.Faq ?? []);
|
||||
settings.ShowactFormSchemaJson = request.ShowactFormSchemaJson ?? "[]";
|
||||
settings.FaqJson = JsonSerializer.Serialize(validatedRequest.Faq);
|
||||
settings.ShowactFormSchemaJson = validatedRequest.ShowactFormSchemaJson ?? "[]";
|
||||
|
||||
adminAuditService.AddEntry(
|
||||
session.TwitchUserId,
|
||||
@@ -228,7 +235,7 @@ public static class AdminSiteSettingsEndpoints
|
||||
settings.HostDisplayName,
|
||||
privacyChanged,
|
||||
socialLinkCount = socialLinks.Length,
|
||||
faqCount = request.Faq?.Length ?? 0,
|
||||
faqCount = validatedRequest.Faq.Length,
|
||||
},
|
||||
RequestMetadataReader.Read(context));
|
||||
|
||||
@@ -340,6 +347,58 @@ public static class AdminSiteSettingsEndpoints
|
||||
return null;
|
||||
}
|
||||
|
||||
private static IResult? ValidateSiteSettingsRequest(UpdateSiteSettingsRequest? request)
|
||||
{
|
||||
if (request is null)
|
||||
{
|
||||
return Results.BadRequest(new { message = "Public Site Settings brauchen einen Request-Body." });
|
||||
}
|
||||
|
||||
if (request.SocialLinks is null || request.Faq is null)
|
||||
{
|
||||
return Results.BadRequest(new { message = "Public Site Settings brauchen Social Links und FAQ im Request-Body." });
|
||||
}
|
||||
|
||||
string?[] requiredTextFields =
|
||||
[
|
||||
request.HostDisplayName,
|
||||
request.HostTagline,
|
||||
request.HostArtistName,
|
||||
request.NewsletterUrl,
|
||||
request.ShareXUrl,
|
||||
request.ShareDiscordUrl,
|
||||
request.PrivacyEmail,
|
||||
request.PrivacyPolicyContent,
|
||||
request.ImprintUrl,
|
||||
request.ImprintContent,
|
||||
request.ContactUrl,
|
||||
request.ContactContent,
|
||||
request.SponsorsUrl,
|
||||
request.SponsorsContent,
|
||||
request.ShowactsUrl,
|
||||
request.ShowactsContent,
|
||||
request.StreamBannerEyebrow,
|
||||
request.StreamBannerTitle,
|
||||
request.StreamBannerText,
|
||||
request.StreamBannerLiveButtonLabel,
|
||||
request.StreamBannerLiveButtonUrl,
|
||||
request.StreamBannerLockedButtonLabel,
|
||||
request.StreamBannerCompletedEyebrow,
|
||||
request.StreamBannerCompletedTitle,
|
||||
request.StreamBannerCompletedText,
|
||||
request.StreamBannerCompletedButtonLabel,
|
||||
request.StreamBannerCompletedButtonUrl,
|
||||
request.AwardsSectionTitle,
|
||||
request.AwardsSectionDescription,
|
||||
request.SubcategoriesSectionTitle,
|
||||
request.SubcategoriesSectionDescription,
|
||||
];
|
||||
|
||||
return requiredTextFields.Any(field => field is null)
|
||||
? Results.BadRequest(new { message = "Public Site Settings brauchen alle Pflichtfelder im Request-Body." })
|
||||
: null;
|
||||
}
|
||||
|
||||
private static bool TryNormalizePublicUrl(string? value, string fieldName, out string normalizedUrl, out string errorMessage)
|
||||
{
|
||||
normalizedUrl = (value ?? string.Empty).Trim();
|
||||
|
||||
Reference in New Issue
Block a user