mirror of
https://github.com/matrix-org/sliding-sync.git
synced 2025-03-10 13:37:11 +00:00
New storage func for removing redundant invites
This commit is contained in:
parent
cd8390fe2f
commit
9b9da2a8e9
@ -3,6 +3,7 @@ package state
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"github.com/lib/pq"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
@ -14,14 +15,17 @@ import (
|
||||
// correctly when the user joined the room.
|
||||
// - The user could read room data in the room without being joined to the room e.g could pull
|
||||
// `required_state` and `timeline` as they would be authorised by the invite to see this data.
|
||||
//
|
||||
// Instead, we now completely split out invites from the normal event flow. This fixes the issues
|
||||
// outlined above but introduce more problems:
|
||||
// - How do you sort the invite with rooms?
|
||||
// - How do you calculate the room name when you lack heroes?
|
||||
//
|
||||
// For now, we say that invites:
|
||||
// - are treated as a highlightable event for the purposes of sorting by highlight count.
|
||||
// - are given the timestamp of when the invite arrived.
|
||||
// - calculate the room name on a best-effort basis given the lack of heroes (same as element-web).
|
||||
//
|
||||
// When an invite is rejected, it appears in the `leave` section which then causes the invite to be
|
||||
// removed from this table.
|
||||
type InvitesTable struct {
|
||||
@ -47,6 +51,40 @@ func (t *InvitesTable) RemoveInvite(userID, roomID string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// RemoveSupersededInvites accepts a list of events in the given room. The events should
|
||||
// either
|
||||
// - contain at most one membership event per user, or else
|
||||
// - be in timeline order (most recent last)
|
||||
//
|
||||
// (corresponding to an Accumulate and an Initialise call, respectively).
|
||||
//
|
||||
// The events are scanned in order for membership changes, to determine the "final"
|
||||
// memberships. Users who final membership is not "invite" have their outstanding
|
||||
// invites to this room deleted.
|
||||
func (t *InvitesTable) RemoveSupersededInvites(txn *sqlx.Tx, roomID string, newEvents []Event) error {
|
||||
memberships := map[string]string{} // user ID -> memberships
|
||||
for _, ev := range newEvents {
|
||||
if ev.Type != "m.room.member" {
|
||||
continue
|
||||
}
|
||||
memberships[ev.StateKey] = ev.Membership
|
||||
}
|
||||
|
||||
var usersToRemove []string
|
||||
for userID, membership := range memberships {
|
||||
if membership != "invite" && membership != "_invite" {
|
||||
usersToRemove = append(usersToRemove, userID)
|
||||
}
|
||||
}
|
||||
|
||||
_, err := txn.Exec(`
|
||||
DELETE FROM syncv3_invites
|
||||
WHERE user_id = ANY($1) AND room_id = $2
|
||||
`, pq.StringArray(usersToRemove), roomID)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (t *InvitesTable) InsertInvite(userID, roomID string, inviteRoomState []json.RawMessage) error {
|
||||
blob, err := json.Marshal(inviteRoomState)
|
||||
if err != nil {
|
||||
|
@ -2,6 +2,8 @@ package state
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/matrix-org/sliding-sync/sqlutil"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
@ -128,6 +130,155 @@ func TestInviteTable(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestInviteTable_RemoveSupersededInvites(t *testing.T) {
|
||||
db, close := connectToDB(t)
|
||||
defer close()
|
||||
|
||||
alice := "@alice:localhost"
|
||||
bob := "@bob:localhost"
|
||||
roomA := "!a:localhost"
|
||||
roomB := "!b:localhost"
|
||||
inviteState := []json.RawMessage{[]byte(`{"foo":"bar"}`)}
|
||||
|
||||
table := NewInvitesTable(db)
|
||||
t.Log("Invite Alice and Bob to both rooms.")
|
||||
|
||||
// Add some invites
|
||||
if err := table.InsertInvite(alice, roomA, inviteState); err != nil {
|
||||
t.Fatalf("failed to InsertInvite: %s", err)
|
||||
}
|
||||
if err := table.InsertInvite(bob, roomA, inviteState); err != nil {
|
||||
t.Fatalf("failed to InsertInvite: %s", err)
|
||||
}
|
||||
if err := table.InsertInvite(alice, roomB, inviteState); err != nil {
|
||||
t.Fatalf("failed to InsertInvite: %s", err)
|
||||
}
|
||||
if err := table.InsertInvite(bob, roomB, inviteState); err != nil {
|
||||
t.Fatalf("failed to InsertInvite: %s", err)
|
||||
}
|
||||
|
||||
t.Log("Alice joins room A. Remove her superseded invite.")
|
||||
newEvents := []Event{
|
||||
{
|
||||
Type: "m.room.member",
|
||||
StateKey: alice,
|
||||
Membership: "join",
|
||||
RoomID: roomA,
|
||||
},
|
||||
}
|
||||
err := sqlutil.WithTransaction(db, func(txn *sqlx.Tx) error {
|
||||
return table.RemoveSupersededInvites(txn, roomA, newEvents)
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to RemoveSupersededInvites: %s", err)
|
||||
}
|
||||
|
||||
t.Log("Alice should still be invited to room B.")
|
||||
assertInvites(t, table, alice, map[string][]json.RawMessage{roomB: inviteState})
|
||||
t.Log("Bob should still be invited to rooms A and B.")
|
||||
assertInvites(t, table, bob, map[string][]json.RawMessage{roomA: inviteState, roomB: inviteState})
|
||||
|
||||
t.Log("Bob declines his invitation to room B.")
|
||||
newEvents = []Event{
|
||||
{
|
||||
Type: "m.room.member",
|
||||
StateKey: bob,
|
||||
Membership: "leave",
|
||||
RoomID: roomB,
|
||||
},
|
||||
}
|
||||
err = sqlutil.WithTransaction(db, func(txn *sqlx.Tx) error {
|
||||
return table.RemoveSupersededInvites(txn, roomB, newEvents)
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to RemoveSupersededInvites: %s", err)
|
||||
}
|
||||
|
||||
t.Log("Alice should still be invited to room B.")
|
||||
assertInvites(t, table, alice, map[string][]json.RawMessage{roomB: inviteState})
|
||||
t.Log("Bob should still be invited to room A.")
|
||||
assertInvites(t, table, bob, map[string][]json.RawMessage{roomA: inviteState})
|
||||
|
||||
// Now try multiple membership changes in one call.
|
||||
t.Log("Alice joins, changes profile, leaves and is re-invited to room B.")
|
||||
newEvents = []Event{
|
||||
{
|
||||
Type: "m.room.member",
|
||||
StateKey: alice,
|
||||
Membership: "join",
|
||||
RoomID: roomB,
|
||||
},
|
||||
{
|
||||
Type: "m.room.member",
|
||||
StateKey: alice,
|
||||
Membership: "_join",
|
||||
RoomID: roomB,
|
||||
},
|
||||
{
|
||||
Type: "m.room.member",
|
||||
StateKey: alice,
|
||||
Membership: "leave",
|
||||
RoomID: roomB,
|
||||
},
|
||||
{
|
||||
Type: "m.room.member",
|
||||
StateKey: alice,
|
||||
Membership: "invite",
|
||||
RoomID: roomB,
|
||||
},
|
||||
}
|
||||
|
||||
err = sqlutil.WithTransaction(db, func(txn *sqlx.Tx) error {
|
||||
return table.RemoveSupersededInvites(txn, roomB, newEvents)
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to RemoveSupersededInvites: %s", err)
|
||||
}
|
||||
|
||||
t.Log("Alice should still be invited to room B.")
|
||||
assertInvites(t, table, alice, map[string][]json.RawMessage{roomB: inviteState})
|
||||
|
||||
t.Log("Bob declines, is reinvited to and joins room A.")
|
||||
newEvents = []Event{
|
||||
{
|
||||
Type: "m.room.member",
|
||||
StateKey: bob,
|
||||
Membership: "leave",
|
||||
RoomID: roomA,
|
||||
},
|
||||
{
|
||||
Type: "m.room.member",
|
||||
StateKey: bob,
|
||||
Membership: "invite",
|
||||
RoomID: roomA,
|
||||
},
|
||||
{
|
||||
Type: "m.room.member",
|
||||
StateKey: bob,
|
||||
Membership: "join",
|
||||
RoomID: roomA,
|
||||
},
|
||||
}
|
||||
|
||||
err = sqlutil.WithTransaction(db, func(txn *sqlx.Tx) error {
|
||||
return table.RemoveSupersededInvites(txn, roomA, newEvents)
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to RemoveSupersededInvites: %s", err)
|
||||
}
|
||||
assertInvites(t, table, bob, map[string][]json.RawMessage{})
|
||||
}
|
||||
|
||||
func assertInvites(t *testing.T, table *InvitesTable, user string, expected map[string][]json.RawMessage) {
|
||||
invites, err := table.SelectAllInvitesForUser(user)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to SelectAllInvitesForUser: %s", err)
|
||||
}
|
||||
if !reflect.DeepEqual(invites, expected) {
|
||||
t.Fatalf("got %v invites, want %v", invites, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func jsonArrStr(a []json.RawMessage) (result string) {
|
||||
for _, e := range a {
|
||||
result += string(e) + "\n"
|
||||
|
Loading…
x
Reference in New Issue
Block a user