Membership viewing API when user left the room (#194)

* Implement case where user left the room

* Filter by membership event

* Move the logic from the storage to the query API

* Fix check on state entries iteration

* Remove aliases methods from query API

* Use structure for response to match with the spec

* Remove filtering on /members and implement /joined_members
This commit is contained in:
Brendan Abolivier 2017-08-24 16:00:14 +01:00 committed by Mark Haines
parent fceb027ecc
commit 685e056ab3
6 changed files with 126 additions and 43 deletions

View File

@ -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},
}
}

View File

@ -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)
}),
)

View File

@ -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

View File

@ -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,

View File

@ -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
}

View File

@ -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 {