281 lines
8.3 KiB
Go
281 lines
8.3 KiB
Go
// 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"
|
|
"errors"
|
|
"fmt"
|
|
|
|
"github.com/matrix-org/dendrite/common"
|
|
|
|
"github.com/lib/pq"
|
|
"github.com/matrix-org/dendrite/publicroomsapi/types"
|
|
)
|
|
|
|
var editableAttributes = []string{
|
|
"aliases",
|
|
"canonical_alias",
|
|
"name",
|
|
"topic",
|
|
"world_readable",
|
|
"guest_can_join",
|
|
"avatar_url",
|
|
"visibility",
|
|
}
|
|
|
|
const publicRoomsSchema = `
|
|
-- Stores all of the rooms with data needed to create the server's room directory
|
|
CREATE TABLE IF NOT EXISTS publicroomsapi_public_rooms(
|
|
-- The room's ID
|
|
room_id TEXT NOT NULL PRIMARY KEY,
|
|
-- Number of joined members in the room
|
|
joined_members INTEGER NOT NULL DEFAULT 0,
|
|
-- Aliases of the room (empty array if none)
|
|
aliases TEXT[] NOT NULL DEFAULT '{}'::TEXT[],
|
|
-- Canonical alias of the room (empty string if none)
|
|
canonical_alias TEXT NOT NULL DEFAULT '',
|
|
-- Name of the room (empty string if none)
|
|
name TEXT NOT NULL DEFAULT '',
|
|
-- Topic of the room (empty string if none)
|
|
topic TEXT NOT NULL DEFAULT '',
|
|
-- Is the room world readable?
|
|
world_readable BOOLEAN NOT NULL DEFAULT false,
|
|
-- Can guest join the room?
|
|
guest_can_join BOOLEAN NOT NULL DEFAULT false,
|
|
-- URL of the room avatar (empty string if none)
|
|
avatar_url TEXT NOT NULL DEFAULT '',
|
|
-- Visibility of the room: true means the room is publicly visible, false
|
|
-- means the room is private
|
|
visibility BOOLEAN NOT NULL DEFAULT false
|
|
);
|
|
`
|
|
|
|
const countPublicRoomsSQL = "" +
|
|
"SELECT COUNT(*) FROM publicroomsapi_public_rooms" +
|
|
" WHERE visibility = true"
|
|
|
|
const selectPublicRoomsSQL = "" +
|
|
"SELECT room_id, joined_members, aliases, canonical_alias, name, topic, world_readable, guest_can_join, avatar_url" +
|
|
" FROM publicroomsapi_public_rooms WHERE visibility = true" +
|
|
" ORDER BY joined_members DESC" +
|
|
" OFFSET $1"
|
|
|
|
const selectPublicRoomsWithLimitSQL = "" +
|
|
"SELECT room_id, joined_members, aliases, canonical_alias, name, topic, world_readable, guest_can_join, avatar_url" +
|
|
" FROM publicroomsapi_public_rooms WHERE visibility = true" +
|
|
" ORDER BY joined_members DESC" +
|
|
" OFFSET $1 LIMIT $2"
|
|
|
|
const selectPublicRoomsWithFilterSQL = "" +
|
|
"SELECT room_id, joined_members, aliases, canonical_alias, name, topic, world_readable, guest_can_join, avatar_url" +
|
|
" FROM publicroomsapi_public_rooms" +
|
|
" WHERE visibility = true" +
|
|
" AND (LOWER(name) LIKE LOWER($1)" +
|
|
" OR LOWER(topic) LIKE LOWER($1)" +
|
|
" OR LOWER(ARRAY_TO_STRING(aliases, ',')) LIKE LOWER($1))" +
|
|
" ORDER BY joined_members DESC" +
|
|
" OFFSET $2"
|
|
|
|
const selectPublicRoomsWithLimitAndFilterSQL = "" +
|
|
"SELECT room_id, joined_members, aliases, canonical_alias, name, topic, world_readable, guest_can_join, avatar_url" +
|
|
" FROM publicroomsapi_public_rooms" +
|
|
" WHERE visibility = true" +
|
|
" AND (LOWER(name) LIKE LOWER($1)" +
|
|
" OR LOWER(topic) LIKE LOWER($1)" +
|
|
" OR LOWER(ARRAY_TO_STRING(aliases, ',')) LIKE LOWER($1))" +
|
|
" ORDER BY joined_members DESC" +
|
|
" OFFSET $2 LIMIT $3"
|
|
|
|
const selectRoomVisibilitySQL = "" +
|
|
"SELECT visibility FROM publicroomsapi_public_rooms" +
|
|
" WHERE room_id = $1"
|
|
|
|
const insertNewRoomSQL = "" +
|
|
"INSERT INTO publicroomsapi_public_rooms(room_id)" +
|
|
" VALUES ($1)"
|
|
|
|
const incrementJoinedMembersInRoomSQL = "" +
|
|
"UPDATE publicroomsapi_public_rooms" +
|
|
" SET joined_members = joined_members + 1" +
|
|
" WHERE room_id = $1"
|
|
|
|
const decrementJoinedMembersInRoomSQL = "" +
|
|
"UPDATE publicroomsapi_public_rooms" +
|
|
" SET joined_members = joined_members - 1" +
|
|
" WHERE room_id = $1"
|
|
|
|
const updateRoomAttributeSQL = "" +
|
|
"UPDATE publicroomsapi_public_rooms" +
|
|
" SET %s = $1" +
|
|
" WHERE room_id = $2"
|
|
|
|
type publicRoomsStatements struct {
|
|
countPublicRoomsStmt *sql.Stmt
|
|
selectPublicRoomsStmt *sql.Stmt
|
|
selectPublicRoomsWithLimitStmt *sql.Stmt
|
|
selectPublicRoomsWithFilterStmt *sql.Stmt
|
|
selectPublicRoomsWithLimitAndFilterStmt *sql.Stmt
|
|
selectRoomVisibilityStmt *sql.Stmt
|
|
insertNewRoomStmt *sql.Stmt
|
|
incrementJoinedMembersInRoomStmt *sql.Stmt
|
|
decrementJoinedMembersInRoomStmt *sql.Stmt
|
|
updateRoomAttributeStmts map[string]*sql.Stmt
|
|
}
|
|
|
|
func (s *publicRoomsStatements) prepare(db *sql.DB) (err error) {
|
|
_, err = db.Exec(publicRoomsSchema)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
stmts := statementList{
|
|
{&s.countPublicRoomsStmt, countPublicRoomsSQL},
|
|
{&s.selectPublicRoomsStmt, selectPublicRoomsSQL},
|
|
{&s.selectPublicRoomsWithLimitStmt, selectPublicRoomsWithLimitSQL},
|
|
{&s.selectPublicRoomsWithFilterStmt, selectPublicRoomsWithFilterSQL},
|
|
{&s.selectPublicRoomsWithLimitAndFilterStmt, selectPublicRoomsWithLimitAndFilterSQL},
|
|
{&s.selectRoomVisibilityStmt, selectRoomVisibilitySQL},
|
|
{&s.insertNewRoomStmt, insertNewRoomSQL},
|
|
{&s.incrementJoinedMembersInRoomStmt, incrementJoinedMembersInRoomSQL},
|
|
{&s.decrementJoinedMembersInRoomStmt, decrementJoinedMembersInRoomSQL},
|
|
}
|
|
|
|
if err = stmts.prepare(db); err != nil {
|
|
return
|
|
}
|
|
|
|
s.updateRoomAttributeStmts = make(map[string]*sql.Stmt)
|
|
for _, editable := range editableAttributes {
|
|
stmt := fmt.Sprintf(updateRoomAttributeSQL, editable)
|
|
if s.updateRoomAttributeStmts[editable], err = db.Prepare(stmt); err != nil {
|
|
return
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func (s *publicRoomsStatements) countPublicRooms(ctx context.Context) (nb int64, err error) {
|
|
err = s.countPublicRoomsStmt.QueryRowContext(ctx).Scan(&nb)
|
|
return
|
|
}
|
|
|
|
func (s *publicRoomsStatements) selectPublicRooms(
|
|
ctx context.Context, offset int64, limit int16, filter string,
|
|
) ([]types.PublicRoom, error) {
|
|
var rows *sql.Rows
|
|
var err error
|
|
|
|
if len(filter) > 0 {
|
|
pattern := "%" + filter + "%"
|
|
if limit == 0 {
|
|
rows, err = s.selectPublicRoomsWithFilterStmt.QueryContext(
|
|
ctx, pattern, offset,
|
|
)
|
|
} else {
|
|
rows, err = s.selectPublicRoomsWithLimitAndFilterStmt.QueryContext(
|
|
ctx, pattern, offset, limit,
|
|
)
|
|
}
|
|
} else {
|
|
if limit == 0 {
|
|
rows, err = s.selectPublicRoomsStmt.QueryContext(ctx, offset)
|
|
} else {
|
|
rows, err = s.selectPublicRoomsWithLimitStmt.QueryContext(
|
|
ctx, offset, limit,
|
|
)
|
|
}
|
|
}
|
|
|
|
if err != nil {
|
|
return []types.PublicRoom{}, nil
|
|
}
|
|
defer common.CloseAndLogIfError(ctx, rows, "selectPublicRooms: rows.close() failed")
|
|
|
|
rooms := []types.PublicRoom{}
|
|
for rows.Next() {
|
|
var r types.PublicRoom
|
|
var aliases pq.StringArray
|
|
|
|
err = rows.Scan(
|
|
&r.RoomID, &r.NumJoinedMembers, &aliases, &r.CanonicalAlias,
|
|
&r.Name, &r.Topic, &r.WorldReadable, &r.GuestCanJoin, &r.AvatarURL,
|
|
)
|
|
if err != nil {
|
|
return rooms, err
|
|
}
|
|
|
|
r.Aliases = aliases
|
|
|
|
rooms = append(rooms, r)
|
|
}
|
|
|
|
return rooms, rows.Err()
|
|
}
|
|
|
|
func (s *publicRoomsStatements) selectRoomVisibility(
|
|
ctx context.Context, roomID string,
|
|
) (v bool, err error) {
|
|
err = s.selectRoomVisibilityStmt.QueryRowContext(ctx, roomID).Scan(&v)
|
|
return
|
|
}
|
|
|
|
func (s *publicRoomsStatements) insertNewRoom(
|
|
ctx context.Context, roomID string,
|
|
) error {
|
|
_, err := s.insertNewRoomStmt.ExecContext(ctx, roomID)
|
|
return err
|
|
}
|
|
|
|
func (s *publicRoomsStatements) incrementJoinedMembersInRoom(
|
|
ctx context.Context, roomID string,
|
|
) error {
|
|
_, err := s.incrementJoinedMembersInRoomStmt.ExecContext(ctx, roomID)
|
|
return err
|
|
}
|
|
|
|
func (s *publicRoomsStatements) decrementJoinedMembersInRoom(
|
|
ctx context.Context, roomID string,
|
|
) error {
|
|
_, err := s.decrementJoinedMembersInRoomStmt.ExecContext(ctx, roomID)
|
|
return err
|
|
}
|
|
|
|
func (s *publicRoomsStatements) updateRoomAttribute(
|
|
ctx context.Context, attrName string, attrValue attributeValue, roomID string,
|
|
) error {
|
|
stmt, isEditable := s.updateRoomAttributeStmts[attrName]
|
|
|
|
if !isEditable {
|
|
return errors.New("Cannot edit " + attrName)
|
|
}
|
|
|
|
var value interface{}
|
|
switch v := attrValue.(type) {
|
|
case []string:
|
|
value = pq.StringArray(v)
|
|
case bool, string:
|
|
value = attrValue
|
|
default:
|
|
return errors.New("Unsupported attribute type, must be bool, string or []string")
|
|
}
|
|
|
|
_, err := stmt.ExecContext(ctx, value, roomID)
|
|
return err
|
|
}
|