feat: Implement "everyone owns all avatar items" toggle; update server settings, controller logic, and admin UI

This commit is contained in:
Alexa
2026-07-09 11:37:47 +03:00
parent 479ea273ea
commit e27b06ce5b
7 changed files with 205 additions and 4 deletions
@@ -496,6 +496,8 @@ public static class DatabaseBootstrap
@"ALTER TABLE ""ServerSettings"" ADD COLUMN IF NOT EXISTS ""ProfanityFilterDisabled"" boolean NOT NULL DEFAULT false;");
await db.Database.ExecuteSqlRawAsync(
@"ALTER TABLE ""ServerSettings"" ADD COLUMN IF NOT EXISTS ""CharadesSlotBindingsJson"" text NOT NULL DEFAULT '';");
await db.Database.ExecuteSqlRawAsync(
@"ALTER TABLE ""ServerSettings"" ADD COLUMN IF NOT EXISTS ""AllAvatarItemsOwned"" boolean NOT NULL DEFAULT false;");
return;
}
@@ -570,6 +572,18 @@ public static class DatabaseBootstrap
// ignore the duplicate-column error; any other schema problem
// will surface when EF reads ServerSettings.
}
try
{
await db.Database.ExecuteSqlRawAsync(
@"ALTER TABLE ""ServerSettings"" ADD COLUMN ""AllAvatarItemsOwned"" INTEGER NOT NULL DEFAULT 0;");
}
catch
{
// SQLite has no ADD COLUMN IF NOT EXISTS. If it already exists,
// ignore the duplicate-column error; any other schema problem
// will surface when EF reads ServerSettings.
}
}
private static async Task EnsureInventorySchemaAsync(DorkNetDbContext db)
@@ -107,7 +107,7 @@ public static class RequestTracingExtensions
try
{
capture.Seek(0, SeekOrigin.Begin);
if (ShouldReadResponseBody(ctx.Response.ContentType, status))
if (ShouldReadResponseBody(ctx.Response.ContentType, status, path))
responseBody = await new StreamReader(capture, leaveOpen: true).ReadToEndAsync();
capture.Seek(0, SeekOrigin.Begin);
await capture.CopyToAsync(originalBody);
@@ -133,6 +133,19 @@ public static class RequestTracingExtensions
: status >= 400 ? LogLevel.Warning
: LogLevel.Information;
// TEMP diagnostic (presence "others online show offline"): the
// normal [req] line truncates to 300 chars and never reads 2xx
// bodies, so we can't see the /player roster the watch renders
// from. Dump the FULL redacted body for /player on any status.
// Remove once the presence-offline bug is localized.
if (path.Equals("/player", StringComparison.OrdinalIgnoreCase) && !string.IsNullOrEmpty(safeResponse))
{
reqLogger.LogInformation(
"[presence-debug] {Method} {Host}{Path}{Query} status={Status} body={Body}",
ctx.Request.Method, ctx.Request.Host, ctx.Request.Path,
ctx.Request.QueryString.Value ?? "", status, safeResponse);
}
reqLogger.Log(level, thrown,
"[req] {Status} {Method} {Host}{Path}{Query} {ElapsedMs}ms auth={AuthPresence} contentType={ContentType} contentLength={ContentLength} req={ReqBody} resp={RespBody}",
status, ctx.Request.Method, ctx.Request.Host, ctx.Request.Path,
@@ -228,9 +241,12 @@ public static class RequestTracingExtensions
|| ext.Equals(".log", StringComparison.OrdinalIgnoreCase);
}
private static bool ShouldReadResponseBody(string? contentType, int status)
private static bool ShouldReadResponseBody(string? contentType, int status, string path)
{
if (status < 400) return false;
// TEMP diagnostic: always read the /player roster body (even 200s) so
// [presence-debug] can dump the exact online payload the watch renders.
var isPresence = path.Equals("/player", StringComparison.OrdinalIgnoreCase);
if (status < 400 && !isPresence) return false;
if (string.IsNullOrWhiteSpace(contentType)) return true;
return contentType.StartsWith("application/json", StringComparison.OrdinalIgnoreCase)
|| contentType.StartsWith("text/", StringComparison.OrdinalIgnoreCase);