/
invites_table.go
140 lines (127 loc) · 4.57 KB
/
invites_table.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
package state
import (
"database/sql"
"encoding/json"
"github.com/lib/pq"
"github.com/jmoiron/sqlx"
)
// InvitesTable stores invites for each user.
// Originally, invites were stored with the main events in a room. We ignored stripped state and
// just kept the m.room.member invite event. This had many problems though:
// - The room would be initialised by the invite event, causing the room to not populate with state
// 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 {
db *sqlx.DB
}
func NewInvitesTable(db *sqlx.DB) *InvitesTable {
// make sure tables are made
db.MustExec(`
CREATE TABLE IF NOT EXISTS syncv3_invites (
room_id TEXT NOT NULL,
user_id TEXT NOT NULL,
-- JSON array. The contents of 'rooms.invite.$room_id.invite_state.events'
invite_state BYTEA NOT NULL,
UNIQUE(user_id, room_id)
);
`)
return &InvitesTable{db}
}
func (t *InvitesTable) RemoveInvite(userID, roomID string) error {
_, err := t.db.Exec(`DELETE FROM syncv3_invites WHERE user_id = $1 AND room_id = $2`, userID, roomID)
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)
}
}
if len(usersToRemove) == 0 {
return nil
}
_, 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 {
return err
}
_, err = t.db.Exec(
`INSERT INTO syncv3_invites(user_id, room_id, invite_state) VALUES($1,$2,$3)
ON CONFLICT (user_id, room_id) DO UPDATE SET invite_state = $3`,
userID, roomID, blob,
)
return err
}
func (t *InvitesTable) SelectInviteState(userID, roomID string) (inviteState []json.RawMessage, err error) {
var blob json.RawMessage
if err := t.db.QueryRow(`SELECT invite_state FROM syncv3_invites WHERE user_id=$1 AND room_id=$2`, userID, roomID).Scan(&blob); err != nil && err != sql.ErrNoRows {
return nil, err
}
if blob == nil {
return
}
if err := json.Unmarshal(blob, &inviteState); err != nil {
return nil, err
}
return inviteState, nil
}
// Select all invites for this user. Returns a map of room ID to invite_state (json array).
func (t *InvitesTable) SelectAllInvitesForUser(userID string) (map[string][]json.RawMessage, error) {
rows, err := t.db.Query(`SELECT room_id, invite_state FROM syncv3_invites WHERE user_id = $1`, userID)
if err != nil {
return nil, err
}
defer rows.Close()
result := make(map[string][]json.RawMessage)
var roomID string
var blob json.RawMessage
for rows.Next() {
if err := rows.Scan(&roomID, &blob); err != nil {
return nil, err
}
var inviteState []json.RawMessage
if err := json.Unmarshal(blob, &inviteState); err != nil {
return nil, err
}
result[roomID] = inviteState
}
return result, nil
}