diff --git a/src/github.com/matrix-org/dendrite/clientapi/readers/memberships.go b/src/github.com/matrix-org/dendrite/clientapi/readers/memberships.go index c734e3b34..530dbe73a 100644 --- a/src/github.com/matrix-org/dendrite/clientapi/readers/memberships.go +++ b/src/github.com/matrix-org/dendrite/clientapi/readers/memberships.go @@ -23,18 +23,24 @@ import ( "github.com/matrix-org/dendrite/clientapi/jsonerror" "github.com/matrix-org/dendrite/common/config" "github.com/matrix-org/dendrite/roomserver/api" + "github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/util" ) +type response struct { + Chunk []gomatrixserverlib.ClientEvent `json:"chunk"` +} + // GetMemberships implements GET /rooms/{roomId}/members func GetMemberships( - req *http.Request, device *authtypes.Device, roomID string, + req *http.Request, device *authtypes.Device, roomID string, joinedOnly bool, accountDB *accounts.Database, cfg config.Dendrite, queryAPI api.RoomserverQueryAPI, ) util.JSONResponse { queryReq := api.QueryMembershipsForRoomRequest{ - RoomID: roomID, - Sender: device.UserID, + JoinedOnly: joinedOnly, + RoomID: roomID, + Sender: device.UserID, } var queryRes api.QueryMembershipsForRoomResponse if err := queryAPI.QueryMembershipsForRoom(&queryReq, &queryRes); err != nil { @@ -50,6 +56,6 @@ func GetMemberships( return util.JSONResponse{ Code: 200, - JSON: queryRes.JoinEvents, + JSON: response{queryRes.JoinEvents}, } } 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 32301c784..7a1078f18 100644 --- a/src/github.com/matrix-org/dendrite/clientapi/routing/routing.go +++ b/src/github.com/matrix-org/dendrite/clientapi/routing/routing.go @@ -302,7 +302,14 @@ func Setup( r0mux.Handle("/rooms/{roomID}/members", common.MakeAuthAPI("rooms_members", deviceDB, func(req *http.Request, device *authtypes.Device) util.JSONResponse { vars := mux.Vars(req) - return readers.GetMemberships(req, device, vars["roomID"], accountDB, cfg, queryAPI) + return readers.GetMemberships(req, device, vars["roomID"], false, accountDB, cfg, queryAPI) + }), + ) + + r0mux.Handle("/rooms/{roomID}/joined_members", + common.MakeAuthAPI("rooms_members", deviceDB, func(req *http.Request, device *authtypes.Device) util.JSONResponse { + vars := mux.Vars(req) + return readers.GetMemberships(req, device, vars["roomID"], true, accountDB, cfg, queryAPI) }), ) 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 e9573d1b4..c26955ab1 100644 --- a/src/github.com/matrix-org/dendrite/roomserver/api/query.go +++ b/src/github.com/matrix-org/dendrite/roomserver/api/query.go @@ -102,6 +102,8 @@ type QueryEventsByIDResponse struct { // QueryMembershipsForRoomRequest is a request to QueryMembershipsForRoom type QueryMembershipsForRoomRequest struct { + // If true, only returns the membership events of "join" membership + JoinedOnly bool `json:"joined_only"` // ID of the room to fetch memberships from RoomID string `json:"room_id"` // ID of the user sending the request 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 a4eccb59f..cbbfa63a3 100644 --- a/src/github.com/matrix-org/dendrite/roomserver/query/query.go +++ b/src/github.com/matrix-org/dendrite/roomserver/query/query.go @@ -40,13 +40,20 @@ type RoomserverQueryAPIDatabase interface { // Look up the numeric IDs for a list of events. // Returns an error if there was a problem talking to the database. EventNIDs(eventIDs []string) (map[string]types.EventNID, error) - // Look up the join events for all members in a room as requested by a given - // user. If the user is currently in the room, returns the room's current - // members, if not returns an empty array (TODO: Fix it) - // If the user requesting the list of members has never been in the room, - // returns nil. - // If there was an issue retrieving the events, returns an error. - GetMembershipEvents(roomNID types.RoomNID, requestSenderUserID string) (events []types.Event, err error) + // Lookup the event IDs for a batch of event numeric IDs. + // Returns an error if the retrieval went wrong. + EventIDs(eventNIDs []types.EventNID) (map[types.EventNID]string, error) + // Lookup the membership of a given user in a given room. + // Returns the numeric ID of the latest membership event sent from this user + // in this room, along a boolean set to true if the user is still in this room, + // false if not. + // Returns an error if there was a problem talking to the database. + GetMembership(roomNID types.RoomNID, requestSenderUserID string) (membershipEventNID types.EventNID, stillInRoom bool, err error) + // Lookup the membership event numeric IDs for all user that are or have + // been members of a given room. Only lookup events of "join" membership if + // joinOnly is set to true. + // Returns an error if there was a problem talking to the database. + GetMembershipEventNIDsForRoom(roomNID types.RoomNID, joinOnly bool) ([]types.EventNID, 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. @@ -194,12 +201,12 @@ func (r *RoomserverQueryAPI) QueryMembershipsForRoom( return err } - events, err := r.DB.GetMembershipEvents(roomNID, request.Sender) + membershipEventNID, stillInRoom, err := r.DB.GetMembership(roomNID, request.Sender) if err != nil { return nil } - if events == nil { + if membershipEventNID == 0 { response.HasBeenInRoom = false response.JoinEvents = nil return nil @@ -207,6 +214,24 @@ func (r *RoomserverQueryAPI) QueryMembershipsForRoom( response.HasBeenInRoom = true response.JoinEvents = []gomatrixserverlib.ClientEvent{} + + var events []types.Event + if stillInRoom { + var eventNIDs []types.EventNID + eventNIDs, err = r.DB.GetMembershipEventNIDsForRoom(roomNID, request.JoinedOnly) + if err != nil { + return err + } + + events, err = r.DB.Events(eventNIDs) + } else { + events, err = r.getMembershipsBeforeEventNID(membershipEventNID, request.JoinedOnly) + } + + if err != nil { + return err + } + for _, event := range events { clientEvent := gomatrixserverlib.ToClientEvent(event.Event, gomatrixserverlib.FormatAll) response.JoinEvents = append(response.JoinEvents, clientEvent) @@ -215,6 +240,63 @@ func (r *RoomserverQueryAPI) QueryMembershipsForRoom( return nil } +// getMembershipsBeforeEventNID takes the numeric ID of an event and fetches the state +// of the event's room as it was when this event was fired, then filters the state events to +// only keep the "m.room.member" events with a "join" membership. These events are returned. +// Returns an error if there was an issue fetching the events. +func (r *RoomserverQueryAPI) getMembershipsBeforeEventNID(eventNID types.EventNID, joinedOnly bool) ([]types.Event, error) { + events := []types.Event{} + // Lookup the event NID + eIDs, err := r.DB.EventIDs([]types.EventNID{eventNID}) + if err != nil { + return nil, err + } + eventIDs := []string{eIDs[eventNID]} + + prevState, err := r.DB.StateAtEventIDs(eventIDs) + if err != nil { + return nil, err + } + + // Fetch the state as it was when this event was fired + stateEntries, err := state.LoadCombinedStateAfterEvents(r.DB, prevState) + if err != nil { + return nil, err + } + + var eventNIDs []types.EventNID + for _, entry := range stateEntries { + // Filter the events to retrieve to only keep the membership events + if entry.EventTypeNID == types.MRoomMemberNID { + eventNIDs = append(eventNIDs, entry.EventNID) + } + } + + // Get all of the events in this state + stateEvents, err := r.DB.Events(eventNIDs) + if err != nil { + return nil, err + } + + if !joinedOnly { + return stateEvents, nil + } + + // Filter the events to only keep the "join" membership events + for _, event := range stateEvents { + membership, err := event.Membership() + if err != nil { + return nil, err + } + + if membership == "join" { + events = append(events, event) + } + } + + return events, nil +} + // QueryInvitesForUser implements api.RoomserverQueryAPI func (r *RoomserverQueryAPI) QueryInvitesForUser( request *api.QueryInvitesForUserRequest, 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 6edc7a528..5db772aad 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 @@ -77,7 +77,7 @@ const selectMembershipsFromRoomAndMembershipSQL = "" + " WHERE room_nid = $1 AND membership_nid = $2" const selectMembershipsFromRoomSQL = "" + - "SELECT membership_nid, event_nid FROM roomserver_membership" + + "SELECT event_nid FROM roomserver_membership" + " WHERE room_nid = $1" const selectMembershipForUpdateSQL = "" + @@ -140,20 +140,18 @@ func (s *membershipStatements) selectMembershipFromRoomAndTarget( func (s *membershipStatements) selectMembershipsFromRoom( roomNID types.RoomNID, -) (eventNIDs map[types.EventNID]membershipState, err error) { +) (eventNIDs []types.EventNID, err error) { rows, err := s.selectMembershipsFromRoomStmt.Query(roomNID) if err != nil { return } - eventNIDs = make(map[types.EventNID]membershipState) for rows.Next() { var eNID types.EventNID - var membership membershipState - if err = rows.Scan(&membership, &eNID); err != nil { + if err = rows.Scan(&eNID); err != nil { return } - eventNIDs[eNID] = membership + eventNIDs = append(eventNIDs, eNID) } return } 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 7e90aebbd..8a0523e56 100644 --- a/src/github.com/matrix-org/dendrite/roomserver/storage/storage.go +++ b/src/github.com/matrix-org/dendrite/roomserver/storage/storage.go @@ -552,8 +552,8 @@ func (u *membershipUpdater) SetToLeave(senderUserID string, eventID string) ([]s return inviteEventIDs, nil } -// GetMembershipEvents implements query.RoomserverQueryAPIDB -func (d *Database) GetMembershipEvents(roomNID types.RoomNID, requestSenderUserID string) (events []types.Event, err error) { +// GetMembership implements query.RoomserverQueryAPIDB +func (d *Database) GetMembership(roomNID types.RoomNID, requestSenderUserID string) (membershipEventNID types.EventNID, stillInRoom bool, err error) { txn, err := d.db.Begin() if err != nil { return @@ -565,36 +565,24 @@ func (d *Database) GetMembershipEvents(roomNID types.RoomNID, requestSenderUserI return } - _, senderMembership, err := d.statements.selectMembershipFromRoomAndTarget(roomNID, requestSenderUserNID) + senderMembershipEventNID, senderMembership, err := d.statements.selectMembershipFromRoomAndTarget(roomNID, requestSenderUserNID) if err == sql.ErrNoRows { // The user has never been a member of that room - return nil, nil + return 0, false, nil } else if err != nil { return } - if senderMembership == membershipStateJoin { - // The user is still in the room: Send the current list of joined members - var joinEventNIDs []types.EventNID - joinEventNIDs, err = d.statements.selectMembershipsFromRoomAndMembership(roomNID, membershipStateJoin) - if err != nil { - return nil, err - } + return senderMembershipEventNID, senderMembership == membershipStateJoin, nil +} - events, err = d.Events(joinEventNIDs) - } else { - // The user isn't in the room anymore - // TODO: Send the list of joined member as it was when the user left - // We cannot do this using only the memberships database, as it - // only stores the latest join event NID for a given target user. - // The solution would be to build the state of a room after before - // the leave event and extract a members list from it. - // For now, we return an empty slice so we know the user has been - // in the room before. - events = []types.Event{} +// GetMembershipEventNIDsForRoom implements query.RoomserverQueryAPIDB +func (d *Database) GetMembershipEventNIDsForRoom(roomNID types.RoomNID, joinOnly bool) ([]types.EventNID, error) { + if joinOnly { + return d.statements.selectMembershipsFromRoomAndMembership(roomNID, membershipStateJoin) } - return + return d.statements.selectMembershipsFromRoom(roomNID) } type transaction struct {