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_membersmain
parent
fceb027ecc
commit
685e056ab3
|
@ -23,16 +23,22 @@ import (
|
||||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||||
"github.com/matrix-org/dendrite/common/config"
|
"github.com/matrix-org/dendrite/common/config"
|
||||||
"github.com/matrix-org/dendrite/roomserver/api"
|
"github.com/matrix-org/dendrite/roomserver/api"
|
||||||
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type response struct {
|
||||||
|
Chunk []gomatrixserverlib.ClientEvent `json:"chunk"`
|
||||||
|
}
|
||||||
|
|
||||||
// GetMemberships implements GET /rooms/{roomId}/members
|
// GetMemberships implements GET /rooms/{roomId}/members
|
||||||
func GetMemberships(
|
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,
|
accountDB *accounts.Database, cfg config.Dendrite,
|
||||||
queryAPI api.RoomserverQueryAPI,
|
queryAPI api.RoomserverQueryAPI,
|
||||||
) util.JSONResponse {
|
) util.JSONResponse {
|
||||||
queryReq := api.QueryMembershipsForRoomRequest{
|
queryReq := api.QueryMembershipsForRoomRequest{
|
||||||
|
JoinedOnly: joinedOnly,
|
||||||
RoomID: roomID,
|
RoomID: roomID,
|
||||||
Sender: device.UserID,
|
Sender: device.UserID,
|
||||||
}
|
}
|
||||||
|
@ -50,6 +56,6 @@ func GetMemberships(
|
||||||
|
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: 200,
|
Code: 200,
|
||||||
JSON: queryRes.JoinEvents,
|
JSON: response{queryRes.JoinEvents},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -302,7 +302,14 @@ func Setup(
|
||||||
r0mux.Handle("/rooms/{roomID}/members",
|
r0mux.Handle("/rooms/{roomID}/members",
|
||||||
common.MakeAuthAPI("rooms_members", deviceDB, func(req *http.Request, device *authtypes.Device) util.JSONResponse {
|
common.MakeAuthAPI("rooms_members", deviceDB, func(req *http.Request, device *authtypes.Device) util.JSONResponse {
|
||||||
vars := mux.Vars(req)
|
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)
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -102,6 +102,8 @@ type QueryEventsByIDResponse struct {
|
||||||
|
|
||||||
// QueryMembershipsForRoomRequest is a request to QueryMembershipsForRoom
|
// QueryMembershipsForRoomRequest is a request to QueryMembershipsForRoom
|
||||||
type QueryMembershipsForRoomRequest struct {
|
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
|
// ID of the room to fetch memberships from
|
||||||
RoomID string `json:"room_id"`
|
RoomID string `json:"room_id"`
|
||||||
// ID of the user sending the request
|
// ID of the user sending the request
|
||||||
|
|
|
@ -40,13 +40,20 @@ type RoomserverQueryAPIDatabase interface {
|
||||||
// Look up the numeric IDs for a list of events.
|
// Look up the numeric IDs for a list of events.
|
||||||
// Returns an error if there was a problem talking to the database.
|
// Returns an error if there was a problem talking to the database.
|
||||||
EventNIDs(eventIDs []string) (map[string]types.EventNID, error)
|
EventNIDs(eventIDs []string) (map[string]types.EventNID, error)
|
||||||
// Look up the join events for all members in a room as requested by a given
|
// Lookup the event IDs for a batch of event numeric IDs.
|
||||||
// user. If the user is currently in the room, returns the room's current
|
// Returns an error if the retrieval went wrong.
|
||||||
// members, if not returns an empty array (TODO: Fix it)
|
EventIDs(eventNIDs []types.EventNID) (map[types.EventNID]string, error)
|
||||||
// If the user requesting the list of members has never been in the room,
|
// Lookup the membership of a given user in a given room.
|
||||||
// returns nil.
|
// Returns the numeric ID of the latest membership event sent from this user
|
||||||
// If there was an issue retrieving the events, returns an error.
|
// in this room, along a boolean set to true if the user is still in this room,
|
||||||
GetMembershipEvents(roomNID types.RoomNID, requestSenderUserID string) (events []types.Event, err error)
|
// 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
|
// 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.
|
// numeric state key IDs for the user IDs who sent them.
|
||||||
// Returns an error if there was a problem talking to the database.
|
// Returns an error if there was a problem talking to the database.
|
||||||
|
@ -194,12 +201,12 @@ func (r *RoomserverQueryAPI) QueryMembershipsForRoom(
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
events, err := r.DB.GetMembershipEvents(roomNID, request.Sender)
|
membershipEventNID, stillInRoom, err := r.DB.GetMembership(roomNID, request.Sender)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if events == nil {
|
if membershipEventNID == 0 {
|
||||||
response.HasBeenInRoom = false
|
response.HasBeenInRoom = false
|
||||||
response.JoinEvents = nil
|
response.JoinEvents = nil
|
||||||
return nil
|
return nil
|
||||||
|
@ -207,6 +214,24 @@ func (r *RoomserverQueryAPI) QueryMembershipsForRoom(
|
||||||
|
|
||||||
response.HasBeenInRoom = true
|
response.HasBeenInRoom = true
|
||||||
response.JoinEvents = []gomatrixserverlib.ClientEvent{}
|
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 {
|
for _, event := range events {
|
||||||
clientEvent := gomatrixserverlib.ToClientEvent(event.Event, gomatrixserverlib.FormatAll)
|
clientEvent := gomatrixserverlib.ToClientEvent(event.Event, gomatrixserverlib.FormatAll)
|
||||||
response.JoinEvents = append(response.JoinEvents, clientEvent)
|
response.JoinEvents = append(response.JoinEvents, clientEvent)
|
||||||
|
@ -215,6 +240,63 @@ func (r *RoomserverQueryAPI) QueryMembershipsForRoom(
|
||||||
return nil
|
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
|
// QueryInvitesForUser implements api.RoomserverQueryAPI
|
||||||
func (r *RoomserverQueryAPI) QueryInvitesForUser(
|
func (r *RoomserverQueryAPI) QueryInvitesForUser(
|
||||||
request *api.QueryInvitesForUserRequest,
|
request *api.QueryInvitesForUserRequest,
|
||||||
|
|
|
@ -77,7 +77,7 @@ const selectMembershipsFromRoomAndMembershipSQL = "" +
|
||||||
" WHERE room_nid = $1 AND membership_nid = $2"
|
" WHERE room_nid = $1 AND membership_nid = $2"
|
||||||
|
|
||||||
const selectMembershipsFromRoomSQL = "" +
|
const selectMembershipsFromRoomSQL = "" +
|
||||||
"SELECT membership_nid, event_nid FROM roomserver_membership" +
|
"SELECT event_nid FROM roomserver_membership" +
|
||||||
" WHERE room_nid = $1"
|
" WHERE room_nid = $1"
|
||||||
|
|
||||||
const selectMembershipForUpdateSQL = "" +
|
const selectMembershipForUpdateSQL = "" +
|
||||||
|
@ -140,20 +140,18 @@ func (s *membershipStatements) selectMembershipFromRoomAndTarget(
|
||||||
|
|
||||||
func (s *membershipStatements) selectMembershipsFromRoom(
|
func (s *membershipStatements) selectMembershipsFromRoom(
|
||||||
roomNID types.RoomNID,
|
roomNID types.RoomNID,
|
||||||
) (eventNIDs map[types.EventNID]membershipState, err error) {
|
) (eventNIDs []types.EventNID, err error) {
|
||||||
rows, err := s.selectMembershipsFromRoomStmt.Query(roomNID)
|
rows, err := s.selectMembershipsFromRoomStmt.Query(roomNID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
eventNIDs = make(map[types.EventNID]membershipState)
|
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var eNID types.EventNID
|
var eNID types.EventNID
|
||||||
var membership membershipState
|
if err = rows.Scan(&eNID); err != nil {
|
||||||
if err = rows.Scan(&membership, &eNID); err != nil {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
eventNIDs[eNID] = membership
|
eventNIDs = append(eventNIDs, eNID)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -552,8 +552,8 @@ func (u *membershipUpdater) SetToLeave(senderUserID string, eventID string) ([]s
|
||||||
return inviteEventIDs, nil
|
return inviteEventIDs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetMembershipEvents implements query.RoomserverQueryAPIDB
|
// GetMembership implements query.RoomserverQueryAPIDB
|
||||||
func (d *Database) GetMembershipEvents(roomNID types.RoomNID, requestSenderUserID string) (events []types.Event, err error) {
|
func (d *Database) GetMembership(roomNID types.RoomNID, requestSenderUserID string) (membershipEventNID types.EventNID, stillInRoom bool, err error) {
|
||||||
txn, err := d.db.Begin()
|
txn, err := d.db.Begin()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
|
@ -565,36 +565,24 @@ func (d *Database) GetMembershipEvents(roomNID types.RoomNID, requestSenderUserI
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_, senderMembership, err := d.statements.selectMembershipFromRoomAndTarget(roomNID, requestSenderUserNID)
|
senderMembershipEventNID, senderMembership, err := d.statements.selectMembershipFromRoomAndTarget(roomNID, requestSenderUserNID)
|
||||||
if err == sql.ErrNoRows {
|
if err == sql.ErrNoRows {
|
||||||
// The user has never been a member of that room
|
// The user has never been a member of that room
|
||||||
return nil, nil
|
return 0, false, nil
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if senderMembership == membershipStateJoin {
|
return senderMembershipEventNID, senderMembership == membershipStateJoin, nil
|
||||||
// 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)
|
// GetMembershipEventNIDsForRoom implements query.RoomserverQueryAPIDB
|
||||||
if err != nil {
|
func (d *Database) GetMembershipEventNIDsForRoom(roomNID types.RoomNID, joinOnly bool) ([]types.EventNID, error) {
|
||||||
return nil, err
|
if joinOnly {
|
||||||
|
return d.statements.selectMembershipsFromRoomAndMembership(roomNID, membershipStateJoin)
|
||||||
}
|
}
|
||||||
|
|
||||||
events, err = d.Events(joinEventNIDs)
|
return d.statements.selectMembershipsFromRoom(roomNID)
|
||||||
} 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{}
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type transaction struct {
|
type transaction struct {
|
||||||
|
|
Loading…
Reference in New Issue