added methods to update vanity colors and submodule bump

This commit is contained in:
azyges
2025-09-26 18:53:47 +09:00
parent f5d621e354
commit 2b05223a4b
3 changed files with 117 additions and 1 deletions

View File

@@ -6,6 +6,7 @@ using LightlessSync.API.Data;
using LightlessSync.API.Dto.Group;
using LightlessSyncShared.Metrics;
using Microsoft.AspNetCore.SignalR;
using System.Threading;
namespace LightlessSyncServer.Hubs;
@@ -97,6 +98,28 @@ public partial class LightlessHub
await _redis.RemoveAsync("UID:" + UserUID, StackExchange.Redis.CommandFlags.FireAndForget).ConfigureAwait(false);
}
private async Task<User?> EnsureUserHasVanity(string uid, CancellationToken cancellationToken = default)
{
cancellationToken = cancellationToken == default && _contextAccessor.HttpContext != null
? _contextAccessor.HttpContext.RequestAborted
: cancellationToken;
var user = await DbContext.Users.SingleOrDefaultAsync(u => u.UID == uid, cancellationToken).ConfigureAwait(false);
if (user == null)
{
_logger.LogCallWarning(LightlessHubLogger.Args("vanity check", uid, "missing user"));
return null;
}
if (user.HasVanity != true)
{
_logger.LogCallWarning(LightlessHubLogger.Args("vanity check", uid, "no vanity"));
return null;
}
return user;
}
private async Task SendGroupDeletedToAll(List<GroupPair> groupUsers)
{
foreach (var pair in groupUsers)

View File

@@ -605,6 +605,62 @@ public partial class LightlessHub
_lightlessMetrics.IncCounter(MetricsAPI.CounterUserPushDataTo, recipientUids.Count);
}
[Authorize(Policy = "Identified")]
public async Task UserUpdateVanityColors(UserVanityColorsDto dto)
{
if (dto == null)
{
throw new HubException("Vanity color payload required");
}
_logger.LogCallInfo(LightlessHubLogger.Args(dto.TextColorHex, dto.TextGlowColorHex));
var cooldownKey = $"vanity:colors:{UserUID}";
var existingCooldown = await _redis.GetAsync<string>(cooldownKey).ConfigureAwait(false);
if (existingCooldown != null)
{
await Clients.Caller.Client_ReceiveServerMessage(MessageSeverity.Warning, "You can update vanity colors once per minute.").ConfigureAwait(false);
return;
}
var user = await EnsureUserHasVanity(UserUID).ConfigureAwait(false);
if (user == null)
{
await Clients.Caller.Client_ReceiveServerMessage(MessageSeverity.Warning, "Vanity privileges are required to update colors.").ConfigureAwait(false);
return;
}
if (!TryNormalizeColor(dto.TextColorHex, out var textColor, out var textColorError))
{
await Clients.Caller.Client_ReceiveServerMessage(MessageSeverity.Warning, textColorError).ConfigureAwait(false);
return;
}
if (!TryNormalizeColor(dto.TextGlowColorHex, out var textGlowColor, out var textGlowError))
{
await Clients.Caller.Client_ReceiveServerMessage(MessageSeverity.Warning, textGlowError).ConfigureAwait(false);
return;
}
var currentColor = user.TextColorHex ?? string.Empty;
var currentGlow = user.TextGlowColorHex ?? string.Empty;
if (string.Equals(currentColor, textColor, StringComparison.Ordinal) &&
string.Equals(currentGlow, textGlowColor, StringComparison.Ordinal))
{
await Clients.Caller.Client_ReceiveServerMessage(MessageSeverity.Information, "Vanity colors are already set to these values.").ConfigureAwait(false);
return;
}
user.TextColorHex = textColor;
user.TextGlowColorHex = textGlowColor;
await DbContext.SaveChangesAsync().ConfigureAwait(false);
await _redis.AddAsync(cooldownKey, "true", TimeSpan.FromMinutes(1)).ConfigureAwait(false);
await Clients.Caller.Client_ReceiveServerMessage(MessageSeverity.Information, "Vanity colors updated.").ConfigureAwait(false);
}
[Authorize(Policy = "Identified")]
public async Task UserRemovePair(UserDto dto)
{
@@ -741,6 +797,43 @@ public partial class LightlessHub
await Clients.Caller.Client_UserUpdateProfile(new(dto.User)).ConfigureAwait(false);
}
private static bool TryNormalizeColor(string? value, out string normalized, out string errorMessage)
{
if (string.IsNullOrWhiteSpace(value))
{
normalized = string.Empty;
errorMessage = string.Empty;
return true;
}
var trimmed = value.Trim();
if (trimmed.StartsWith("#", StringComparison.Ordinal))
{
trimmed = trimmed[1..];
}
if (trimmed.Length != 6 && trimmed.Length != 8)
{
normalized = string.Empty;
errorMessage = "Colors must contain 6 or 8 hexadecimal characters.";
return false;
}
foreach (var ch in trimmed)
{
if (!Uri.IsHexDigit(ch))
{
normalized = string.Empty;
errorMessage = "Colors may only contain hexadecimal characters.";
return false;
}
}
normalized = "#" + trimmed.ToUpperInvariant();
errorMessage = string.Empty;
return true;
}
[GeneratedRegex(@"^([a-z0-9_ '+&,\.\-\{\}]+\/)+([a-z0-9_ '+&,\.\-\{\}]+\.[a-z]{3,4})$", RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.ECMAScript)]
private static partial Regex GamePathRegex();