@@ -211,6 +211,33 @@ impl SpaceService {
211211 spaces
212212 }
213213
214+ /// Returns a flattened list containing all joined spaces where the
215+ /// user has permission to send `m.space.child` state events.
216+ ///
217+ /// Note: Unlike [`Self::joined_spaces()`], this method does not recompute
218+ /// graph, nor does it notify subscribers about changes.
219+ pub async fn all_editable_joined_spaces ( & self ) -> Vec < SpaceRoom > {
220+ let Some ( user_id) = self . client . user_id ( ) else {
221+ return vec ! [ ] ;
222+ } ;
223+
224+ let graph = & self . space_state . lock ( ) . await . graph ;
225+ let rooms = self . client . joined_space_rooms ( ) ;
226+
227+ let mut editable_spaces = Vec :: new ( ) ;
228+ for room in & rooms {
229+ if let Ok ( power_levels) = room. power_levels ( ) . await
230+ && power_levels. user_can_send_state ( user_id, StateEventType :: SpaceChild )
231+ {
232+ let room_id = room. room_id ( ) ;
233+ editable_spaces
234+ . push ( SpaceRoom :: new_from_known ( room, graph. children_of ( room_id) . len ( ) as u64 ) ) ;
235+ }
236+ }
237+
238+ editable_spaces
239+ }
240+
214241 /// Returns a `SpaceRoomList` for the given space ID.
215242 pub async fn space_room_list ( & self , space_id : OwnedRoomId ) -> SpaceRoomList {
216243 SpaceRoomList :: new ( self . client . clone ( ) , space_id) . await
@@ -738,6 +765,74 @@ mod tests {
738765 ) ;
739766 }
740767
768+ #[ async_test]
769+ async fn test_all_editable_spaces ( ) {
770+ // Given a space hierarchy where the user is admin of some spaces and subspaces.
771+ let server = MatrixMockServer :: new ( ) . await ;
772+ let client = server. client_builder ( ) . build ( ) . await ;
773+ let user_id = client. user_id ( ) . unwrap ( ) ;
774+ let factory = EventFactory :: new ( ) ;
775+
776+ server. mock_room_state_encryption ( ) . plain ( ) . mount ( ) . await ;
777+
778+ let admin_space_id = room_id ! ( "!admin_space:example.org" ) ;
779+ let admin_subspace_id = room_id ! ( "!admin_subspace:example.org" ) ;
780+ let regular_space_id = room_id ! ( "!regular_space:example.org" ) ;
781+ let regular_subspace_id = room_id ! ( "!regular_subspace:example.org" ) ;
782+
783+ add_space_rooms (
784+ vec ! [
785+ MockSpaceRoomParameters {
786+ room_id: admin_space_id,
787+ order: None ,
788+ parents: vec![ ] ,
789+ children: vec![ regular_subspace_id] ,
790+ power_level: Some ( 100 ) ,
791+ } ,
792+ MockSpaceRoomParameters {
793+ room_id: admin_subspace_id,
794+ order: None ,
795+ parents: vec![ regular_space_id] ,
796+ children: vec![ ] ,
797+ power_level: Some ( 100 ) ,
798+ } ,
799+ MockSpaceRoomParameters {
800+ room_id: regular_space_id,
801+ order: None ,
802+ parents: vec![ ] ,
803+ children: vec![ admin_subspace_id] ,
804+ power_level: Some ( 0 ) ,
805+ } ,
806+ MockSpaceRoomParameters {
807+ room_id: regular_subspace_id,
808+ order: None ,
809+ parents: vec![ admin_space_id] ,
810+ children: vec![ ] ,
811+ power_level: Some ( 0 ) ,
812+ } ,
813+ ] ,
814+ & client,
815+ & server,
816+ & factory,
817+ user_id,
818+ )
819+ . await ;
820+
821+ let space_service = SpaceService :: new ( client. clone ( ) ) ;
822+
823+ // Wait for the space hierarchy to register.
824+ _ = space_service. joined_spaces ( ) . await ;
825+
826+ // When retrieving all editable joined spaces.
827+ let editable_spaces = space_service. all_editable_joined_spaces ( ) . await ;
828+
829+ // Then only the spaces where the user is admin are returned.
830+ assert_eq ! (
831+ editable_spaces. iter( ) . map( |room| room. room_id. to_owned( ) ) . collect:: <Vec <_>>( ) ,
832+ vec![ admin_space_id. to_owned( ) , admin_subspace_id. to_owned( ) ]
833+ ) ;
834+ }
835+
741836 #[ async_test]
742837 async fn test_joined_parents_of_child ( ) {
743838 // Given a space with three parent spaces, two of which are joined.
0 commit comments