summary refs log tree commit diff
path: root/ModAS.Server/Controllers/Admin/RoomQueryController.cs
diff options
context:
space:
mode:
Diffstat (limited to 'ModAS.Server/Controllers/Admin/RoomQueryController.cs')
-rw-r--r--ModAS.Server/Controllers/Admin/RoomQueryController.cs135
1 files changed, 135 insertions, 0 deletions
diff --git a/ModAS.Server/Controllers/Admin/RoomQueryController.cs b/ModAS.Server/Controllers/Admin/RoomQueryController.cs
new file mode 100644
index 0000000..a49e5c0
--- /dev/null
+++ b/ModAS.Server/Controllers/Admin/RoomQueryController.cs
@@ -0,0 +1,135 @@
+using System.Collections.Frozen;
+using ArcaneLibs.Extensions;
+using Elastic.Apm;
+using Elastic.Apm.Api;
+using LibMatrix;
+using LibMatrix.EventTypes.Spec.State;
+using LibMatrix.EventTypes.Spec.State.RoomInfo;
+using LibMatrix.Responses.ModAS;
+using LibMatrix.RoomTypes;
+using Microsoft.AspNetCore.Mvc;
+using ModAS.Classes;
+using ModAS.Server.Services;
+using MxApiExtensions.Services;
+
+namespace ModAS.Server.Controllers;
+
+[ApiController]
+public class RoomQueryController(UserProviderService ahsProvider, ModASConfiguration config, RoomStateCacheService stateCacheService, UserProviderService userProviderService) : ControllerBase {
+    [HttpGet("/_matrix/_modas/room_query")]
+    public async IAsyncEnumerable<ModASRoomQueryResult> RoomQuery() {
+        var fetchRoomQueryDataSpan = currentTransaction.StartSpan("fetchRoomQueryData", ApiConstants.TypeApp);
+        List<string> processedRooms = new();
+        var getUsersSpan = currentTransaction.StartSpan("getUsers", ApiConstants.TypeApp);
+        var validUsers = await ahsProvider.GetValidUsers();
+        getUsersSpan.End();
+
+        var collectRoomsSpan = currentTransaction.StartSpan("collectRooms", ApiConstants.TypeApp);
+        // var userRoomLists = validUsers.Values.Select(ahs => ahs.GetJoinedRooms()).ToList();
+        // await Task.WhenAll(userRoomLists);
+        // var rooms = userRoomLists.SelectMany(r => r.Result).DistinctBy(x => x.RoomId).ToFrozenSet();
+        var roomsTasks = validUsers.Values.Select(u => userProviderService.GetUserRoomsCached(u.WhoAmI.UserId)).ToList();
+        await Task.WhenAll(roomsTasks);
+        var rooms = roomsTasks.SelectMany(r => r.Result).DistinctBy(x => x.RoomId).ToFrozenSet();
+        collectRoomsSpan.End();
+
+        var collectRoomStateSpan = currentTransaction.StartSpan("collectRoomState", ApiConstants.TypeApp);
+        //make sure we lock!!!!
+        await stateCacheService.EnsureCachedFromRoomList(rooms);
+        var roomStateTasks = rooms.Select(GetRoomState).ToAsyncEnumerable();
+        var awaitRoomStateSpan = currentTransaction.StartSpan("fetchRoomState", ApiConstants.TypeApp);
+        await foreach (var (room, roomState) in roomStateTasks) {
+            awaitRoomStateSpan.Name = $"awaitRoomState {room.RoomId}";
+            awaitRoomStateSpan.End();
+
+            var filterStateEventsSpan = currentTransaction.StartSpan($"filterStateEvents {room.RoomId}", ApiConstants.TypeApp);
+            var roomMembers = roomState.Where(r => r.Type == RoomMemberEventContent.EventId).ToFrozenSet();
+            var localRoomMembers = roomMembers.Where(x => x.StateKey.EndsWith(':' + config.ServerName)).ToFrozenSet();
+            var nonMemberState = roomState.Where(x => !roomMembers.Contains(x)).ToFrozenSet();
+            filterStateEventsSpan.End();
+            var buildResultSpan = currentTransaction.StartSpan($"buildResult {room.RoomId}", ApiConstants.TypeApp);
+
+            // forgive me for raw json access... attempt at optimisation -emma
+            yield return new ModASRoomQueryResult {
+                RoomId = room.RoomId,
+                StateEvents = roomState.Count,
+                //members
+                TotalMembers = roomMembers.Count,
+                TotalLocalMembers = localRoomMembers.Count,
+                JoinedMembers = roomMembers.Count(x => (x.TypedContent as RoomMemberEventContent)?.Membership == "join"),
+                JoinedLocalMembers = localRoomMembers.Count(x => (x.TypedContent as RoomMemberEventContent)?.Membership == "join"),
+                //-members
+                //creation event
+                Creator = nonMemberState.FirstOrDefault(r => r.Type == RoomCreateEventContent.EventId)?.Sender,
+                Version = nonMemberState.FirstOrDefault(r => r.Type == RoomCreateEventContent.EventId)?.RawContent?["room_version"]?.GetValue<string>(),
+                Type = nonMemberState.FirstOrDefault(r => r.Type == RoomCreateEventContent.EventId)?.RawContent?["type"]?.GetValue<string>(),
+                Federatable = nonMemberState.FirstOrDefault(r => r.Type == RoomCreateEventContent.EventId)?.RawContent?["m.federate"]?.GetValue<bool>() ?? true,
+                //-creation event
+                Name = nonMemberState.FirstOrDefault(r => r.Type == RoomNameEventContent.EventId)?.RawContent?["name"]?.GetValue<string>(),
+                CanonicalAlias = nonMemberState.FirstOrDefault(r => r.Type == RoomCanonicalAliasEventContent.EventId)?.RawContent?["alias"]?.GetValue<string>(),
+                JoinRules = nonMemberState.FirstOrDefault(r => r.Type == RoomJoinRulesEventContent.EventId)?.RawContent?["join_rule"]?.GetValue<string>(),
+                GuestAccess = nonMemberState.FirstOrDefault(r => r.Type == RoomGuestAccessEventContent.EventId)?.RawContent?["guest_access"]?.GetValue<string>(),
+                HistoryVisibility =
+                    nonMemberState.FirstOrDefault(r => r.Type == RoomHistoryVisibilityEventContent.EventId)?.RawContent?["history_visibility"]?.GetValue<string>(),
+                Public = nonMemberState.FirstOrDefault(r => r.Type == RoomJoinRulesEventContent.EventId)?.RawContent?["join_rule"]?.GetValue<string>() == "public",
+                Encryption = nonMemberState.FirstOrDefault(r => r.Type == RoomEncryptionEventContent.EventId)?.RawContent?["algorithm"]?.GetValue<string>(),
+                AvatarUrl = nonMemberState.FirstOrDefault(r => r.Type == RoomAvatarEventContent.EventId)?.RawContent?["url"]?.GetValue<string>(),
+                RoomTopic = nonMemberState.FirstOrDefault(r => r.Type == RoomTopicEventContent.EventId)?.RawContent?["topic"]?.GetValue<string>()
+            };
+            buildResultSpan.End();
+            awaitRoomStateSpan = currentTransaction.StartSpan("fetchRoomState", ApiConstants.TypeApp);
+        }
+
+        collectRoomStateSpan.End();
+        fetchRoomQueryDataSpan.End();
+    }
+
+    [HttpPost("/_matrix/_modas/room_query")]
+    public async IAsyncEnumerable<ModASRoomQueryResult> RoomQuery([FromBody] RoomQueryFilter request) {
+        await foreach (var room in RoomQuery()) {
+            if (!string.IsNullOrWhiteSpace(request.RoomIdContains) && !room.RoomId.Contains(request.RoomIdContains)) continue;
+
+            if (!string.IsNullOrWhiteSpace(request.NameContains) && room.Name?.Contains(request.NameContains) != true) continue;
+
+            if (!string.IsNullOrWhiteSpace(request.CanonicalAliasContains) && room.CanonicalAlias?.Contains(request.CanonicalAliasContains) != true) continue;
+
+            if (!string.IsNullOrWhiteSpace(request.VersionContains) && room.Version?.Contains(request.VersionContains) != true) continue;
+
+            if (!string.IsNullOrWhiteSpace(request.CreatorContains) && room.Creator?.Contains(request.CreatorContains) != true) continue;
+
+            if (!string.IsNullOrWhiteSpace(request.EncryptionContains) && room.Encryption?.Contains(request.EncryptionContains) != true) continue;
+
+            if (!string.IsNullOrWhiteSpace(request.JoinRulesContains) && room.JoinRules?.Contains(request.JoinRulesContains) != true) continue;
+
+            if (!string.IsNullOrWhiteSpace(request.GuestAccessContains) && room.GuestAccess?.Contains(request.GuestAccessContains) != true) continue;
+
+            if (!string.IsNullOrWhiteSpace(request.HistoryVisibilityContains) && room.HistoryVisibility?.Contains(request.HistoryVisibilityContains) != true) continue;
+
+            if (!string.IsNullOrWhiteSpace(request.AvatarUrlContains) && room.AvatarUrl?.Contains(request.AvatarUrlContains) != true) continue;
+
+            if (!string.IsNullOrWhiteSpace(request.RoomTopicContains) && room.RoomTopic?.Contains(request.RoomTopicContains) != true) continue;
+
+            if (request.JoinedMembersMin.HasValue && room.JoinedMembers < request.JoinedMembersMin.Value) continue;
+            if (request.JoinedMembersMax.HasValue && room.JoinedMembers > request.JoinedMembersMax.Value) continue;
+
+            if (request.JoinedLocalMembersMin.HasValue && room.JoinedLocalMembers < request.JoinedLocalMembersMin.Value) continue;
+            if (request.JoinedLocalMembersMax.HasValue && room.JoinedLocalMembers > request.JoinedLocalMembersMax.Value) continue;
+
+            if (request.StateEventsMin.HasValue && room.StateEvents < request.StateEventsMin.Value) continue;
+            if (request.StateEventsMax.HasValue && room.StateEvents > request.StateEventsMax.Value) continue;
+
+            if (request.IsFederatable.HasValue && room.Federatable != request.IsFederatable.Value) continue;
+
+            if (request.IsPublic.HasValue && room.Public != request.IsPublic.Value) continue;
+
+            yield return room;
+        }
+    }
+
+    private async Task<KeyValuePair<GenericRoom, FrozenSet<StateEventResponse>>> GetRoomState(GenericRoom room) {
+        var roomState = await stateCacheService.GetRoomState(room.RoomId, room);
+        return new(room, roomState);
+    }
+
+    private static ITransaction currentTransaction => Agent.Tracer.CurrentTransaction;
+}
\ No newline at end of file