Refactor room data handling: standardize timestamp usage with savedAt variable; ensure editable persistence version for room role data

This commit is contained in:
Alexa
2026-06-02 15:51:11 +03:00
parent 143d3a9a07
commit f1a616e9c1
3 changed files with 36 additions and 14 deletions
@@ -397,6 +397,7 @@ public class RoomsController(
if (room is null) return NotFound(new { error = "room_not_found" }); if (room is null) return NotFound(new { error = "room_not_found" });
var newBlob = body.RoomDataFilename?.Trim() ?? string.Empty; var newBlob = body.RoomDataFilename?.Trim() ?? string.Empty;
var savedAt = DateTime.UtcNow;
// Multi-scene rooms have a RoomScenes row per scene. The 2020 // Multi-scene rooms have a RoomScenes row per scene. The 2020
// client usually sends the scene's OrderIndex, but some flows // client usually sends the scene's OrderIndex, but some flows
@@ -426,27 +427,33 @@ public class RoomsController(
db.DormStates.Add(dormState); db.DormStates.Add(dormState);
} }
dormState.CurrentDataBlobName = newBlob; dormState.CurrentDataBlobName = newBlob;
dormState.UpdatedAt = DateTime.UtcNow; dormState.UpdatedAt = savedAt;
} }
else else
{ {
// Owned rooms: only the creator can save. Admin override var canSave = room.CreatorPlayerId == pid
// intentionally not included — admins should use /restore || await db.Players.AnyAsync(p => p.Id == pid && p.IsAdmin)
// when they need to roll a room back. || await db.RoomRoles.AnyAsync(r =>
if (room.CreatorPlayerId != pid) return Forbid(); r.RoomId == room.Id &&
r.PlayerId == pid &&
r.Accepted &&
r.Role == 0);
if (!canSave) return Forbid();
} }
if (sceneRow is not null) if (sceneRow is not null)
{ {
sceneRow.DataBlobName = newBlob; sceneRow.DataBlobName = newBlob;
sceneRow.DataModifiedAt = DateTime.UtcNow; sceneRow.DataModifiedAt = savedAt;
} }
await db.Rooms await db.Rooms
.Where(r => r.Id == roomId) .Where(r => r.Id == roomId)
.ExecuteUpdateAsync(s => s .ExecuteUpdateAsync(s => s
.SetProperty(r => r.CurrentDataBlobName, newBlob) .SetProperty(r => r.CurrentDataBlobName, newBlob)
.SetProperty(r => r.UpdatedAt, DateTime.UtcNow)); .SetProperty(r => r.UpdatedAt, savedAt));
await db.SaveChangesAsync(); await db.SaveChangesAsync();
room.CurrentDataBlobName = newBlob;
room.UpdatedAt = savedAt;
// The watch's MasterUploadRoomDataBlobCoroutine waits for BOTH // The watch's MasterUploadRoomDataBlobCoroutine waits for BOTH
// the /upload + /saveData responses AND a SubscriptionUpdateRoom // the /upload + /saveData responses AND a SubscriptionUpdateRoom
@@ -470,7 +477,7 @@ public class RoomsController(
if (sceneRow is not null && scene.Id == sceneRow.Id) if (sceneRow is not null && scene.Id == sceneRow.Id)
{ {
scene.DataBlobName = newBlob; scene.DataBlobName = newBlob;
scene.DataModifiedAt = DateTime.UtcNow; scene.DataModifiedAt = savedAt;
} }
} }
var savedSceneId = sceneRow?.OrderIndex ?? body.RoomSceneId; var savedSceneId = sceneRow?.OrderIndex ?? body.RoomSceneId;
@@ -577,7 +584,7 @@ public class RoomsController(
DataBlobName = newBlob, DataBlobName = newBlob,
MaxPlayers = sceneRow?.MaxPlayers ?? 8, MaxPlayers = sceneRow?.MaxPlayers ?? 8,
CanMatchmakeInto = sceneRow?.CanMatchmakeInto ?? true, CanMatchmakeInto = sceneRow?.CanMatchmakeInto ?? true,
DataModifiedAt = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ"), DataModifiedAt = savedAt.ToString("yyyy-MM-ddTHH:mm:ssZ"),
}); });
} }
@@ -82,13 +82,14 @@ message RoomRoleCollectionData {
// ── PersistedRoomData top-level (rec_room/persistence/persisted_room_data.proto) // ── PersistedRoomData top-level (rec_room/persistence/persisted_room_data.proto)
// //
// The 2026 schema has 30+ fields — we only set field 23 (room_role_data). // The full schema has many more fields — we only set the fields the
// Everything else stays at proto3 defaults, which the client treats as // December 2020 client needs for an editable minimal blob. Its
// "no saved state for this room" — exactly what we want for a fresh dorm // RoomPersistenceManager checks field 1 against a minimum editable
// /clone with the role config injected. // persistence version of 20, so generated blobs must carry a current
// version or room editors see the native "save data is out-of-date" prompt.
message PersistedRoomData { message PersistedRoomData {
// field 1 (DEPRECATED_version) — reserved, do not set. int32 DEPRECATED_version = 1;
// field 2 (persistence_views) — repeated, default empty list. // field 2 (persistence_views) — repeated, default empty list.
// fields 3..22 — left at defaults. // fields 3..22 — left at defaults.
RoomRoleCollectionData room_role_data = 23; RoomRoleCollectionData room_role_data = 23;
@@ -24,6 +24,8 @@ namespace DorkNet.Server.Services;
/// </summary> /// </summary>
public class RoomDataBlobService public class RoomDataBlobService
{ {
private const int December2020PersistenceVersion = 26;
/// <summary> /// <summary>
/// Cached bytes of the "all-permissions-on" blob. Identical for every /// Cached bytes of the "all-permissions-on" blob. Identical for every
/// room — there's no per-room state we vary yet, so building once at /// room — there's no per-room state we vary yet, so building once at
@@ -40,6 +42,7 @@ public class RoomDataBlobService
public byte[] OverlayAllPermsRoleData(byte[] existingBlob) public byte[] OverlayAllPermsRoleData(byte[] existingBlob)
{ {
var msg = PersistedRoomData.Parser.ParseFrom(existingBlob); var msg = PersistedRoomData.Parser.ParseFrom(existingBlob);
EnsureEditablePersistenceVersion(msg);
msg.RoomRoleData = _allPermsRoleData.Clone(); msg.RoomRoleData = _allPermsRoleData.Clone();
return msg.ToByteArray(); return msg.ToByteArray();
} }
@@ -47,6 +50,7 @@ public class RoomDataBlobService
public byte[] OverlayRroEditableRoleData(byte[] existingBlob) public byte[] OverlayRroEditableRoleData(byte[] existingBlob)
{ {
var msg = PersistedRoomData.Parser.ParseFrom(existingBlob); var msg = PersistedRoomData.Parser.ParseFrom(existingBlob);
EnsureEditablePersistenceVersion(msg);
msg.RoomRoleData = _rroEditableRoleData.Clone(); msg.RoomRoleData = _rroEditableRoleData.Clone();
return msg.ToByteArray(); return msg.ToByteArray();
} }
@@ -55,6 +59,7 @@ public class RoomDataBlobService
{ {
var msg = new PersistedRoomData var msg = new PersistedRoomData
{ {
DEPRECATEDVersion = December2020PersistenceVersion,
RoomRoleData = BuildAllPermsRoleData(), RoomRoleData = BuildAllPermsRoleData(),
}; };
@@ -73,6 +78,7 @@ public class RoomDataBlobService
{ {
var msg = new PersistedRoomData var msg = new PersistedRoomData
{ {
DEPRECATEDVersion = December2020PersistenceVersion,
RoomRoleData = BuildRroEditableRoleData(), RoomRoleData = BuildRroEditableRoleData(),
}; };
@@ -153,6 +159,14 @@ public class RoomDataBlobService
}; };
} }
private static void EnsureEditablePersistenceVersion(PersistedRoomData msg)
{
if (msg.DEPRECATEDVersion < 20)
{
msg.DEPRECATEDVersion = December2020PersistenceVersion;
}
}
private static string StableRoleGuid(int roleId) => private static string StableRoleGuid(int roleId) =>
GuidUtility.Create(GuidUtility.UrlNamespace, $"dorknet-room-role:{roleId}").ToString(); GuidUtility.Create(GuidUtility.UrlNamespace, $"dorknet-room-role:{roleId}").ToString();
} }