From fad8d81961106b7aeed5e28ed6c99b40c171042d Mon Sep 17 00:00:00 2001 From: Alexa <53471801+Alexa-RR@users.noreply.github.com> Date: Sun, 31 May 2026 15:32:16 +0300 Subject: [PATCH] inventions: resolve real creator ids for a room (creatorIds) api/inventions/v1/creatorIds was a stub returning an empty list, so the watch's GetCreatorIdsOfInventionsInRoom never learned which creators it must request invention permissions for before spawning a room's invention-based geometry. Resolve the real set from InventionEntity.CreationRoomId and factor the room->inventions lookup the /room endpoint already used into a shared ResolveRoomInventionsAsync helper (direct matches unioned with the imported cluster). ByRoom now calls the helper too. creatorIds returns List of distinct creator account ids. --- .../API/Inventions/InventionsController.cs | 96 ++++++++++--------- DorkNet.Server/Data/url-coverage.md | 2 +- 2 files changed, 52 insertions(+), 46 deletions(-) diff --git a/DorkNet.Server/Controllers/API/Inventions/InventionsController.cs b/DorkNet.Server/Controllers/API/Inventions/InventionsController.cs index bbfba6c..54b5816 100644 --- a/DorkNet.Server/Controllers/API/Inventions/InventionsController.cs +++ b/DorkNet.Server/Controllers/API/Inventions/InventionsController.cs @@ -167,13 +167,58 @@ public class InventionsController( }); } + /// GET api/inventions/v1/creatorIds — the distinct + /// creator account ids of every invention placed in a room + /// (GetCreatorIdsOfInventionsInRoom). The watch uses this to + /// resolve which creators it must request invention permissions for + /// before spawning a room's invention-based geometry. Returns + /// List<int> — empty when the room has no tracked + /// inventions. [HttpGet("api/inventions/v1/creatorIds")] - public IActionResult CreatorIdsInRoom([FromQuery] long roomId) + public async Task CreatorIdsInRoom([FromQuery] long roomId) { - // We don't yet track per-room invention placements; return - // empty list (correct shape: List). - _ = roomId; - return Ok(Array.Empty()); + var inventions = await ResolveRoomInventionsAsync(roomId); + var creatorIds = inventions + .Select(i => (int)i.CreatorPlayerId) + .Distinct() + .ToList(); + return Ok(creatorIds); + } + + /// Resolve the inventions that belong to a room. Direct + /// matches are inventions whose + /// equals the room's + /// local id (RR-Originals, dorms, anything created in-place). + /// Zip-imported rooms instead carry each invention's ORIGINAL Rec + /// Room CreationRoomId, which won't equal our local id and can't be + /// reverse-resolved without an OriginalRoomId column; we approximate + /// that cluster as the most-frequent non-local CreationRoomId among + /// the room creator's inventions. The two sets are unioned so a room + /// with both kinds resolves fully. + private async Task> ResolveRoomInventionsAsync(long roomId) + { + var creatorId = await db.Rooms + .Where(r => r.Id == roomId) + .Select(r => (long?)r.CreatorPlayerId) + .FirstOrDefaultAsync(); + + long? importedRoomId = null; + if (creatorId is long creator) + { + importedRoomId = await db.Inventions + .Where(i => !i.IsDeleted && i.CreatorPlayerId == creator + && i.CreationRoomId != null && i.CreationRoomId != roomId) + .GroupBy(i => i.CreationRoomId) + .OrderByDescending(g => g.Count()) + .Select(g => g.Key) + .FirstOrDefaultAsync(); + } + + return await db.Inventions + .Where(i => !i.IsDeleted + && (i.CreationRoomId == roomId + || (importedRoomId != null && i.CreationRoomId == importedRoomId))) + .ToListAsync(); } /// GET api/inventions/v1/fulllineageowner — @@ -225,46 +270,7 @@ public class InventionsController( [HttpGet("api/inventions/v1/room")] public async Task ByRoom([FromQuery] long id) { - // The watch sends our local room id, but zip-imported inventions - // carry the ORIGINAL Rec Room room id in CreationRoomId (the - // importer preserves it so cross-zip references survive). Match - // by name first to find any candidate original ids, then union - // the lookup. For dorm/RR-Original rooms (CreatorPlayerId=1, - // never imported), CreationRoomId equals the local id directly. - var roomName = await db.Rooms - .Where(r => r.Id == id) - .Select(r => r.Name) - .FirstOrDefaultAsync(); - var inventions = await db.Inventions - .Where(i => !i.IsDeleted - && (i.CreationRoomId == id - // Imported rooms: the invention has CreationRoomId = - // the original Rec Room id. We can't reverse-resolve - // without an OriginalRoomId column, so use - // ReplicationId-based name match: every invention - // imported alongside this room has CreatorPlayerId - // matching the room creator AND CreationRoomId set - // to a non-zero value that isn't an RR-Original. - // Loose match — accept any invention whose creator - // is the room creator and CreationRoomId is "the same - // remote id" for that room. We approximate by - // looking at the most-frequent CreationRoomId among - // inventions whose creator matches the room creator - // and joining on that. - || (roomName != null && db.Inventions - .Where(j => !j.IsDeleted - && j.CreatorPlayerId == db.Rooms - .Where(r => r.Id == id) - .Select(r => r.CreatorPlayerId) - .FirstOrDefault() - && j.CreationRoomId != null - && j.CreationRoomId != id) - .GroupBy(j => j.CreationRoomId) - .OrderByDescending(g => g.Count()) - .Select(g => g.Key) - .FirstOrDefault() == i.CreationRoomId) - )) - .ToListAsync(); + var inventions = await ResolveRoomInventionsAsync(id); return Ok(inventions.Select(ToWire).ToList()); } diff --git a/DorkNet.Server/Data/url-coverage.md b/DorkNet.Server/Data/url-coverage.md index 244a82f..07bb0e6 100644 --- a/DorkNet.Server/Data/url-coverage.md +++ b/DorkNet.Server/Data/url-coverage.md @@ -140,7 +140,7 @@ Note: counts are approximate because several client URLs (`api/avatar/v3/saved`, | POST | `api/inventions/v1/batch` | REAL | `InventionsController.Batch` — bulk-fetch inventions by id list, returns multi-row payload | | GET | `api/inventions/v1/details` | REAL | `InventionsController.Details` — extended invention payload with version/creator metadata | | GET | `api/inventions/v1/versions` | REAL | `InventionsController.Versions` — list of `InventionVersionEntity` rows for an invention | -| GET | `api/inventions/v1/creatorIds` | REAL | `InventionsController.CreatorIds` — list of distinct creator player ids over the matching invention set | +| GET | `api/inventions/v1/creatorIds` | REAL | `InventionsController.CreatorIdsInRoom` — distinct creator account ids of the inventions placed in the room, resolved via `InventionEntity.CreationRoomId` (direct + imported-cluster, shared with `/room`). `List` | | GET | `api/inventions/v1/tagfilters` | REAL | `InventionsController.TagFilters` — returns the curated tag-filter set (currently empty list, but registered as a real handler) | | GET | `api/inventions/v1/delete` | REAL | `InventionsController.Delete` — soft-deletes an invention the caller owns | | GET | `api/inventions/v2/publish` | REAL | `InventionsController.PublishV2` — flips `IsPublished=true`, sets `FirstPublishedAt` |