aboutsummaryrefslogtreecommitdiff
path: root/publicroomsapi/storage/postgres/storage.go
blob: 691199a0b66be1aeaf6bbb92998f2d66e070dffa (plain)
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
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
// Copyright 2017-2018 New Vector Ltd
// Copyright 2019-2020 The Matrix.org Foundation C.I.C.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package postgres

import (
	"context"
	"database/sql"
	"encoding/json"

	"github.com/matrix-org/dendrite/internal"
	"github.com/matrix-org/dendrite/internal/sqlutil"

	"github.com/matrix-org/gomatrixserverlib"
)

// PublicRoomsServerDatabase represents a public rooms server database.
type PublicRoomsServerDatabase struct {
	db *sql.DB
	internal.PartitionOffsetStatements
	statements publicRoomsStatements
}

type attributeValue interface{}

// NewPublicRoomsServerDatabase creates a new public rooms server database.
func NewPublicRoomsServerDatabase(dataSourceName string, dbProperties internal.DbProperties) (*PublicRoomsServerDatabase, error) {
	var db *sql.DB
	var err error
	if db, err = sqlutil.Open("postgres", dataSourceName, dbProperties); err != nil {
		return nil, err
	}
	storage := PublicRoomsServerDatabase{
		db: db,
	}
	if err = storage.PartitionOffsetStatements.Prepare(db, "publicroomsapi"); err != nil {
		return nil, err
	}
	if err = storage.statements.prepare(db); err != nil {
		return nil, err
	}
	return &storage, nil
}

// GetRoomVisibility returns the room visibility as a boolean: true if the room
// is publicly visible, false if not.
// Returns an error if the retrieval failed.
func (d *PublicRoomsServerDatabase) GetRoomVisibility(
	ctx context.Context, roomID string,
) (bool, error) {
	return d.statements.selectRoomVisibility(ctx, roomID)
}

// SetRoomVisibility updates the visibility attribute of a room. This attribute
// must be set to true if the room is publicly visible, false if not.
// Returns an error if the update failed.
func (d *PublicRoomsServerDatabase) SetRoomVisibility(
	ctx context.Context, visible bool, roomID string,
) error {
	return d.statements.updateRoomAttribute(ctx, "visibility", visible, roomID)
}

// CountPublicRooms returns the number of room set as publicly visible on the server.
// Returns an error if the retrieval failed.
func (d *PublicRoomsServerDatabase) CountPublicRooms(ctx context.Context) (int64, error) {
	return d.statements.countPublicRooms(ctx)
}

// GetPublicRooms returns an array containing the local rooms set as publicly visible, ordered by their number
// of joined members. This array can be limited by a given number of elements, and offset by a given value.
// If the limit is 0, doesn't limit the number of results. If the offset is 0 too, the array contains all
// the rooms set as publicly visible on the server.
// Returns an error if the retrieval failed.
func (d *PublicRoomsServerDatabase) GetPublicRooms(
	ctx context.Context, offset int64, limit int16, filter string,
) ([]gomatrixserverlib.PublicRoom, error) {
	return d.statements.selectPublicRooms(ctx, offset, limit, filter)
}

// UpdateRoomFromEvents iterate over a slice of state events and call
// UpdateRoomFromEvent on each of them to update the database representation of
// the rooms updated by each event.
// The slice of events to remove is used to update the number of joined members
// for the room in the database.
// If the update triggered by one of the events failed, aborts the process and
// returns an error.
func (d *PublicRoomsServerDatabase) UpdateRoomFromEvents(
	ctx context.Context,
	eventsToAdd []gomatrixserverlib.Event,
	eventsToRemove []gomatrixserverlib.Event,
) error {
	for _, event := range eventsToAdd {
		if err := d.UpdateRoomFromEvent(ctx, event); err != nil {
			return err
		}
	}

	for _, event := range eventsToRemove {
		if event.Type() == "m.room.member" {
			if err := d.updateNumJoinedUsers(ctx, event, true); err != nil {
				return err
			}
		}
	}

	return nil
}

// UpdateRoomFromEvent updates the database representation of a room from a Matrix event, by
// checking the event's type to know which attribute to change and using the event's content
// to define the new value of the attribute.
// If the event doesn't match with any property used to compute the public room directory,
// does nothing.
// If something went wrong during the process, returns an error.
func (d *PublicRoomsServerDatabase) UpdateRoomFromEvent(
	ctx context.Context, event gomatrixserverlib.Event,
) error {
	// Process the event according to its type
	switch event.Type() {
	case "m.room.create":
		return d.statements.insertNewRoom(ctx, event.RoomID())
	case "m.room.member":
		return d.updateNumJoinedUsers(ctx, event, false)
	case "m.room.aliases":
		return d.updateRoomAliases(ctx, event)
	case "m.room.canonical_alias":
		var content internal.CanonicalAliasContent
		field := &(content.Alias)
		attrName := "canonical_alias"
		return d.updateStringAttribute(ctx, attrName, event, &content, field)
	case "m.room.name":
		var content internal.NameContent
		field := &(content.Name)
		attrName := "name"
		return d.updateStringAttribute(ctx, attrName, event, &content, field)
	case "m.room.topic":
		var content internal.TopicContent
		field := &(content.Topic)
		attrName := "topic"
		return d.updateStringAttribute(ctx, attrName, event, &content, field)
	case "m.room.avatar":
		var content internal.AvatarContent
		field := &(content.URL)
		attrName := "avatar_url"
		return d.updateStringAttribute(ctx, attrName, event, &content, field)
	case "m.room.history_visibility":
		var content internal.HistoryVisibilityContent
		field := &(content.HistoryVisibility)
		attrName := "world_readable"
		strForTrue := "world_readable"
		return d.updateBooleanAttribute(ctx, attrName, event, &content, field, strForTrue)
	case "m.room.guest_access":
		var content internal.GuestAccessContent
		field := &(content.GuestAccess)
		attrName := "guest_can_join"
		strForTrue := "can_join"
		return d.updateBooleanAttribute(ctx, attrName, event, &content, field, strForTrue)
	}

	// If the event type didn't match, return with no error
	return nil
}

// updateNumJoinedUsers updates the number of joined user in the database representation
// of a room using a given "m.room.member" Matrix event.
// If the membership property of the event isn't "join", ignores it and returs nil.
// If the remove parameter is set to false, increments the joined members counter in the
// database, if set to truem decrements it.
// Returns an error if the update failed.
func (d *PublicRoomsServerDatabase) updateNumJoinedUsers(
	ctx context.Context, membershipEvent gomatrixserverlib.Event, remove bool,
) error {
	membership, err := membershipEvent.Membership()
	if err != nil {
		return err
	}

	if membership != gomatrixserverlib.Join {
		return nil
	}

	if remove {
		return d.statements.decrementJoinedMembersInRoom(ctx, membershipEvent.RoomID())
	}
	return d.statements.incrementJoinedMembersInRoom(ctx, membershipEvent.RoomID())
}

// updateStringAttribute updates a given string attribute in the database
// representation of a room using a given string data field from content of the
// Matrix event triggering the update.
// Returns an error if decoding the Matrix event's content or updating the attribute
// failed.
func (d *PublicRoomsServerDatabase) updateStringAttribute(
	ctx context.Context, attrName string, event gomatrixserverlib.Event,
	content interface{}, field *string,
) error {
	if err := json.Unmarshal(event.Content(), content); err != nil {
		return err
	}

	return d.statements.updateRoomAttribute(ctx, attrName, *field, event.RoomID())
}

// updateBooleanAttribute updates a given boolean attribute in the database
// representation of a room using a given string data field from content of the
// Matrix event triggering the update.
// The attribute is set to true if the field matches a given string, false if not.
// Returns an error if decoding the Matrix event's content or updating the attribute
// failed.
func (d *PublicRoomsServerDatabase) updateBooleanAttribute(
	ctx context.Context, attrName string, event gomatrixserverlib.Event,
	content interface{}, field *string, strForTrue string,
) error {
	if err := json.Unmarshal(event.Content(), content); err != nil {
		return err
	}

	var attrValue bool
	if *field == strForTrue {
		attrValue = true
	} else {
		attrValue = false
	}

	return d.statements.updateRoomAttribute(ctx, attrName, attrValue, event.RoomID())
}

// updateRoomAliases decodes the content of a "m.room.aliases" Matrix event and update the list of aliases of
// a given room with it.
// Returns an error if decoding the Matrix event or updating the list failed.
func (d *PublicRoomsServerDatabase) updateRoomAliases(
	ctx context.Context, aliasesEvent gomatrixserverlib.Event,
) error {
	var content internal.AliasesContent
	if err := json.Unmarshal(aliasesEvent.Content(), &content); err != nil {
		return err
	}

	return d.statements.updateRoomAttribute(
		ctx, "aliases", content.Aliases, aliasesEvent.RoomID(),
	)
}