Files
Migu2.0/MiGu.Server/Dashboard/DashboardShortcutService.cs
T

177 lines
5.8 KiB
C#

using System.Text.Json;
using Microsoft.EntityFrameworkCore;
using MiGu.Server.Auth;
using MiGu.Server.Persistence;
namespace MiGu.Server.Dashboard;
public sealed class DashboardShortcutService
{
private readonly PlatformDbContext _db;
private readonly RbacStore _rbac;
private static readonly JsonSerializerOptions JsonOpts = new()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
public DashboardShortcutService(PlatformDbContext db, RbacStore rbac)
{
_db = db;
_rbac = rbac;
}
public sealed record QuickEntriesResult(IReadOnlyList<string> Keys, bool UsingDefaults);
public async Task<QuickEntriesResult> GetAsync(string userId, string scope, CancellationToken ct = default)
{
scope = NormalizeScope(scope);
var allowed = AllowedPages(userId, scope);
var row = await _db.UserDashboardShortcuts
.AsNoTracking()
.FirstOrDefaultAsync(x => x.UserId == userId && x.Scope == scope, ct);
if (row == null)
{
var defaults = EnsureMandatoryKeys(
FilterKeys(DashboardShortcutCatalog.DefaultKeysForScope(scope), scope, allowed),
scope, allowed);
return new QuickEntriesResult(
defaults.Take(DashboardShortcutCatalog.MaxKeysPerUser).ToList(),
UsingDefaults: true);
}
var keys = ParseKeys(row.KeysJson);
var filtered = EnsureMandatoryKeys(
FilterKeys(keys, scope, allowed), scope, allowed)
.Take(DashboardShortcutCatalog.MaxKeysPerUser)
.ToList();
return new QuickEntriesResult(filtered, UsingDefaults: false);
}
public async Task<QuickEntriesResult> SaveAsync(
string userId, string scope, IReadOnlyList<string>? keys, CancellationToken ct = default)
{
scope = NormalizeScope(scope);
var allowed = AllowedPages(userId, scope);
var sanitized = EnsureMandatoryKeys(
FilterKeys(Deduplicate(keys ?? []), scope, allowed), scope, allowed);
if (sanitized.Count > DashboardShortcutCatalog.MaxKeysPerUser)
sanitized = sanitized.Take(DashboardShortcutCatalog.MaxKeysPerUser).ToList();
var row = await _db.UserDashboardShortcuts
.FirstOrDefaultAsync(x => x.UserId == userId && x.Scope == scope, ct);
var json = JsonSerializer.Serialize(sanitized, JsonOpts);
var now = DateTimeOffset.UtcNow;
if (row == null)
{
_db.UserDashboardShortcuts.Add(new UserDashboardShortcut
{
UserId = userId,
Scope = scope,
KeysJson = json,
UpdatedAt = now
});
}
else
{
row.KeysJson = json;
row.UpdatedAt = now;
}
await _db.SaveChangesAsync(ct);
return new QuickEntriesResult(sanitized, UsingDefaults: false);
}
private HashSet<string> AllowedPages(string userId, string scope)
{
var user = _rbac.FindUserById(userId);
if (user == null || !user.Enabled)
return new HashSet<string>(StringComparer.OrdinalIgnoreCase);
var eff = _rbac.ComputeEffective(user, scope);
return eff.Pages.ToHashSet(StringComparer.OrdinalIgnoreCase);
}
private static List<string> FilterKeys(
IEnumerable<string> keys, string scope, HashSet<string> allowedPages)
{
var outKeys = new List<string>();
var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
foreach (var raw in keys)
{
var key = DashboardShortcutCatalog.NormalizeKey(raw ?? "");
if (string.IsNullOrEmpty(key)) continue;
if (!DashboardShortcutCatalog.IsValidKey(key)) continue;
if (!DashboardShortcutCatalog.KeyMatchesScope(key, scope)) continue;
if (seen.Contains(key)) continue;
var pageKey = DashboardShortcutCatalog.PageKeyFor(key);
if (!allowedPages.Contains(pageKey)) continue;
seen.Add(key);
outKeys.Add(key);
}
return outKeys;
}
private static List<string> EnsureMandatoryKeys(
List<string> keys, string scope, HashSet<string> allowedPages)
{
if (!string.Equals(scope, PageCatalog.ScopePlatform, StringComparison.OrdinalIgnoreCase))
return keys;
var result = new List<string>();
var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
foreach (var raw in DashboardShortcutCatalog.MandatoryPlatformKeys)
{
var key = DashboardShortcutCatalog.NormalizeKey(raw);
if (!DashboardShortcutCatalog.IsValidKey(key)) continue;
var pageKey = DashboardShortcutCatalog.PageKeyFor(key);
if (!allowedPages.Contains(pageKey)) continue;
if (seen.Add(key)) result.Add(key);
}
foreach (var key in keys)
{
if (seen.Add(key)) result.Add(key);
}
return result;
}
private static List<string> Deduplicate(IReadOnlyList<string> keys)
{
var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
var list = new List<string>();
foreach (var k in keys)
{
if (string.IsNullOrWhiteSpace(k)) continue;
var t = k.Trim();
if (seen.Add(t)) list.Add(t);
}
return list;
}
private static List<string> ParseKeys(string json)
{
try
{
return JsonSerializer.Deserialize<List<string>>(json, JsonOpts) ?? [];
}
catch
{
return [];
}
}
private static string NormalizeScope(string scope) =>
string.Equals(scope, PageCatalog.ScopeMonitor, StringComparison.OrdinalIgnoreCase)
? PageCatalog.ScopeMonitor
: PageCatalog.ScopePlatform;
}