diff --git a/src/github.com/matrix-org/dendrite/clientapi/routing/memberships.go b/src/github.com/matrix-org/dendrite/clientapi/routing/memberships.go index 5b8903287..f2785ff5c 100644 --- a/src/github.com/matrix-org/dendrite/clientapi/routing/memberships.go +++ b/src/github.com/matrix-org/dendrite/clientapi/routing/memberships.go @@ -30,6 +30,10 @@ type response struct { Chunk []gomatrixserverlib.ClientEvent `json:"chunk"` } +type joinedResponse struct { + JoinedRooms []string `json:"joined_rooms,flow"` +} + // GetMemberships implements GET /rooms/{roomId}/members func GetMemberships( req *http.Request, device *authtypes.Device, roomID string, joinedOnly bool, @@ -58,3 +62,24 @@ func GetMemberships( JSON: response{queryRes.JoinEvents}, } } + +// GetJoinedRooms implements GET /joined_rooms +func GetJoinedRooms( + req *http.Request, device *authtypes.Device, + _ config.Dendrite, + queryAPI api.RoomserverQueryAPI, +) util.JSONResponse { + queryReq := api.QueryRoomsForUserRequest{ + UserID: device.UserID, + Membership:"join", + } + var queryRes api.QueryRoomsForUserResponse + if err := queryAPI.QueryRoomsForUser(req.Context(), &queryReq, &queryRes); err != nil { + return httputil.LogThenError(req, err) + } + + return util.JSONResponse{ + Code: http.StatusOK, + JSON: joinedResponse{queryRes.RoomIDs}, + } +} diff --git a/src/github.com/matrix-org/dendrite/clientapi/routing/routing.go b/src/github.com/matrix-org/dendrite/clientapi/routing/routing.go index ee593c682..9906a2e03 100644 --- a/src/github.com/matrix-org/dendrite/clientapi/routing/routing.go +++ b/src/github.com/matrix-org/dendrite/clientapi/routing/routing.go @@ -344,6 +344,12 @@ func Setup( }), ).Methods(http.MethodGet, http.MethodOptions) + r0mux.Handle("/joined_rooms", + common.MakeAuthAPI("joined_rooms", authData, func(req *http.Request, device *authtypes.Device) util.JSONResponse { + return GetJoinedRooms(req, device, cfg, queryAPI) + }), + ).Methods(http.MethodGet, http.MethodOptions) + r0mux.Handle("/rooms/{roomID}/read_markers", common.MakeExternalAPI("rooms_read_markers", func(req *http.Request) util.JSONResponse { // TODO: return the read_markers. diff --git a/src/github.com/matrix-org/dendrite/roomserver/api/query.go b/src/github.com/matrix-org/dendrite/roomserver/api/query.go index 8c375321a..807831270 100644 --- a/src/github.com/matrix-org/dendrite/roomserver/api/query.go +++ b/src/github.com/matrix-org/dendrite/roomserver/api/query.go @@ -138,6 +138,20 @@ type QueryMembershipsForRoomResponse struct { HasBeenInRoom bool `json:"has_been_in_room"` } +// QueryRoomsForUserRequest is a request to QueryRoomsForUser +type QueryRoomsForUserRequest struct { + // The ID of the user sending the request + UserID string `json:"user_id"` + // The membership state to be queried for + Membership string `json:"membership"` +} + +// QueryRoomsForUserResponse is a response to QueryRoomsForUser +type QueryRoomsForUserResponse struct { + // The room IDs that match the user ID and membership requested + RoomIDs []string `json:"room_ids,flow"` +} + // QueryInvitesForUserRequest is a request to QueryInvitesForUser type QueryInvitesForUserRequest struct { // The room ID to look up invites in. @@ -251,6 +265,13 @@ type RoomserverQueryAPI interface { response *QueryMembershipsForRoomResponse, ) error + // Query a list of rooms the user has membership in + QueryRoomsForUser( + ctx context.Context, + request *QueryRoomsForUserRequest, + response *QueryRoomsForUserResponse, + ) error + // Query a list of invite event senders for a user in a room. QueryInvitesForUser( ctx context.Context, @@ -297,6 +318,9 @@ const RoomserverQueryMembershipForUserPath = "/api/roomserver/queryMembershipFor // RoomserverQueryMembershipsForRoomPath is the HTTP path for the QueryMembershipsForRoom API const RoomserverQueryMembershipsForRoomPath = "/api/roomserver/queryMembershipsForRoom" +// RoomserverQueryRoomsForUserPath is the HTTP path for the QueryRoomsForUser API +const RoomserverQueryRoomsForUserPath = "/api/roomserver/queryRoomsForUser" + // RoomserverQueryInvitesForUserPath is the HTTP path for the QueryInvitesForUser API const RoomserverQueryInvitesForUserPath = "/api/roomserver/queryInvitesForUser" @@ -388,6 +412,18 @@ func (h *httpRoomserverQueryAPI) QueryMembershipsForRoom( return commonHTTP.PostJSON(ctx, span, h.httpClient, apiURL, request, response) } +func (h *httpRoomserverQueryAPI) QueryRoomsForUser( + ctx context.Context, + request *QueryRoomsForUserRequest, + response *QueryRoomsForUserResponse, +) error { + span, ctx := opentracing.StartSpanFromContext(ctx, "QueryRoomsForUser") + defer span.Finish() + + apiURL := h.roomserverURL + RoomserverQueryRoomsForUserPath + return commonHTTP.PostJSON(ctx, span, h.httpClient, apiURL, request, response) +} + // QueryInvitesForUser implements RoomserverQueryAPI func (h *httpRoomserverQueryAPI) QueryInvitesForUser( ctx context.Context, diff --git a/src/github.com/matrix-org/dendrite/roomserver/query/query.go b/src/github.com/matrix-org/dendrite/roomserver/query/query.go index 836edcef2..387bf1389 100644 --- a/src/github.com/matrix-org/dendrite/roomserver/query/query.go +++ b/src/github.com/matrix-org/dendrite/roomserver/query/query.go @@ -45,6 +45,9 @@ type RoomserverQueryAPIDatabase interface { // Returns 0 if the room doesn't exists. // Returns an error if there was a problem talking to the database. RoomNID(ctx context.Context, roomID string) (types.RoomNID, error) + // Look up the room IDs corresponding to the room numeric IDs + // Returns an error if there was a problem talking to the database. + GetRoomIDs(ctx context.Context, roomNIDs []types.RoomNID) ([]string, error) // Look up event references for the latest events in the room and the current state snapshot. // Returns the latest events, the current state and the maximum depth of the latest events plus 1. // Returns an error if there was a problem talking to the database. @@ -72,6 +75,11 @@ type RoomserverQueryAPIDatabase interface { GetMembershipEventNIDsForRoom( ctx context.Context, roomNID types.RoomNID, joinOnly bool, ) ([]types.EventNID, error) + // Lookup the rooms for which a user has a particular membership state. + // Returns an error if there was a problem talkign to the database. + GetRoomsForUserMembership( + ctx context.Context, userNID types.EventStateKeyNID, membership string, + ) ([]types.RoomNID, error) // Look up the active invites targeting a user in a room and return the // numeric state key IDs for the user IDs who sent them. // Returns an error if there was a problem talking to the database. @@ -367,6 +375,32 @@ func (r *RoomserverQueryAPI) getMembershipsBeforeEventNID( return events, nil } +// QueryRoomsForUser implements api.RoomserverQueryAPI +func (r *RoomserverQueryAPI) QueryRoomsForUser( + ctx context.Context, + request *api.QueryRoomsForUserRequest, + response *api.QueryRoomsForUserResponse, +) error { + targetUserNIDs, err := r.DB.EventStateKeyNIDs(ctx, []string{request.UserID}) + if err != nil { + return err + } + targetUserNID := targetUserNIDs[request.UserID] + + roomNIDs, err := r.DB.GetRoomsForUserMembership(ctx, targetUserNID, request.Membership) + if err != nil { + return err + } + + roomIDs, err := r.DB.GetRoomIDs(ctx, roomNIDs) + if err != nil { + return err + } + + response.RoomIDs = roomIDs + return nil +} + // QueryInvitesForUser implements api.RoomserverQueryAPI func (r *RoomserverQueryAPI) QueryInvitesForUser( ctx context.Context, @@ -652,6 +686,20 @@ func (r *RoomserverQueryAPI) SetupHTTP(servMux *http.ServeMux) { return util.JSONResponse{Code: http.StatusOK, JSON: &response} }), ) + servMux.Handle( + api.RoomserverQueryRoomsForUserPath, + common.MakeInternalAPI("queryRoomsForUser", func(req *http.Request) util.JSONResponse { + var request api.QueryRoomsForUserRequest + var response api.QueryRoomsForUserResponse + if err := json.NewDecoder(req.Body).Decode(&request); err != nil { + return util.ErrorResponse(err) + } + if err := r.QueryRoomsForUser(req.Context(), &request, &response); err != nil { + return util.ErrorResponse(err) + } + return util.JSONResponse{Code: http.StatusOK, JSON: &response} + }), + ) servMux.Handle( api.RoomserverQueryInvitesForUserPath, common.MakeInternalAPI("queryInvitesForUser", func(req *http.Request) util.JSONResponse { diff --git a/src/github.com/matrix-org/dendrite/roomserver/storage/membership_table.go b/src/github.com/matrix-org/dendrite/roomserver/storage/membership_table.go index 88a9ed725..341fb0ffe 100644 --- a/src/github.com/matrix-org/dendrite/roomserver/storage/membership_table.go +++ b/src/github.com/matrix-org/dendrite/roomserver/storage/membership_table.go @@ -89,12 +89,17 @@ const updateMembershipSQL = "" + "UPDATE roomserver_membership SET sender_nid = $3, membership_nid = $4, event_nid = $5" + " WHERE room_nid = $1 AND target_nid = $2" +const selectRoomsForUserMembershipSQL = "" + + "SELECT room_nid FROM roomserver_membership" + + " WHERE target_nid = $1 AND membership_nid = $2" + type membershipStatements struct { insertMembershipStmt *sql.Stmt selectMembershipForUpdateStmt *sql.Stmt selectMembershipFromRoomAndTargetStmt *sql.Stmt selectMembershipsFromRoomAndMembershipStmt *sql.Stmt selectMembershipsFromRoomStmt *sql.Stmt + selectRoomsForUserMembershipStmt *sql.Stmt updateMembershipStmt *sql.Stmt } @@ -110,6 +115,7 @@ func (s *membershipStatements) prepare(db *sql.DB) (err error) { {&s.selectMembershipFromRoomAndTargetStmt, selectMembershipFromRoomAndTargetSQL}, {&s.selectMembershipsFromRoomAndMembershipStmt, selectMembershipsFromRoomAndMembershipSQL}, {&s.selectMembershipsFromRoomStmt, selectMembershipsFromRoomSQL}, + {&s.selectRoomsForUserMembershipStmt, selectRoomsForUserMembershipSQL}, {&s.updateMembershipStmt, updateMembershipSQL}, }.prepare(db) } @@ -180,6 +186,26 @@ func (s *membershipStatements) selectMembershipsFromRoomAndMembership( return } +func (s *membershipStatements) selectRoomsForUserMembership( + ctx context.Context, + targetUserNID types.EventStateKeyNID, membership membershipState, +) (roomNIDs []types.RoomNID, err error) { + stmt := s.selectRoomsForUserMembershipStmt + rows, err := stmt.QueryContext(ctx, targetUserNID, membership) + if err != nil { + return + } + + for rows.Next() { + var rNID types.RoomNID + if err = rows.Scan(&rNID); err != nil { + return + } + roomNIDs = append(roomNIDs, rNID) + } + return +} + func (s *membershipStatements) updateMembership( ctx context.Context, txn *sql.Tx, roomNID types.RoomNID, targetUserNID types.EventStateKeyNID, diff --git a/src/github.com/matrix-org/dendrite/roomserver/storage/rooms_table.go b/src/github.com/matrix-org/dendrite/roomserver/storage/rooms_table.go index 64193ffee..6d86e0ddf 100644 --- a/src/github.com/matrix-org/dendrite/roomserver/storage/rooms_table.go +++ b/src/github.com/matrix-org/dendrite/roomserver/storage/rooms_table.go @@ -51,6 +51,9 @@ const insertRoomNIDSQL = "" + const selectRoomNIDSQL = "" + "SELECT room_nid FROM roomserver_rooms WHERE room_id = $1" +const selectRoomIDSQL = "" + + "SELECT room_id FROM roomserver_rooms WHERE room_nid = $1" + const selectLatestEventNIDsSQL = "" + "SELECT latest_event_nids, state_snapshot_nid FROM roomserver_rooms WHERE room_nid = $1" @@ -63,6 +66,7 @@ const updateLatestEventNIDsSQL = "" + type roomStatements struct { insertRoomNIDStmt *sql.Stmt selectRoomNIDStmt *sql.Stmt + selectRoomIDStmt *sql.Stmt selectLatestEventNIDsStmt *sql.Stmt selectLatestEventNIDsForUpdateStmt *sql.Stmt updateLatestEventNIDsStmt *sql.Stmt @@ -76,6 +80,7 @@ func (s *roomStatements) prepare(db *sql.DB) (err error) { return statementList{ {&s.insertRoomNIDStmt, insertRoomNIDSQL}, {&s.selectRoomNIDStmt, selectRoomNIDSQL}, + {&s.selectRoomIDStmt, selectRoomIDSQL}, {&s.selectLatestEventNIDsStmt, selectLatestEventNIDsSQL}, {&s.selectLatestEventNIDsForUpdateStmt, selectLatestEventNIDsForUpdateSQL}, {&s.updateLatestEventNIDsStmt, updateLatestEventNIDsSQL}, @@ -100,6 +105,15 @@ func (s *roomStatements) selectRoomNID( return types.RoomNID(roomNID), err } +func (s *roomStatements) selectRoomID( + ctx context.Context, txn *sql.Tx, roomNID types.RoomNID, +) (string, error) { + var roomID string + stmt := common.TxStmt(txn, s.selectRoomIDStmt) + err := stmt.QueryRowContext(ctx, roomNID).Scan(&roomID) + return roomID, err +} + func (s *roomStatements) selectLatestEventNIDs( ctx context.Context, roomNID types.RoomNID, ) ([]types.EventNID, types.StateSnapshotNID, error) { diff --git a/src/github.com/matrix-org/dendrite/roomserver/storage/storage.go b/src/github.com/matrix-org/dendrite/roomserver/storage/storage.go index f6c2fccd4..6f7db82af 100644 --- a/src/github.com/matrix-org/dendrite/roomserver/storage/storage.go +++ b/src/github.com/matrix-org/dendrite/roomserver/storage/storage.go @@ -412,6 +412,21 @@ func (d *Database) RoomNID(ctx context.Context, roomID string) (types.RoomNID, e return roomNID, err } +func (d *Database) GetRoomIDs(ctx context.Context, roomNIDs []types.RoomNID) ([]string, error) { + roomIDs := make([]string, 0) + + for _, nid := range roomNIDs { + roomID, err := d.statements.selectRoomID(ctx, nil, nid) + if err != nil { + return nil, err + } + + roomIDs = append(roomIDs, roomID) + } + + return roomIDs, nil +} + // LatestEventIDs implements query.RoomserverQueryAPIDatabase func (d *Database) LatestEventIDs( ctx context.Context, roomNID types.RoomNID, @@ -431,6 +446,20 @@ func (d *Database) LatestEventIDs( return references, currentStateSnapshotNID, depth, nil } +func (d *Database) GetRoomsForUserMembership( + ctx context.Context, + userNID types.EventStateKeyNID, + membership string, +) (roomNIDs []types.RoomNID, err error) { + membershipNID := membershipStateLeaveOrBan + if membership == "join" { + membershipNID = membershipStateJoin + } else if membership == "invite" { + membershipNID = membershipStateInvite + } + return d.statements.selectRoomsForUserMembership(ctx, userNID, membershipNID) +} + // GetInvitesForUser implements query.RoomserverQueryAPIDatabase func (d *Database) GetInvitesForUser( ctx context.Context,