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 (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"github.com/lib/pq"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
)
|
)
|
||||||
@ -14,14 +15,17 @@ import (
|
|||||||
// correctly when the user joined the room.
|
// 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
|
// - 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.
|
// `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
|
// Instead, we now completely split out invites from the normal event flow. This fixes the issues
|
||||||
// outlined above but introduce more problems:
|
// outlined above but introduce more problems:
|
||||||
// - How do you sort the invite with rooms?
|
// - How do you sort the invite with rooms?
|
||||||
// - How do you calculate the room name when you lack heroes?
|
// - How do you calculate the room name when you lack heroes?
|
||||||
|
//
|
||||||
// For now, we say that invites:
|
// For now, we say that invites:
|
||||||
// - are treated as a highlightable event for the purposes of sorting by highlight count.
|
// - are treated as a highlightable event for the purposes of sorting by highlight count.
|
||||||
// - are given the timestamp of when the invite arrived.
|
// - 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).
|
// - 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
|
// When an invite is rejected, it appears in the `leave` section which then causes the invite to be
|
||||||
// removed from this table.
|
// removed from this table.
|
||||||
type InvitesTable struct {
|
type InvitesTable struct {
|
||||||
@ -47,6 +51,40 @@ func (t *InvitesTable) RemoveInvite(userID, roomID string) error {
|
|||||||
return err
|
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 {
|
func (t *InvitesTable) InsertInvite(userID, roomID string, inviteRoomState []json.RawMessage) error {
|
||||||
blob, err := json.Marshal(inviteRoomState)
|
blob, err := json.Marshal(inviteRoomState)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -2,6 +2,8 @@ package state
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
"github.com/matrix-org/sliding-sync/sqlutil"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"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) {
|
func jsonArrStr(a []json.RawMessage) (result string) {
|
||||||
for _, e := range a {
|
for _, e := range a {
|
||||||
result += string(e) + "\n"
|
result += string(e) + "\n"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user