From 29e5e7942bef879b5f1c0918cc1003458e0613c4 Mon Sep 17 00:00:00 2001 From: Brendan Abolivier Date: Mon, 7 Aug 2017 15:10:03 +0100 Subject: [PATCH 01/18] Create placeholder handler for room directory updates --- .../dendrite/clientapi/routing/routing.go | 14 ++++++ .../clientapi/writers/room_directory.go | 48 +++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 src/github.com/matrix-org/dendrite/clientapi/writers/room_directory.go diff --git a/src/github.com/matrix-org/dendrite/clientapi/routing/routing.go b/src/github.com/matrix-org/dendrite/clientapi/routing/routing.go index 68a9de075..0cd9e8595 100644 --- a/src/github.com/matrix-org/dendrite/clientapi/routing/routing.go +++ b/src/github.com/matrix-org/dendrite/clientapi/routing/routing.go @@ -133,6 +133,20 @@ func Setup( }), ).Methods("DELETE") + r0mux.Handle("/directory/list/room/{roomID}", + common.MakeAPI("directory_list", func(req *http.Request) util.JSONResponse { + vars := mux.Vars(req) + return writers.GetVisibility(req, vars["roomID"]) + }), + ).Methods("GET") + + r0mux.Handle("/directory/list/room/{roomID}", + common.MakeAuthAPI("directory_list", deviceDB, func(req *http.Request, device *authtypes.Device) util.JSONResponse { + vars := mux.Vars(req) + return writers.SetVisibility(req, *device, vars["roomID"], cfg) + }), + ).Methods("PUT", "OPTIONS") + r0mux.Handle("/logout", common.MakeAuthAPI("logout", deviceDB, func(req *http.Request, device *authtypes.Device) util.JSONResponse { return readers.Logout(req, deviceDB, device) diff --git a/src/github.com/matrix-org/dendrite/clientapi/writers/room_directory.go b/src/github.com/matrix-org/dendrite/clientapi/writers/room_directory.go new file mode 100644 index 000000000..a4c175719 --- /dev/null +++ b/src/github.com/matrix-org/dendrite/clientapi/writers/room_directory.go @@ -0,0 +1,48 @@ +// Copyright 2017 Vector Creations Ltd +// +// 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 writers + +import ( + "net/http" + + "github.com/matrix-org/dendrite/clientapi/auth/authtypes" + "github.com/matrix-org/dendrite/common/config" + "github.com/matrix-org/util" +) + +type roomDirectoryVisibility struct { + Visibility string `json:"visibility"` +} + +// GetVisibility implements GET /directory/list/room/{roomID} +func GetVisibility( + req *http.Request, roomID string, +) util.JSONResponse { + return util.JSONResponse{ + Code: 200, + JSON: roomDirectoryVisibility{"public"}, + } +} + +// SetVisibility implements PUT /directory/list/room/{roomID} +func SetVisibility( + req *http.Request, device authtypes.Device, roomID string, + cfg config.Dendrite, +) util.JSONResponse { + return util.JSONResponse{ + Code: 200, + JSON: struct{}{}, + } +} From 9e772402d43818b34de63d243b1f0f72ee85df71 Mon Sep 17 00:00:00 2001 From: Brendan Abolivier Date: Tue, 8 Aug 2017 13:06:00 +0100 Subject: [PATCH 02/18] Add database functions in the rooms table --- .../roomserver/storage/rooms_table.go | 50 ++++++++++++++++++- .../dendrite/roomserver/storage/storage.go | 24 +++++++++ 2 files changed, 73 insertions(+), 1 deletion(-) diff --git a/src/github.com/matrix-org/dendrite/roomserver/storage/rooms_table.go b/src/github.com/matrix-org/dendrite/roomserver/storage/rooms_table.go index 03cacd7db..5858dd88a 100644 --- a/src/github.com/matrix-org/dendrite/roomserver/storage/rooms_table.go +++ b/src/github.com/matrix-org/dendrite/roomserver/storage/rooms_table.go @@ -36,7 +36,11 @@ CREATE TABLE IF NOT EXISTS roomserver_rooms ( last_event_sent_nid BIGINT NOT NULL DEFAULT 0, -- The state of the room after the current set of latest events. -- This will be 0 if there are no latest events in the room. - state_snapshot_nid BIGINT NOT NULL DEFAULT 0 + state_snapshot_nid BIGINT NOT NULL DEFAULT 0, + -- The visibility of the room. + -- This will be true if the room has a public visibility, and false if it + -- has a private visibility. + visibility BOOLEAN NOT NULL DEFAULT false ); ` @@ -55,15 +59,27 @@ const selectLatestEventNIDsSQL = "" + const selectLatestEventNIDsForUpdateSQL = "" + "SELECT latest_event_nids, last_event_sent_nid, state_snapshot_nid FROM roomserver_rooms WHERE room_nid = $1 FOR UPDATE" +const selectVisibilityForRoomNIDSQL = "" + + "SELECT visibility FROM roomserver_rooms WHERE room_nid = $1" + +const selectVisibleRoomIDsSQL = "" + + "SELECT room_id FROM roomserver_rooms WHERE visibility = true" + const updateLatestEventNIDsSQL = "" + "UPDATE roomserver_rooms SET latest_event_nids = $2, last_event_sent_nid = $3, state_snapshot_nid = $4 WHERE room_nid = $1" +const updateVisibilityForRoomNIDSQL = "" + + "UPDATE roomserver_rooms SET visibility = $1 WHERE room_nid = $2" + type roomStatements struct { insertRoomNIDStmt *sql.Stmt selectRoomNIDStmt *sql.Stmt selectLatestEventNIDsStmt *sql.Stmt selectLatestEventNIDsForUpdateStmt *sql.Stmt + selectVisibilityForRoomNIDStmt *sql.Stmt + selectVisibleRoomIDsStmt *sql.Stmt updateLatestEventNIDsStmt *sql.Stmt + updateVisibilityForRoomNIDStmt *sql.Stmt } func (s *roomStatements) prepare(db *sql.DB) (err error) { @@ -76,7 +92,10 @@ func (s *roomStatements) prepare(db *sql.DB) (err error) { {&s.selectRoomNIDStmt, selectRoomNIDSQL}, {&s.selectLatestEventNIDsStmt, selectLatestEventNIDsSQL}, {&s.selectLatestEventNIDsForUpdateStmt, selectLatestEventNIDsForUpdateSQL}, + {&s.selectVisibilityForRoomNIDStmt, selectVisibilityForRoomNIDSQL}, + {&s.selectVisibleRoomIDsStmt, selectVisibleRoomIDsSQL}, {&s.updateLatestEventNIDsStmt, updateLatestEventNIDsSQL}, + {&s.updateVisibilityForRoomNIDStmt, updateVisibilityForRoomNIDSQL}, }.prepare(db) } @@ -123,6 +142,35 @@ func (s *roomStatements) selectLatestEventsNIDsForUpdate(txn *sql.Tx, roomNID ty return eventNIDs, types.EventNID(lastEventSentNID), types.StateSnapshotNID(stateSnapshotNID), nil } +func (s *roomStatements) selectVisibilityForRoomNID(roomNID types.RoomNID) (bool, error) { + var visibility bool + err := s.selectVisibilityForRoomNIDStmt.QueryRow(roomNID).Scan(&visibility) + return visibility, err +} + +func (s *roomStatements) selectVisibleRoomIDs() ([]string, error) { + roomIDs := []string{} + rows, err := s.selectVisibleRoomIDsStmt.Query() + if err != nil { + return roomIDs, err + } + + for rows.Next() { + var roomID string + if err = rows.Scan(&roomID); err != nil { + return roomIDs, err + } + roomIDs = append(roomIDs, roomID) + } + + return roomIDs, nil +} + +func (s *roomStatements) updateVisibilityForRoomNID(roomNID types.RoomNID, visibility bool) error { + _, err := s.updateVisibilityForRoomNIDStmt.Exec(visibility, roomNID) + return err +} + func (s *roomStatements) updateLatestEventNIDs( txn *sql.Tx, roomNID types.RoomNID, eventNIDs []types.EventNID, lastEventSentNID types.EventNID, stateSnapshotNID types.StateSnapshotNID, diff --git a/src/github.com/matrix-org/dendrite/roomserver/storage/storage.go b/src/github.com/matrix-org/dendrite/roomserver/storage/storage.go index 3f99e7d85..e459519a2 100644 --- a/src/github.com/matrix-org/dendrite/roomserver/storage/storage.go +++ b/src/github.com/matrix-org/dendrite/roomserver/storage/storage.go @@ -381,3 +381,27 @@ func (d *Database) StateEntriesForTuples( ) ([]types.StateEntryList, error) { return d.statements.bulkSelectFilteredStateBlockEntries(stateBlockNIDs, stateKeyTuples) } + +// IsRoomVisibleFromRoomNID checks the visibility of the room identified by the +// given numeric ID. Returns true if the room is publicly visible, returns false +// if not. +// If there's no room matching this numeric ID, or if the retrieval failed, +// returns an error. +func (d *Database) IsRoomVisibleFromRoomNID(roomNID types.RoomNID) (bool, error) { + return d.statements.selectVisibilityForRoomNID(roomNID) +} + +// GetVisibleRoomIDs returns an array of string containing the room IDs of the +// rooms that are publicly visible. +// Returns an error if the retrieval failed. +func (d *Database) GetVisibleRoomIDs() ([]string, error) { + return d.statements.selectVisibleRoomIDs() +} + +// UpdateRoomVisibility updates the visibility for a room to the given value: +// true means that the room is publicly visible, false means that the room isn't +// publicly visible. +// Returns an error if the update failed. +func (d *Database) UpdateRoomVisibility(roomNID types.RoomNID, visibility bool) error { + return d.statements.updateVisibilityForRoomNID(roomNID, visibility) +} From 706be4edb6a44f4c3221d5f2f316a5c2eb205034 Mon Sep 17 00:00:00 2001 From: Brendan Abolivier Date: Tue, 8 Aug 2017 15:41:26 +0100 Subject: [PATCH 03/18] Add function for batch aliases retrieval --- .../roomserver/storage/room_aliases_table.go | 36 ++++++++++++++++--- .../dendrite/roomserver/storage/storage.go | 7 ++++ 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/src/github.com/matrix-org/dendrite/roomserver/storage/room_aliases_table.go b/src/github.com/matrix-org/dendrite/roomserver/storage/room_aliases_table.go index 433835d7a..b08ce96cd 100644 --- a/src/github.com/matrix-org/dendrite/roomserver/storage/room_aliases_table.go +++ b/src/github.com/matrix-org/dendrite/roomserver/storage/room_aliases_table.go @@ -39,14 +39,18 @@ const selectRoomIDFromAliasSQL = "" + const selectAliasesFromRoomIDSQL = "" + "SELECT alias FROM roomserver_room_aliases WHERE room_id = $1" +const selectAliasesFromRoomIDsSQL = "" + + "SELECT alias, room_id FROM roomserver_room_aliases WHERE room_id = ANY($1)" + const deleteRoomAliasSQL = "" + "DELETE FROM roomserver_room_aliases WHERE alias = $1" type roomAliasesStatements struct { - insertRoomAliasStmt *sql.Stmt - selectRoomIDFromAliasStmt *sql.Stmt - selectAliasesFromRoomIDStmt *sql.Stmt - deleteRoomAliasStmt *sql.Stmt + insertRoomAliasStmt *sql.Stmt + selectRoomIDFromAliasStmt *sql.Stmt + selectAliasesFromRoomIDStmt *sql.Stmt + selectAliasesFromRoomIDsStmt *sql.Stmt + deleteRoomAliasStmt *sql.Stmt } func (s *roomAliasesStatements) prepare(db *sql.DB) (err error) { @@ -58,6 +62,7 @@ func (s *roomAliasesStatements) prepare(db *sql.DB) (err error) { {&s.insertRoomAliasStmt, insertRoomAliasSQL}, {&s.selectRoomIDFromAliasStmt, selectRoomIDFromAliasSQL}, {&s.selectAliasesFromRoomIDStmt, selectAliasesFromRoomIDSQL}, + {&s.selectAliasesFromRoomIDsStmt, selectAliasesFromRoomIDsSQL}, {&s.deleteRoomAliasStmt, deleteRoomAliasSQL}, }.prepare(db) } @@ -94,6 +99,29 @@ func (s *roomAliasesStatements) selectAliasesFromRoomID(roomID string) (aliases return } +func (s *roomAliasesStatements) selectAliasesFromRoomIDs(roomIDs []string) (aliases map[string][]string, err error) { + aliases = make(map[string][]string) + rows, err := s.selectAliasesFromRoomIDsStmt.Query(roomIDs) + if err != nil { + return + } + + for rows.Next() { + var alias, roomID string + if err = rows.Scan(&alias, &roomID); err != nil { + return + } + + if len(aliases[roomID]) > 0 { + aliases[roomID] = append(aliases[roomID], alias) + } else { + aliases[roomID] = []string{alias} + } + } + + return +} + func (s *roomAliasesStatements) deleteRoomAlias(alias string) (err error) { _, err = s.deleteRoomAliasStmt.Exec(alias) return diff --git a/src/github.com/matrix-org/dendrite/roomserver/storage/storage.go b/src/github.com/matrix-org/dendrite/roomserver/storage/storage.go index e459519a2..09f1fe8ff 100644 --- a/src/github.com/matrix-org/dendrite/roomserver/storage/storage.go +++ b/src/github.com/matrix-org/dendrite/roomserver/storage/storage.go @@ -370,6 +370,13 @@ func (d *Database) GetAliasesFromRoomID(roomID string) ([]string, error) { return d.statements.selectAliasesFromRoomID(roomID) } +// GetAliasesFromRoomIDs returns a map of the aliases bound to a given set of +// room IDs, ordered by room ID (ie map[roomID] = []alias) +// Returns an error if the retrieval failed +func (d *Database) GetAliasesFromRoomIDs(roomIDs []string) (map[string][]string, error) { + return d.statements.selectAliasesFromRoomIDs(roomIDs) +} + // RemoveRoomAlias implements alias.RoomserverAliasAPIDB func (d *Database) RemoveRoomAlias(alias string) error { return d.statements.deleteRoomAlias(alias) From a2586d6d62967d47145694843bc1e6ede9e0c349 Mon Sep 17 00:00:00 2001 From: Brendan Abolivier Date: Tue, 8 Aug 2017 17:31:25 +0100 Subject: [PATCH 04/18] Move misplaced comment --- src/github.com/matrix-org/dendrite/roomserver/api/alias.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/github.com/matrix-org/dendrite/roomserver/api/alias.go b/src/github.com/matrix-org/dendrite/roomserver/api/alias.go index bb65c3ae3..999b818cf 100644 --- a/src/github.com/matrix-org/dendrite/roomserver/api/alias.go +++ b/src/github.com/matrix-org/dendrite/roomserver/api/alias.go @@ -115,11 +115,11 @@ func (h *httpRoomserverAliasAPI) GetAliasRoomID( request *GetAliasRoomIDRequest, response *GetAliasRoomIDResponse, ) error { - // RemoveRoomAlias implements RoomserverAliasAPI apiURL := h.roomserverURL + RoomserverGetAliasRoomIDPath return postJSON(h.httpClient, apiURL, request, response) } +// RemoveRoomAlias implements RoomserverAliasAPI func (h *httpRoomserverAliasAPI) RemoveRoomAlias( request *RemoveRoomAliasRequest, response *RemoveRoomAliasResponse, From 135a811fc70ad3f20d5ddcddbe02745bacef100e Mon Sep 17 00:00:00 2001 From: Brendan Abolivier Date: Tue, 8 Aug 2017 17:56:36 +0100 Subject: [PATCH 05/18] Fix indentation --- .../matrix-org/dendrite/roomserver/storage/rooms_table.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/github.com/matrix-org/dendrite/roomserver/storage/rooms_table.go b/src/github.com/matrix-org/dendrite/roomserver/storage/rooms_table.go index 5858dd88a..2eae7badd 100644 --- a/src/github.com/matrix-org/dendrite/roomserver/storage/rooms_table.go +++ b/src/github.com/matrix-org/dendrite/roomserver/storage/rooms_table.go @@ -37,10 +37,10 @@ CREATE TABLE IF NOT EXISTS roomserver_rooms ( -- The state of the room after the current set of latest events. -- This will be 0 if there are no latest events in the room. state_snapshot_nid BIGINT NOT NULL DEFAULT 0, - -- The visibility of the room. - -- This will be true if the room has a public visibility, and false if it - -- has a private visibility. - visibility BOOLEAN NOT NULL DEFAULT false + -- The visibility of the room. + -- This will be true if the room has a public visibility, and false if it + -- has a private visibility. + visibility BOOLEAN NOT NULL DEFAULT false ); ` From 41b641138900781b7974883d8d54b6fbe0d3cd07 Mon Sep 17 00:00:00 2001 From: Brendan Abolivier Date: Tue, 8 Aug 2017 18:41:35 +0100 Subject: [PATCH 06/18] Rename for more accuracy --- .../roomserver/storage/rooms_table.go | 10 ++++---- .../dendrite/roomserver/storage/storage.go | 25 ++++++------------- 2 files changed, 12 insertions(+), 23 deletions(-) diff --git a/src/github.com/matrix-org/dendrite/roomserver/storage/rooms_table.go b/src/github.com/matrix-org/dendrite/roomserver/storage/rooms_table.go index 2eae7badd..76b3f67f6 100644 --- a/src/github.com/matrix-org/dendrite/roomserver/storage/rooms_table.go +++ b/src/github.com/matrix-org/dendrite/roomserver/storage/rooms_table.go @@ -62,7 +62,7 @@ const selectLatestEventNIDsForUpdateSQL = "" + const selectVisibilityForRoomNIDSQL = "" + "SELECT visibility FROM roomserver_rooms WHERE room_nid = $1" -const selectVisibleRoomIDsSQL = "" + +const selectPublicRoomIDsSQL = "" + "SELECT room_id FROM roomserver_rooms WHERE visibility = true" const updateLatestEventNIDsSQL = "" + @@ -77,7 +77,7 @@ type roomStatements struct { selectLatestEventNIDsStmt *sql.Stmt selectLatestEventNIDsForUpdateStmt *sql.Stmt selectVisibilityForRoomNIDStmt *sql.Stmt - selectVisibleRoomIDsStmt *sql.Stmt + selectPublicRoomIDsStmt *sql.Stmt updateLatestEventNIDsStmt *sql.Stmt updateVisibilityForRoomNIDStmt *sql.Stmt } @@ -93,7 +93,7 @@ func (s *roomStatements) prepare(db *sql.DB) (err error) { {&s.selectLatestEventNIDsStmt, selectLatestEventNIDsSQL}, {&s.selectLatestEventNIDsForUpdateStmt, selectLatestEventNIDsForUpdateSQL}, {&s.selectVisibilityForRoomNIDStmt, selectVisibilityForRoomNIDSQL}, - {&s.selectVisibleRoomIDsStmt, selectVisibleRoomIDsSQL}, + {&s.selectPublicRoomIDsStmt, selectPublicRoomIDsSQL}, {&s.updateLatestEventNIDsStmt, updateLatestEventNIDsSQL}, {&s.updateVisibilityForRoomNIDStmt, updateVisibilityForRoomNIDSQL}, }.prepare(db) @@ -148,9 +148,9 @@ func (s *roomStatements) selectVisibilityForRoomNID(roomNID types.RoomNID) (bool return visibility, err } -func (s *roomStatements) selectVisibleRoomIDs() ([]string, error) { +func (s *roomStatements) selectPublicRoomIDs() ([]string, error) { roomIDs := []string{} - rows, err := s.selectVisibleRoomIDsStmt.Query() + rows, err := s.selectPublicRoomIDsStmt.Query() if err != nil { return roomIDs, err } diff --git a/src/github.com/matrix-org/dendrite/roomserver/storage/storage.go b/src/github.com/matrix-org/dendrite/roomserver/storage/storage.go index 09f1fe8ff..782da7635 100644 --- a/src/github.com/matrix-org/dendrite/roomserver/storage/storage.go +++ b/src/github.com/matrix-org/dendrite/roomserver/storage/storage.go @@ -370,9 +370,7 @@ func (d *Database) GetAliasesFromRoomID(roomID string) ([]string, error) { return d.statements.selectAliasesFromRoomID(roomID) } -// GetAliasesFromRoomIDs returns a map of the aliases bound to a given set of -// room IDs, ordered by room ID (ie map[roomID] = []alias) -// Returns an error if the retrieval failed +// GetAliasesFromRoomIDs implements publicroom.RoomserverPublicRoomAPIDB func (d *Database) GetAliasesFromRoomIDs(roomIDs []string) (map[string][]string, error) { return d.statements.selectAliasesFromRoomIDs(roomIDs) } @@ -389,26 +387,17 @@ func (d *Database) StateEntriesForTuples( return d.statements.bulkSelectFilteredStateBlockEntries(stateBlockNIDs, stateKeyTuples) } -// IsRoomVisibleFromRoomNID checks the visibility of the room identified by the -// given numeric ID. Returns true if the room is publicly visible, returns false -// if not. -// If there's no room matching this numeric ID, or if the retrieval failed, -// returns an error. -func (d *Database) IsRoomVisibleFromRoomNID(roomNID types.RoomNID) (bool, error) { +// IsRoomPublic implements publicroom.RoomserverPublicRoomAPIDB +func (d *Database) IsRoomPublic(roomNID types.RoomNID) (bool, error) { return d.statements.selectVisibilityForRoomNID(roomNID) } -// GetVisibleRoomIDs returns an array of string containing the room IDs of the -// rooms that are publicly visible. -// Returns an error if the retrieval failed. -func (d *Database) GetVisibleRoomIDs() ([]string, error) { - return d.statements.selectVisibleRoomIDs() +// GetPublicRoomIDs implements publicroom.RoomserverPublicRoomAPIDB +func (d *Database) GetPublicRoomIDs() ([]string, error) { + return d.statements.selectPublicRoomIDs() } -// UpdateRoomVisibility updates the visibility for a room to the given value: -// true means that the room is publicly visible, false means that the room isn't -// publicly visible. -// Returns an error if the update failed. +// UpdateRoomVisibility implements publicroom.RoomserverPublicRoomAPIDB func (d *Database) UpdateRoomVisibility(roomNID types.RoomNID, visibility bool) error { return d.statements.updateVisibilityForRoomNID(roomNID, visibility) } From 584ebe772567a967886a976e1ca78897a447288b Mon Sep 17 00:00:00 2001 From: Brendan Abolivier Date: Tue, 8 Aug 2017 19:44:41 +0100 Subject: [PATCH 07/18] Add API and retrieval and update of visibility for a single room --- .../dendrite/roomserver/api/public_room.go | 141 ++++++++++++++++ .../roomserver/publicroom/public_room.go | 158 ++++++++++++++++++ 2 files changed, 299 insertions(+) create mode 100644 src/github.com/matrix-org/dendrite/roomserver/api/public_room.go create mode 100644 src/github.com/matrix-org/dendrite/roomserver/publicroom/public_room.go diff --git a/src/github.com/matrix-org/dendrite/roomserver/api/public_room.go b/src/github.com/matrix-org/dendrite/roomserver/api/public_room.go new file mode 100644 index 000000000..ba8ec7421 --- /dev/null +++ b/src/github.com/matrix-org/dendrite/roomserver/api/public_room.go @@ -0,0 +1,141 @@ +// Copyright 2017 Vector Creations Ltd +// +// 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 api + +import ( + "net/http" +) + +// SetRoomVisibilityRequest is a request to SetRoomVisibility +type SetRoomVisibilityRequest struct { + // ID of the room of which the visibility will be updated + RoomID string `json:"room_id"` + // The new visibility of the room. Either "public" or "private" + Visibility string `json:"visibility"` +} + +// SetRoomVisibilityResponse is a response to SetRoomVisibility +type SetRoomVisibilityResponse struct{} + +// GetRoomVisibilityRequest is a request to GetRoomVisibility +type GetRoomVisibilityRequest struct { + // ID of the room to get the visibility of + RoomID string `json:"room_id"` +} + +// GetRoomVisibilityResponse is a response to GetRoomVisibility +type GetRoomVisibilityResponse struct { + // Visibility of the room. Either "public" or "private" + Visibility string `json:"visibility"` +} + +// GetPublicRoomsRequest is a request to GetPublicRooms +type GetPublicRoomsRequest struct { + Limit int16 `json:"limit"` + Since string `json:"since"` +} + +// GetPublicRoomsResponse is a response to GetPublicRooms +type GetPublicRoomsResponse struct { + Chunk []PublicRoomsChunk `json:"chunk"` + NextBatch string `json:"next_batch"` + PrevBatch string `json:"prev_batch"` + TotalRoomCountEstimate int64 `json:"total_room_count_estimate"` +} + +// PublicRoomsChunk implements the PublicRoomsChunk structure from the Matrix spec +type PublicRoomsChunk struct { + RoomID string `json:"room_id"` + Aliases []string `json:"aliases"` + CanonicalAlias string `json:"canonical_alias"` + Name string `json:"name"` + Topic string `json:"topic"` + AvatarURL string `json:"avatar_url"` + JoinedMembers int64 `json:"num_joined_members"` + WorldReadable bool `json:"world_readable"` + GuestCanJoin bool `json:"guest_can_join"` +} + +// RoomserverPublicRoomAPI is used to update or retrieve the visibility setting +// of a room, or to retrieve all of the public rooms +type RoomserverPublicRoomAPI interface { + // Set the visibility for a room + SetRoomVisibility( + req *SetRoomVisibilityRequest, + response *SetRoomVisibilityResponse, + ) error + + // Get the visibility for a room + GetRoomVisibility( + req *GetRoomVisibilityRequest, + response *GetRoomVisibilityResponse, + ) error + + // Get all rooms publicly visible rooms on the server + GetPublicRooms( + req *GetPublicRoomsRequest, + response *GetPublicRoomsResponse, + ) error +} + +// RoomserverSetRoomVisibilityPath is the HTTP path for the SetRoomVisibility API +const RoomserverSetRoomVisibilityPath = "/api/roomserver/setRoomVisibility" + +// RoomserverGetRoomVisibilityPath is the HTTP path for the GetRoomVisibility API +const RoomserverGetRoomVisibilityPath = "/api/roomserver/getRoomVisibility" + +// RoomserverGetPublicRoomsPath is the HTTP path for the GetPublicRooms API +const RoomserverGetPublicRoomsPath = "/api/roomserver/getPublicRooms" + +// NewRoomserverPublicRoomAPIHTTP creates a RoomserverPublicRoomAPI implemented by talking to a HTTP POST API. +// If httpClient is nil then it uses the http.DefaultClient +func NewRoomserverPublicRoomAPIHTTP(roomserverURL string, httpClient *http.Client) RoomserverPublicRoomAPI { + if httpClient == nil { + httpClient = http.DefaultClient + } + return &httpRoomserverPublicRoomAPI{roomserverURL, httpClient} +} + +type httpRoomserverPublicRoomAPI struct { + roomserverURL string + httpClient *http.Client +} + +// SetRoomVisibility implements RoomserverPublicRoomAPI +func (h *httpRoomserverPublicRoomAPI) SetRoomVisibility( + request *SetRoomVisibilityRequest, + response *SetRoomVisibilityResponse, +) error { + apiURL := h.roomserverURL + RoomserverSetRoomVisibilityPath + return postJSON(h.httpClient, apiURL, request, response) +} + +// GetRoomVisibility implements RoomserverPublicRoomAPI +func (h *httpRoomserverPublicRoomAPI) GetRoomVisibility( + request *GetRoomVisibilityRequest, + response *GetRoomVisibilityResponse, +) error { + apiURL := h.roomserverURL + RoomserverGetRoomVisibilityPath + return postJSON(h.httpClient, apiURL, request, response) +} + +// GetPublicRooms implements RoomserverPublicRoomAPI +func (h *httpRoomserverPublicRoomAPI) GetPublicRooms( + request *GetPublicRoomsRequest, + response *GetPublicRoomsResponse, +) error { + apiURL := h.roomserverURL + RoomserverGetPublicRoomsPath + return postJSON(h.httpClient, apiURL, request, response) +} diff --git a/src/github.com/matrix-org/dendrite/roomserver/publicroom/public_room.go b/src/github.com/matrix-org/dendrite/roomserver/publicroom/public_room.go new file mode 100644 index 000000000..a318489a6 --- /dev/null +++ b/src/github.com/matrix-org/dendrite/roomserver/publicroom/public_room.go @@ -0,0 +1,158 @@ +// Copyright 2017 Vector Creations Ltd +// +// 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 publicroom + +import ( + "encoding/json" + "errors" + "net/http" + + "github.com/matrix-org/dendrite/common" + "github.com/matrix-org/dendrite/roomserver/api" + "github.com/matrix-org/dendrite/roomserver/types" + "github.com/matrix-org/util" +) + +// RoomserverPublicRoomAPIDatabase has the storage APIs needed to implement the +// public room API. +type RoomserverPublicRoomAPIDatabase interface { + // Lookup the numeric ID for the room. + // Returns 0 if the room doesn't exists. + // Returns an error if there was a problem talking to the database. + RoomNID(roomID string) (types.RoomNID, error) + // Checks the visibility of the room identified by the given numeric ID. + // Returns true if the room is publicly visible, returns false if not. + // If there's no room matching this numeric ID, or if the retrieval failed, + // returns an error. + IsRoomPublic(roomNID types.RoomNID) (bool, error) + // Returns an array of string containing the room IDs of the rooms that are + // publicly visible. + // Returns an error if the retrieval failed. + GetPublicRoomIDs() ([]string, error) + // Updates the visibility for a room to the given value: true means that the + // room is publicly visible, false means that the room isn't publicly visible. + // Returns an error if the update failed. + UpdateRoomVisibility(roomNID types.RoomNID, visibility bool) error + // Returns a map of the aliases bound to a given set of room IDs, ordered + // by room ID (ie map[roomID] = []alias) + // Returns an error if the retrieval failed + GetAliasesFromRoomIDs(roomIDs []string) (map[string][]string, error) +} + +// RoomserverPublicRoomAPI is an implementation of api.RoomserverPublicRoomAPI +type RoomserverPublicRoomAPI struct { + DB RoomserverPublicRoomAPIDatabase +} + +// SetRoomVisibility implements api.RoomserverPublicRoomAPI +func (r *RoomserverPublicRoomAPI) SetRoomVisibility( + req *api.SetRoomVisibilityRequest, + response *api.SetRoomVisibilityResponse, +) error { + roomNID, err := r.DB.RoomNID(req.RoomID) + if err != nil || roomNID == 0 { + return err + } + + var visibility bool + if req.Visibility == "public" { + visibility = true + } else if req.Visibility == "private" { + visibility = false + } else { + return errors.New("Invalid visibility setting") + } + + if err = r.DB.UpdateRoomVisibility(roomNID, visibility); err != nil { + return err + } + + return nil +} + +// GetRoomVisibility implements api.RoomserverPublicRoomAPI +func (r *RoomserverPublicRoomAPI) GetRoomVisibility( + req *api.GetRoomVisibilityRequest, + response *api.GetRoomVisibilityResponse, +) error { + roomNID, err := r.DB.RoomNID(req.RoomID) + if err != nil || roomNID == 0 { + return err + } + + if isPublic, err := r.DB.IsRoomPublic(roomNID); err != nil { + return err + } else if isPublic { + response.Visibility = "public" + } else { + response.Visibility = "private" + } + + return nil +} + +// GetPublicRooms implements api.RoomserverPublicRoomAPI +func (r *RoomserverPublicRoomAPI) GetPublicRooms( + req *api.GetPublicRoomsRequest, + response *api.GetPublicRoomsResponse, +) error { + return nil +} + +// SetupHTTP adds the RoomserverPublicRoomAPI handlers to the http.ServeMux. +func (r *RoomserverPublicRoomAPI) SetupHTTP(servMux *http.ServeMux) { + servMux.Handle( + api.RoomserverSetRoomVisibilityPath, + common.MakeAPI("setRoomVisibility", func(req *http.Request) util.JSONResponse { + var request api.SetRoomVisibilityRequest + var response api.SetRoomVisibilityResponse + if err := json.NewDecoder(req.Body).Decode(&request); err != nil { + return util.ErrorResponse(err) + } + if err := r.SetRoomVisibility(&request, &response); err != nil { + return util.ErrorResponse(err) + } + return util.JSONResponse{Code: 200, JSON: &response} + }), + ) + servMux.Handle( + api.RoomserverGetRoomVisibilityPath, + common.MakeAPI("getRoomVisibility", func(req *http.Request) util.JSONResponse { + var request api.GetRoomVisibilityRequest + var response api.GetRoomVisibilityResponse + if err := json.NewDecoder(req.Body).Decode(&request); err != nil { + return util.ErrorResponse(err) + } + if err := r.GetRoomVisibility(&request, &response); err != nil { + return util.ErrorResponse(err) + } + return util.JSONResponse{Code: 200, JSON: &response} + }), + ) + servMux.Handle( + api.RoomserverGetPublicRoomsPath, + common.MakeAPI("getPublicRooms", func(req *http.Request) util.JSONResponse { + var request api.GetPublicRoomsRequest + var response api.GetPublicRoomsResponse + if err := json.NewDecoder(req.Body).Decode(&request); err != nil { + return util.ErrorResponse(err) + } + if err := r.GetPublicRooms(&request, &response); err != nil { + return util.ErrorResponse(err) + } + return util.JSONResponse{Code: 200, JSON: &response} + }), + ) +} From 24a529bb42aaa324f18d23c5c4eb41cdafefeb5a Mon Sep 17 00:00:00 2001 From: Brendan Abolivier Date: Wed, 9 Aug 2017 13:22:17 +0100 Subject: [PATCH 08/18] Basic API for handling room directory --- .../dendrite/roomserver/api/public_room.go | 20 ++++++++-------- .../roomserver/publicroom/public_room.go | 23 +++++++++++++++++++ 2 files changed, 33 insertions(+), 10 deletions(-) diff --git a/src/github.com/matrix-org/dendrite/roomserver/api/public_room.go b/src/github.com/matrix-org/dendrite/roomserver/api/public_room.go index ba8ec7421..b9edb62b4 100644 --- a/src/github.com/matrix-org/dendrite/roomserver/api/public_room.go +++ b/src/github.com/matrix-org/dendrite/roomserver/api/public_room.go @@ -49,7 +49,7 @@ type GetPublicRoomsRequest struct { // GetPublicRoomsResponse is a response to GetPublicRooms type GetPublicRoomsResponse struct { - Chunk []PublicRoomsChunk `json:"chunk"` + Chunks []PublicRoomsChunk `json:"chunk"` NextBatch string `json:"next_batch"` PrevBatch string `json:"prev_batch"` TotalRoomCountEstimate int64 `json:"total_room_count_estimate"` @@ -57,15 +57,15 @@ type GetPublicRoomsResponse struct { // PublicRoomsChunk implements the PublicRoomsChunk structure from the Matrix spec type PublicRoomsChunk struct { - RoomID string `json:"room_id"` - Aliases []string `json:"aliases"` - CanonicalAlias string `json:"canonical_alias"` - Name string `json:"name"` - Topic string `json:"topic"` - AvatarURL string `json:"avatar_url"` - JoinedMembers int64 `json:"num_joined_members"` - WorldReadable bool `json:"world_readable"` - GuestCanJoin bool `json:"guest_can_join"` + RoomID string `json:"room_id"` + Aliases []string `json:"aliases"` + CanonicalAlias string `json:"canonical_alias"` + Name string `json:"name"` + Topic string `json:"topic"` + AvatarURL string `json:"avatar_url"` + NumJoinedMembers int64 `json:"num_joined_members"` + WorldReadable bool `json:"world_readable"` + GuestCanJoin bool `json:"guest_can_join"` } // RoomserverPublicRoomAPI is used to update or retrieve the visibility setting diff --git a/src/github.com/matrix-org/dendrite/roomserver/publicroom/public_room.go b/src/github.com/matrix-org/dendrite/roomserver/publicroom/public_room.go index a318489a6..cc1584836 100644 --- a/src/github.com/matrix-org/dendrite/roomserver/publicroom/public_room.go +++ b/src/github.com/matrix-org/dendrite/roomserver/publicroom/public_room.go @@ -108,6 +108,29 @@ func (r *RoomserverPublicRoomAPI) GetPublicRooms( req *api.GetPublicRoomsRequest, response *api.GetPublicRoomsResponse, ) error { + roomIDs, err := r.DB.GetPublicRoomIDs() + if err != nil { + return err + } + + rooms, err := r.DB.GetAliasesFromRoomIDs(roomIDs) + if err != nil { + return err + } + + var chunks []api.PublicRoomsChunk + for room, aliases := range rooms { + chunk := api.PublicRoomsChunk{ + RoomID: room, + Aliases: aliases, + NumJoinedMembers: 0, + WorldReadable: true, + GuestCanJoin: true, + } + chunks = append(chunks, chunk) + } + + response.Chunks = chunks return nil } From b794d23594fb26ca4a0b4b2c49eabcc701143f5e Mon Sep 17 00:00:00 2001 From: Brendan Abolivier Date: Wed, 9 Aug 2017 13:24:37 +0100 Subject: [PATCH 09/18] Omit optional fields if empty --- .../dendrite/roomserver/api/public_room.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/github.com/matrix-org/dendrite/roomserver/api/public_room.go b/src/github.com/matrix-org/dendrite/roomserver/api/public_room.go index b9edb62b4..8f7302954 100644 --- a/src/github.com/matrix-org/dendrite/roomserver/api/public_room.go +++ b/src/github.com/matrix-org/dendrite/roomserver/api/public_room.go @@ -50,19 +50,19 @@ type GetPublicRoomsRequest struct { // GetPublicRoomsResponse is a response to GetPublicRooms type GetPublicRoomsResponse struct { Chunks []PublicRoomsChunk `json:"chunk"` - NextBatch string `json:"next_batch"` - PrevBatch string `json:"prev_batch"` - TotalRoomCountEstimate int64 `json:"total_room_count_estimate"` + NextBatch string `json:"next_batch,omitempty"` + PrevBatch string `json:"prev_batch,omitempty"` + TotalRoomCountEstimate int64 `json:"total_room_count_estimate,omitempty"` } // PublicRoomsChunk implements the PublicRoomsChunk structure from the Matrix spec type PublicRoomsChunk struct { RoomID string `json:"room_id"` - Aliases []string `json:"aliases"` - CanonicalAlias string `json:"canonical_alias"` - Name string `json:"name"` - Topic string `json:"topic"` - AvatarURL string `json:"avatar_url"` + Aliases []string `json:"aliases,omitempty"` + CanonicalAlias string `json:"canonical_alias,omitempty"` + Name string `json:"name,omitempty"` + Topic string `json:"topic,omitempty"` + AvatarURL string `json:"avatar_url,omitempty"` NumJoinedMembers int64 `json:"num_joined_members"` WorldReadable bool `json:"world_readable"` GuestCanJoin bool `json:"guest_can_join"` From f23f38620750f4e5181f19bdae1da4f7087842cb Mon Sep 17 00:00:00 2001 From: Brendan Abolivier Date: Wed, 9 Aug 2017 13:34:36 +0100 Subject: [PATCH 10/18] TODO --- .../matrix-org/dendrite/roomserver/publicroom/public_room.go | 1 + 1 file changed, 1 insertion(+) diff --git a/src/github.com/matrix-org/dendrite/roomserver/publicroom/public_room.go b/src/github.com/matrix-org/dendrite/roomserver/publicroom/public_room.go index cc1584836..e04ca3121 100644 --- a/src/github.com/matrix-org/dendrite/roomserver/publicroom/public_room.go +++ b/src/github.com/matrix-org/dendrite/roomserver/publicroom/public_room.go @@ -108,6 +108,7 @@ func (r *RoomserverPublicRoomAPI) GetPublicRooms( req *api.GetPublicRoomsRequest, response *api.GetPublicRoomsResponse, ) error { + // TODO: Limit by req.Limit and offset by req.Since roomIDs, err := r.DB.GetPublicRoomIDs() if err != nil { return err From 93a50afaf54f35c6014c4725c75f3d1462404644 Mon Sep 17 00:00:00 2001 From: Brendan Abolivier Date: Wed, 9 Aug 2017 13:56:40 +0100 Subject: [PATCH 11/18] Various fixes in the roomserver public room API --- .../dendrite/roomserver/publicroom/public_room.go | 10 ++++++---- .../dendrite/roomserver/storage/room_aliases_table.go | 4 +++- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/github.com/matrix-org/dendrite/roomserver/publicroom/public_room.go b/src/github.com/matrix-org/dendrite/roomserver/publicroom/public_room.go index e04ca3121..6ee6f9e9b 100644 --- a/src/github.com/matrix-org/dendrite/roomserver/publicroom/public_room.go +++ b/src/github.com/matrix-org/dendrite/roomserver/publicroom/public_room.go @@ -114,16 +114,18 @@ func (r *RoomserverPublicRoomAPI) GetPublicRooms( return err } - rooms, err := r.DB.GetAliasesFromRoomIDs(roomIDs) + aliases, err := r.DB.GetAliasesFromRoomIDs(roomIDs) if err != nil { return err } - var chunks []api.PublicRoomsChunk - for room, aliases := range rooms { + chunks := []api.PublicRoomsChunk{} + // Iterate over the array of aliases instead of the array of rooms, because + // a room must have at least one alias to be listed + for room, as := range aliases { chunk := api.PublicRoomsChunk{ RoomID: room, - Aliases: aliases, + Aliases: as, NumJoinedMembers: 0, WorldReadable: true, GuestCanJoin: true, diff --git a/src/github.com/matrix-org/dendrite/roomserver/storage/room_aliases_table.go b/src/github.com/matrix-org/dendrite/roomserver/storage/room_aliases_table.go index b08ce96cd..2c47a35d5 100644 --- a/src/github.com/matrix-org/dendrite/roomserver/storage/room_aliases_table.go +++ b/src/github.com/matrix-org/dendrite/roomserver/storage/room_aliases_table.go @@ -16,6 +16,8 @@ package storage import ( "database/sql" + + "github.com/lib/pq" ) const roomAliasesSchema = ` @@ -101,7 +103,7 @@ func (s *roomAliasesStatements) selectAliasesFromRoomID(roomID string) (aliases func (s *roomAliasesStatements) selectAliasesFromRoomIDs(roomIDs []string) (aliases map[string][]string, err error) { aliases = make(map[string][]string) - rows, err := s.selectAliasesFromRoomIDsStmt.Query(roomIDs) + rows, err := s.selectAliasesFromRoomIDsStmt.Query(pq.StringArray(roomIDs)) if err != nil { return } From 4e4dc8a9c234152040ff84944feaa420ff0fbb6b Mon Sep 17 00:00:00 2001 From: Brendan Abolivier Date: Wed, 9 Aug 2017 13:57:42 +0100 Subject: [PATCH 12/18] Implement the client API routes --- .../dendrite/clientapi/routing/routing.go | 15 ++---- .../clientapi/writers/room_directory.go | 46 +++++++++++++++++-- .../cmd/dendrite-client-api-server/main.go | 5 +- .../cmd/dendrite-monolith-server/main.go | 16 +++++-- 4 files changed, 60 insertions(+), 22 deletions(-) diff --git a/src/github.com/matrix-org/dendrite/clientapi/routing/routing.go b/src/github.com/matrix-org/dendrite/clientapi/routing/routing.go index 0cd9e8595..410c9f7db 100644 --- a/src/github.com/matrix-org/dendrite/clientapi/routing/routing.go +++ b/src/github.com/matrix-org/dendrite/clientapi/routing/routing.go @@ -41,7 +41,7 @@ const pathPrefixUnstable = "/_matrix/client/unstable" func Setup( apiMux *mux.Router, httpClient *http.Client, cfg config.Dendrite, producer *producers.RoomserverProducer, queryAPI api.RoomserverQueryAPI, - aliasAPI api.RoomserverAliasAPI, + aliasAPI api.RoomserverAliasAPI, publicRoomAPI api.RoomserverPublicRoomAPI, accountDB *accounts.Database, deviceDB *devices.Database, federation *gomatrixserverlib.FederationClient, @@ -136,14 +136,14 @@ func Setup( r0mux.Handle("/directory/list/room/{roomID}", common.MakeAPI("directory_list", func(req *http.Request) util.JSONResponse { vars := mux.Vars(req) - return writers.GetVisibility(req, vars["roomID"]) + return writers.GetVisibility(req, vars["roomID"], publicRoomAPI) }), ).Methods("GET") r0mux.Handle("/directory/list/room/{roomID}", common.MakeAuthAPI("directory_list", deviceDB, func(req *http.Request, device *authtypes.Device) util.JSONResponse { vars := mux.Vars(req) - return writers.SetVisibility(req, *device, vars["roomID"], cfg) + return writers.SetVisibility(req, *device, vars["roomID"], publicRoomAPI) }), ).Methods("PUT", "OPTIONS") @@ -276,14 +276,7 @@ func Setup( r0mux.Handle("/publicRooms", common.MakeAPI("public_rooms", func(req *http.Request) util.JSONResponse { // TODO: Return a list of public rooms - return util.JSONResponse{ - Code: 200, - JSON: struct { - Chunk []struct{} `json:"chunk"` - Start string `json:"start"` - End string `json:"end"` - }{[]struct{}{}, "", ""}, - } + return writers.GetPublicRooms(req, publicRoomAPI) }), ) diff --git a/src/github.com/matrix-org/dendrite/clientapi/writers/room_directory.go b/src/github.com/matrix-org/dendrite/clientapi/writers/room_directory.go index a4c175719..49a73750c 100644 --- a/src/github.com/matrix-org/dendrite/clientapi/writers/room_directory.go +++ b/src/github.com/matrix-org/dendrite/clientapi/writers/room_directory.go @@ -18,7 +18,8 @@ import ( "net/http" "github.com/matrix-org/dendrite/clientapi/auth/authtypes" - "github.com/matrix-org/dendrite/common/config" + "github.com/matrix-org/dendrite/clientapi/httputil" + "github.com/matrix-org/dendrite/roomserver/api" "github.com/matrix-org/util" ) @@ -28,21 +29,58 @@ type roomDirectoryVisibility struct { // GetVisibility implements GET /directory/list/room/{roomID} func GetVisibility( - req *http.Request, roomID string, + req *http.Request, roomID string, publicRoomAPI api.RoomserverPublicRoomAPI, ) util.JSONResponse { + queryReq := api.GetRoomVisibilityRequest{roomID} + var queryRes api.GetRoomVisibilityResponse + if err := publicRoomAPI.GetRoomVisibility(&queryReq, &queryRes); err != nil { + return httputil.LogThenError(req, err) + } + return util.JSONResponse{ Code: 200, - JSON: roomDirectoryVisibility{"public"}, + JSON: roomDirectoryVisibility{queryRes.Visibility}, } } // SetVisibility implements PUT /directory/list/room/{roomID} +// TODO: Check if user has the power leven to edit the room visibility func SetVisibility( req *http.Request, device authtypes.Device, roomID string, - cfg config.Dendrite, + publicRoomAPI api.RoomserverPublicRoomAPI, ) util.JSONResponse { + var r roomDirectoryVisibility + if resErr := httputil.UnmarshalJSONRequest(req, &r); resErr != nil { + return *resErr + } + + queryReq := api.SetRoomVisibilityRequest{ + RoomID: roomID, + Visibility: r.Visibility, + } + var queryRes api.SetRoomVisibilityResponse + if err := publicRoomAPI.SetRoomVisibility(&queryReq, &queryRes); err != nil { + return httputil.LogThenError(req, err) + } + return util.JSONResponse{ Code: 200, JSON: struct{}{}, } } + +// GetPublicRooms implements GET /publicRooms +func GetPublicRooms( + req *http.Request, publicRoomAPI api.RoomserverPublicRoomAPI, +) util.JSONResponse { + queryReq := api.GetPublicRoomsRequest{} + var queryRes api.GetPublicRoomsResponse + if err := publicRoomAPI.GetPublicRooms(&queryReq, &queryRes); err != nil { + return httputil.LogThenError(req, err) + } + + return util.JSONResponse{ + Code: 200, + JSON: queryRes, + } +} diff --git a/src/github.com/matrix-org/dendrite/cmd/dendrite-client-api-server/main.go b/src/github.com/matrix-org/dendrite/cmd/dendrite-client-api-server/main.go index 5d195bee8..beddb2978 100644 --- a/src/github.com/matrix-org/dendrite/cmd/dendrite-client-api-server/main.go +++ b/src/github.com/matrix-org/dendrite/cmd/dendrite-client-api-server/main.go @@ -54,6 +54,7 @@ func main() { queryAPI := api.NewRoomserverQueryAPIHTTP(cfg.RoomServerURL(), nil) aliasAPI := api.NewRoomserverAliasAPIHTTP(cfg.RoomServerURL(), nil) + publicRoomAPI := api.NewRoomserverPublicRoomAPIHTTP(cfg.RoomServerURL(), nil) inputAPI := api.NewRoomserverInputAPIHTTP(cfg.RoomServerURL(), nil) roomserverProducer := producers.NewRoomserverProducer(inputAPI) @@ -108,8 +109,8 @@ func main() { api := mux.NewRouter() routing.Setup( api, http.DefaultClient, *cfg, roomserverProducer, - queryAPI, aliasAPI, accountDB, deviceDB, federation, keyRing, - userUpdateProducer, syncProducer, + queryAPI, aliasAPI, publicRoomAPI, accountDB, deviceDB, federation, + keyRing, userUpdateProducer, syncProducer, ) common.SetupHTTPAPI(http.DefaultServeMux, api) diff --git a/src/github.com/matrix-org/dendrite/cmd/dendrite-monolith-server/main.go b/src/github.com/matrix-org/dendrite/cmd/dendrite-monolith-server/main.go index f0046d7b1..66c77933c 100644 --- a/src/github.com/matrix-org/dendrite/cmd/dendrite-monolith-server/main.go +++ b/src/github.com/matrix-org/dendrite/cmd/dendrite-monolith-server/main.go @@ -33,6 +33,7 @@ import ( roomserver_alias "github.com/matrix-org/dendrite/roomserver/alias" roomserver_input "github.com/matrix-org/dendrite/roomserver/input" + roomserver_publicroom "github.com/matrix-org/dendrite/roomserver/publicroom" roomserver_query "github.com/matrix-org/dendrite/roomserver/query" roomserver_storage "github.com/matrix-org/dendrite/roomserver/storage" @@ -121,9 +122,10 @@ type monolith struct { federation *gomatrixserverlib.FederationClient keyRing gomatrixserverlib.KeyRing - inputAPI *roomserver_input.RoomserverInputAPI - queryAPI *roomserver_query.RoomserverQueryAPI - aliasAPI *roomserver_alias.RoomserverAliasAPI + inputAPI *roomserver_input.RoomserverInputAPI + queryAPI *roomserver_query.RoomserverQueryAPI + aliasAPI *roomserver_alias.RoomserverAliasAPI + publicRoomAPI *roomserver_publicroom.RoomserverPublicRoomAPI roomServerProducer *producers.RoomserverProducer userUpdateProducer *producers.UserUpdateProducer @@ -204,6 +206,10 @@ func (m *monolith) setupRoomServer() { InputAPI: m.inputAPI, QueryAPI: m.queryAPI, } + + m.publicRoomAPI = &roomserver_publicroom.RoomserverPublicRoomAPI{ + DB: m.roomServerDB, + } } func (m *monolith) setupProducers() { @@ -280,8 +286,8 @@ func (m *monolith) setupConsumers() { func (m *monolith) setupAPIs() { clientapi_routing.Setup( m.api, http.DefaultClient, *m.cfg, m.roomServerProducer, - m.queryAPI, m.aliasAPI, m.accountDB, m.deviceDB, m.federation, m.keyRing, - m.userUpdateProducer, m.syncProducer, + m.queryAPI, m.aliasAPI, m.publicRoomAPI, m.accountDB, m.deviceDB, + m.federation, m.keyRing, m.userUpdateProducer, m.syncProducer, ) mediaapi_routing.Setup( From f4a53ee54bb4c8129fdfc32cfc46287296863853 Mon Sep 17 00:00:00 2001 From: Brendan Abolivier Date: Thu, 10 Aug 2017 12:07:28 +0100 Subject: [PATCH 13/18] Support the since and limit parameters --- .../dendrite/clientapi/routing/routing.go | 2 +- .../clientapi/writers/room_directory.go | 26 ++++++++++++++++++- .../dendrite/roomserver/api/public_room.go | 11 ++++++-- .../roomserver/publicroom/public_room.go | 22 ++++++++++++++-- 4 files changed, 55 insertions(+), 6 deletions(-) diff --git a/src/github.com/matrix-org/dendrite/clientapi/routing/routing.go b/src/github.com/matrix-org/dendrite/clientapi/routing/routing.go index 410c9f7db..0ce65e544 100644 --- a/src/github.com/matrix-org/dendrite/clientapi/routing/routing.go +++ b/src/github.com/matrix-org/dendrite/clientapi/routing/routing.go @@ -278,7 +278,7 @@ func Setup( // TODO: Return a list of public rooms return writers.GetPublicRooms(req, publicRoomAPI) }), - ) + ).Methods("GET", "POST", "OPTIONS") unstableMux.Handle("/thirdparty/protocols", common.MakeAPI("thirdparty_protocols", func(req *http.Request) util.JSONResponse { diff --git a/src/github.com/matrix-org/dendrite/clientapi/writers/room_directory.go b/src/github.com/matrix-org/dendrite/clientapi/writers/room_directory.go index 49a73750c..a40e8a288 100644 --- a/src/github.com/matrix-org/dendrite/clientapi/writers/room_directory.go +++ b/src/github.com/matrix-org/dendrite/clientapi/writers/room_directory.go @@ -16,6 +16,7 @@ package writers import ( "net/http" + "strconv" "github.com/matrix-org/dendrite/clientapi/auth/authtypes" "github.com/matrix-org/dendrite/clientapi/httputil" @@ -69,11 +70,14 @@ func SetVisibility( } } -// GetPublicRooms implements GET /publicRooms +// GetPublicRooms implements GET and POST /publicRooms func GetPublicRooms( req *http.Request, publicRoomAPI api.RoomserverPublicRoomAPI, ) util.JSONResponse { queryReq := api.GetPublicRoomsRequest{} + if fillErr := fillPublicRoomsReq(req, &queryReq); fillErr != nil { + return *fillErr + } var queryRes api.GetPublicRoomsResponse if err := publicRoomAPI.GetPublicRooms(&queryReq, &queryRes); err != nil { return httputil.LogThenError(req, err) @@ -84,3 +88,23 @@ func GetPublicRooms( JSON: queryRes, } } + +func fillPublicRoomsReq(httpReq *http.Request, queryReq *api.GetPublicRoomsRequest) *util.JSONResponse { + if httpReq.Method == "GET" { + limit, err := strconv.Atoi(httpReq.FormValue("limit")) + // Atoi returns 0 and an error when trying to parse an empty string + // In that case, we want to assign 0 so we ignore the error + if err != nil && len(httpReq.FormValue("limit")) > 0 { + reqErr := httputil.LogThenError(httpReq, err) + return &reqErr + } + queryReq.Limit = int16(limit) + queryReq.Since = httpReq.FormValue("since") + } else if httpReq.Method == "POST" { + if reqErr := httputil.UnmarshalJSONRequest(httpReq, queryReq); reqErr != nil { + return reqErr + } + } + + return nil +} diff --git a/src/github.com/matrix-org/dendrite/roomserver/api/public_room.go b/src/github.com/matrix-org/dendrite/roomserver/api/public_room.go index 8f7302954..63f7afc7b 100644 --- a/src/github.com/matrix-org/dendrite/roomserver/api/public_room.go +++ b/src/github.com/matrix-org/dendrite/roomserver/api/public_room.go @@ -42,9 +42,16 @@ type GetRoomVisibilityResponse struct { } // GetPublicRoomsRequest is a request to GetPublicRooms +// TODO: Support the "server" request parameter type GetPublicRoomsRequest struct { - Limit int16 `json:"limit"` - Since string `json:"since"` + Limit int16 `json:"limit"` + Since string `json:"since"` + Filter Filter `json:"filter"` +} + +// Filter implements the Filter structure from the Matrix spec +type Filter struct { + SearchTerm string `json:"generic_search_term"` } // GetPublicRoomsResponse is a response to GetPublicRooms diff --git a/src/github.com/matrix-org/dendrite/roomserver/publicroom/public_room.go b/src/github.com/matrix-org/dendrite/roomserver/publicroom/public_room.go index 6ee6f9e9b..1598d2efc 100644 --- a/src/github.com/matrix-org/dendrite/roomserver/publicroom/public_room.go +++ b/src/github.com/matrix-org/dendrite/roomserver/publicroom/public_room.go @@ -18,6 +18,7 @@ import ( "encoding/json" "errors" "net/http" + "strconv" "github.com/matrix-org/dendrite/common" "github.com/matrix-org/dendrite/roomserver/api" @@ -108,7 +109,18 @@ func (r *RoomserverPublicRoomAPI) GetPublicRooms( req *api.GetPublicRoomsRequest, response *api.GetPublicRoomsResponse, ) error { - // TODO: Limit by req.Limit and offset by req.Since + var limit int16 + var offset int64 + + limit = req.Limit + ofst, err := strconv.Atoi(req.Since) + // Atoi returns 0 and an error when trying to parse an empty string + // In that case, we want to assign 0 so we ignore the error + if err != nil && len(req.Since) > 0 { + return err + } + offset = int64(ofst) + roomIDs, err := r.DB.GetPublicRoomIDs() if err != nil { return err @@ -133,7 +145,13 @@ func (r *RoomserverPublicRoomAPI) GetPublicRooms( chunks = append(chunks, chunk) } - response.Chunks = chunks + if limit == 0 { + // If limit is 0, don't limit the results + response.Chunks = chunks[offset:] + } else { + response.Chunks = chunks[offset:limit] + } + return nil } From a99f7de70fd7b40907519ec9894a8709cc81fd44 Mon Sep 17 00:00:00 2001 From: Brendan Abolivier Date: Thu, 10 Aug 2017 12:17:11 +0100 Subject: [PATCH 14/18] Doc + use ParseInt instead of Atoi in roomserver --- .../matrix-org/dendrite/clientapi/writers/room_directory.go | 2 ++ .../matrix-org/dendrite/roomserver/publicroom/public_room.go | 5 ++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/github.com/matrix-org/dendrite/clientapi/writers/room_directory.go b/src/github.com/matrix-org/dendrite/clientapi/writers/room_directory.go index a40e8a288..8298fec9c 100644 --- a/src/github.com/matrix-org/dendrite/clientapi/writers/room_directory.go +++ b/src/github.com/matrix-org/dendrite/clientapi/writers/room_directory.go @@ -89,6 +89,8 @@ func GetPublicRooms( } } +// fillPublicRoomsReq fills the Limit, Since and Filter attributes of a request to the roomserver's +// GetPublicRooms API by parsing the incoming HTTP request func fillPublicRoomsReq(httpReq *http.Request, queryReq *api.GetPublicRoomsRequest) *util.JSONResponse { if httpReq.Method == "GET" { limit, err := strconv.Atoi(httpReq.FormValue("limit")) diff --git a/src/github.com/matrix-org/dendrite/roomserver/publicroom/public_room.go b/src/github.com/matrix-org/dendrite/roomserver/publicroom/public_room.go index 1598d2efc..7045c367b 100644 --- a/src/github.com/matrix-org/dendrite/roomserver/publicroom/public_room.go +++ b/src/github.com/matrix-org/dendrite/roomserver/publicroom/public_room.go @@ -113,13 +113,12 @@ func (r *RoomserverPublicRoomAPI) GetPublicRooms( var offset int64 limit = req.Limit - ofst, err := strconv.Atoi(req.Since) - // Atoi returns 0 and an error when trying to parse an empty string + offset, err := strconv.ParseInt(req.Since, 10, 64) + // ParseInt returns 0 and an error when trying to parse an empty string // In that case, we want to assign 0 so we ignore the error if err != nil && len(req.Since) > 0 { return err } - offset = int64(ofst) roomIDs, err := r.DB.GetPublicRoomIDs() if err != nil { From 9a077b044098dab74adad47e4e37b63a8b66c6b1 Mon Sep 17 00:00:00 2001 From: Brendan Abolivier Date: Fri, 11 Aug 2017 17:41:17 +0100 Subject: [PATCH 15/18] Use NIDs instead of IDs for alias and room retrieval --- .../dendrite/roomserver/alias/alias.go | 53 +++++++++---- .../roomserver/publicroom/public_room.go | 39 ++++++---- .../roomserver/storage/room_aliases_table.go | 75 +++++++++++-------- .../roomserver/storage/rooms_table.go | 59 +++++++++++++-- .../dendrite/roomserver/storage/storage.go | 38 ++++++---- 5 files changed, 179 insertions(+), 85 deletions(-) diff --git a/src/github.com/matrix-org/dendrite/roomserver/alias/alias.go b/src/github.com/matrix-org/dendrite/roomserver/alias/alias.go index faf91bc47..1a35d6770 100644 --- a/src/github.com/matrix-org/dendrite/roomserver/alias/alias.go +++ b/src/github.com/matrix-org/dendrite/roomserver/alias/alias.go @@ -23,21 +23,30 @@ import ( "github.com/matrix-org/dendrite/common" "github.com/matrix-org/dendrite/common/config" "github.com/matrix-org/dendrite/roomserver/api" + "github.com/matrix-org/dendrite/roomserver/types" "github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/util" ) // RoomserverAliasAPIDatabase has the storage APIs needed to implement the alias API. type RoomserverAliasAPIDatabase interface { - // Save a given room alias with the room ID it refers to. + // Lookup the numeric ID for the room. + // Returns 0 if the room doesn't exists. // Returns an error if there was a problem talking to the database. - SetRoomAlias(alias string, roomID string) error - // Lookup the room ID a given alias refers to. + RoomNID(roomID string) (types.RoomNID, error) + // Lookup the ID of a room identified by a given numeric ID. + // Returns an error if there was a problem talking to the database or if no + // room matches the given numeric ID. + RoomID(roomNID types.RoomNID) (string, error) + // Save a given room alias with the room numeric ID it refers to. // Returns an error if there was a problem talking to the database. - GetRoomIDFromAlias(alias string) (string, error) - // Lookup all aliases referring to a given room ID. + SetRoomAlias(alias string, roomNID types.RoomNID) error + // Lookup the room numeric ID a given alias refers to. // Returns an error if there was a problem talking to the database. - GetAliasesFromRoomID(roomID string) ([]string, error) + GetRoomNIDFromAlias(alias string) (types.RoomNID, error) + // Lookup all aliases referring to a given room numeric ID. + // Returns an error if there was a problem talking to the database. + GetAliasesFromRoomNID(roomNID types.RoomNID) ([]string, error) // Remove a given room alias. // Returns an error if there was a problem talking to the database. RemoveRoomAlias(alias string) error @@ -57,24 +66,28 @@ func (r *RoomserverAliasAPI) SetRoomAlias( response *api.SetRoomAliasResponse, ) error { // Check if the alias isn't already referring to a room - roomID, err := r.DB.GetRoomIDFromAlias(request.Alias) + roomNID, err := r.DB.GetRoomNIDFromAlias(request.Alias) if err != nil { return err } - if len(roomID) > 0 { + if roomNID != 0 { // If the alias already exists, stop the process response.AliasExists = true return nil } response.AliasExists = false + roomNID, err = r.DB.RoomNID(request.RoomID) + if err != nil { + return err + } // Save the new alias - if err := r.DB.SetRoomAlias(request.Alias, request.RoomID); err != nil { + if err := r.DB.SetRoomAlias(request.Alias, roomNID); err != nil { return err } // Send a m.room.aliases event with the updated list of aliases for this room - if err := r.sendUpdatedAliasesEvent(request.UserID, request.RoomID); err != nil { + if err := r.sendUpdatedAliasesEvent(request.UserID, request.RoomID, roomNID); err != nil { return err } @@ -87,7 +100,12 @@ func (r *RoomserverAliasAPI) GetAliasRoomID( response *api.GetAliasRoomIDResponse, ) error { // Lookup the room ID in the database - roomID, err := r.DB.GetRoomIDFromAlias(request.Alias) + roomNID, err := r.DB.GetRoomNIDFromAlias(request.Alias) + if err != nil { + return err + } + + roomID, err := r.DB.RoomID(roomNID) if err != nil { return err } @@ -102,7 +120,12 @@ func (r *RoomserverAliasAPI) RemoveRoomAlias( response *api.RemoveRoomAliasResponse, ) error { // Lookup the room ID in the database - roomID, err := r.DB.GetRoomIDFromAlias(request.Alias) + roomNID, err := r.DB.GetRoomNIDFromAlias(request.Alias) + if err != nil { + return err + } + + roomID, err := r.DB.RoomID(roomNID) if err != nil { return err } @@ -113,7 +136,7 @@ func (r *RoomserverAliasAPI) RemoveRoomAlias( } // Send an updated m.room.aliases event - if err := r.sendUpdatedAliasesEvent(request.UserID, roomID); err != nil { + if err := r.sendUpdatedAliasesEvent(request.UserID, roomID, roomNID); err != nil { return err } @@ -126,7 +149,7 @@ type roomAliasesContent struct { // Build the updated m.room.aliases event to send to the room after addition or // removal of an alias -func (r *RoomserverAliasAPI) sendUpdatedAliasesEvent(userID string, roomID string) error { +func (r *RoomserverAliasAPI) sendUpdatedAliasesEvent(userID string, roomID string, roomNID types.RoomNID) error { serverName := string(r.Cfg.Matrix.ServerName) builder := gomatrixserverlib.EventBuilder{ @@ -138,7 +161,7 @@ func (r *RoomserverAliasAPI) sendUpdatedAliasesEvent(userID string, roomID strin // Retrieve the updated list of aliases, marhal it and set it as the // event's content - aliases, err := r.DB.GetAliasesFromRoomID(roomID) + aliases, err := r.DB.GetAliasesFromRoomNID(roomNID) if err != nil { return err } diff --git a/src/github.com/matrix-org/dendrite/roomserver/publicroom/public_room.go b/src/github.com/matrix-org/dendrite/roomserver/publicroom/public_room.go index 7045c367b..6fa68cd8a 100644 --- a/src/github.com/matrix-org/dendrite/roomserver/publicroom/public_room.go +++ b/src/github.com/matrix-org/dendrite/roomserver/publicroom/public_room.go @@ -33,23 +33,27 @@ type RoomserverPublicRoomAPIDatabase interface { // Returns 0 if the room doesn't exists. // Returns an error if there was a problem talking to the database. RoomNID(roomID string) (types.RoomNID, error) + // Lookup the room IDs matching a batch of room numeric IDs, ordered by + // numeric ID (ie map[roomNID] = roomID). + // Returns an error if the retrieval failed. + RoomIDs([]types.RoomNID) (map[types.RoomNID]string, error) // Checks the visibility of the room identified by the given numeric ID. // Returns true if the room is publicly visible, returns false if not. // If there's no room matching this numeric ID, or if the retrieval failed, // returns an error. IsRoomPublic(roomNID types.RoomNID) (bool, error) - // Returns an array of string containing the room IDs of the rooms that are - // publicly visible. + // Returns an array of string containing the room numeric IDs of the rooms + // that are publicly visible. // Returns an error if the retrieval failed. - GetPublicRoomIDs() ([]string, error) + GetPublicRoomNIDs() ([]types.RoomNID, error) // Updates the visibility for a room to the given value: true means that the // room is publicly visible, false means that the room isn't publicly visible. // Returns an error if the update failed. UpdateRoomVisibility(roomNID types.RoomNID, visibility bool) error - // Returns a map of the aliases bound to a given set of room IDs, ordered - // by room ID (ie map[roomID] = []alias) + // Returns a map of the aliases bound to a given set of room numeric IDs, + // ordered by room NID (ie map[roomNID] = []alias) // Returns an error if the retrieval failed - GetAliasesFromRoomIDs(roomIDs []string) (map[string][]string, error) + GetAliasesFromRoomNIDs(roomNIDs []types.RoomNID) (map[types.RoomNID][]string, error) } // RoomserverPublicRoomAPI is an implementation of api.RoomserverPublicRoomAPI @@ -120,12 +124,16 @@ func (r *RoomserverPublicRoomAPI) GetPublicRooms( return err } - roomIDs, err := r.DB.GetPublicRoomIDs() + roomNIDs, err := r.DB.GetPublicRoomNIDs() if err != nil { return err } - aliases, err := r.DB.GetAliasesFromRoomIDs(roomIDs) + aliases, err := r.DB.GetAliasesFromRoomNIDs(roomNIDs) + if err != nil { + return err + } + roomIDs, err := r.DB.RoomIDs(roomNIDs) if err != nil { return err } @@ -133,9 +141,9 @@ func (r *RoomserverPublicRoomAPI) GetPublicRooms( chunks := []api.PublicRoomsChunk{} // Iterate over the array of aliases instead of the array of rooms, because // a room must have at least one alias to be listed - for room, as := range aliases { + for roomNID, as := range aliases { chunk := api.PublicRoomsChunk{ - RoomID: room, + RoomID: roomIDs[roomNID], Aliases: as, NumJoinedMembers: 0, WorldReadable: true, @@ -144,13 +152,14 @@ func (r *RoomserverPublicRoomAPI) GetPublicRooms( chunks = append(chunks, chunk) } - if limit == 0 { - // If limit is 0, don't limit the results - response.Chunks = chunks[offset:] - } else { - response.Chunks = chunks[offset:limit] + chunks = chunks[offset:] + + if len(chunks) >= int(limit) { + chunks = chunks[offset:limit] } + response.Chunks = chunks + return nil } diff --git a/src/github.com/matrix-org/dendrite/roomserver/storage/room_aliases_table.go b/src/github.com/matrix-org/dendrite/roomserver/storage/room_aliases_table.go index 2c47a35d5..00f35a0f4 100644 --- a/src/github.com/matrix-org/dendrite/roomserver/storage/room_aliases_table.go +++ b/src/github.com/matrix-org/dendrite/roomserver/storage/room_aliases_table.go @@ -18,6 +18,7 @@ import ( "database/sql" "github.com/lib/pq" + "github.com/matrix-org/dendrite/roomserver/types" ) const roomAliasesSchema = ` @@ -25,34 +26,34 @@ const roomAliasesSchema = ` CREATE TABLE IF NOT EXISTS roomserver_room_aliases ( -- Alias of the room alias TEXT NOT NULL PRIMARY KEY, - -- Room ID the alias refers to - room_id TEXT NOT NULL + -- Room numeric ID the alias refers to + room_nid TEXT NOT NULL ); -CREATE UNIQUE INDEX IF NOT EXISTS roomserver_room_id_idx ON roomserver_room_aliases(room_id); +CREATE UNIQUE INDEX IF NOT EXISTS roomserver_room_nid_idx ON roomserver_room_aliases(room_nid); ` const insertRoomAliasSQL = "" + - "INSERT INTO roomserver_room_aliases (alias, room_id) VALUES ($1, $2)" + "INSERT INTO roomserver_room_aliases (alias, room_nid) VALUES ($1, $2)" -const selectRoomIDFromAliasSQL = "" + - "SELECT room_id FROM roomserver_room_aliases WHERE alias = $1" +const selectRoomNIDFromAliasSQL = "" + + "SELECT room_nid FROM roomserver_room_aliases WHERE alias = $1" -const selectAliasesFromRoomIDSQL = "" + - "SELECT alias FROM roomserver_room_aliases WHERE room_id = $1" +const selectAliasesFromRoomNIDSQL = "" + + "SELECT alias FROM roomserver_room_aliases WHERE room_nid = $1" -const selectAliasesFromRoomIDsSQL = "" + - "SELECT alias, room_id FROM roomserver_room_aliases WHERE room_id = ANY($1)" +const selectAliasesFromRoomNIDsSQL = "" + + "SELECT alias, room_nid FROM roomserver_room_aliases WHERE room_nid = ANY($1)" const deleteRoomAliasSQL = "" + "DELETE FROM roomserver_room_aliases WHERE alias = $1" type roomAliasesStatements struct { - insertRoomAliasStmt *sql.Stmt - selectRoomIDFromAliasStmt *sql.Stmt - selectAliasesFromRoomIDStmt *sql.Stmt - selectAliasesFromRoomIDsStmt *sql.Stmt - deleteRoomAliasStmt *sql.Stmt + insertRoomAliasStmt *sql.Stmt + selectRoomNIDFromAliasStmt *sql.Stmt + selectAliasesFromRoomNIDStmt *sql.Stmt + selectAliasesFromRoomNIDsStmt *sql.Stmt + deleteRoomAliasStmt *sql.Stmt } func (s *roomAliasesStatements) prepare(db *sql.DB) (err error) { @@ -62,29 +63,29 @@ func (s *roomAliasesStatements) prepare(db *sql.DB) (err error) { } return statementList{ {&s.insertRoomAliasStmt, insertRoomAliasSQL}, - {&s.selectRoomIDFromAliasStmt, selectRoomIDFromAliasSQL}, - {&s.selectAliasesFromRoomIDStmt, selectAliasesFromRoomIDSQL}, - {&s.selectAliasesFromRoomIDsStmt, selectAliasesFromRoomIDsSQL}, + {&s.selectRoomNIDFromAliasStmt, selectRoomNIDFromAliasSQL}, + {&s.selectAliasesFromRoomNIDStmt, selectAliasesFromRoomNIDSQL}, + {&s.selectAliasesFromRoomNIDsStmt, selectAliasesFromRoomNIDsSQL}, {&s.deleteRoomAliasStmt, deleteRoomAliasSQL}, }.prepare(db) } -func (s *roomAliasesStatements) insertRoomAlias(alias string, roomID string) (err error) { - _, err = s.insertRoomAliasStmt.Exec(alias, roomID) +func (s *roomAliasesStatements) insertRoomAlias(alias string, roomNID types.RoomNID) (err error) { + _, err = s.insertRoomAliasStmt.Exec(alias, roomNID) return } -func (s *roomAliasesStatements) selectRoomIDFromAlias(alias string) (roomID string, err error) { - err = s.selectRoomIDFromAliasStmt.QueryRow(alias).Scan(&roomID) +func (s *roomAliasesStatements) selectRoomNIDFromAlias(alias string) (roomNID types.RoomNID, err error) { + err = s.selectRoomNIDFromAliasStmt.QueryRow(alias).Scan(&roomNID) if err == sql.ErrNoRows { - return "", nil + return 0, nil } return } -func (s *roomAliasesStatements) selectAliasesFromRoomID(roomID string) (aliases []string, err error) { +func (s *roomAliasesStatements) selectAliasesFromRoomNID(roomNID types.RoomNID) (aliases []string, err error) { aliases = []string{} - rows, err := s.selectAliasesFromRoomIDStmt.Query(roomID) + rows, err := s.selectAliasesFromRoomNIDStmt.Query(roomNID) if err != nil { return } @@ -101,23 +102,31 @@ func (s *roomAliasesStatements) selectAliasesFromRoomID(roomID string) (aliases return } -func (s *roomAliasesStatements) selectAliasesFromRoomIDs(roomIDs []string) (aliases map[string][]string, err error) { - aliases = make(map[string][]string) - rows, err := s.selectAliasesFromRoomIDsStmt.Query(pq.StringArray(roomIDs)) +func (s *roomAliasesStatements) selectAliasesFromRoomNIDs(roomNIDs []types.RoomNID) (aliases map[types.RoomNID][]string, err error) { + aliases = make(map[types.RoomNID][]string) + // Convert the array of numeric IDs into one that can be used in the query + nIDs := []int64{} + for _, roomNID := range roomNIDs { + nIDs = append(nIDs, int64(roomNID)) + } + rows, err := s.selectAliasesFromRoomNIDsStmt.Query(pq.Int64Array(nIDs)) + if err != nil { return } for rows.Next() { - var alias, roomID string - if err = rows.Scan(&alias, &roomID); err != nil { + var alias string + var roomNID types.RoomNID + + if err = rows.Scan(&alias, &roomNID); err != nil { return } - if len(aliases[roomID]) > 0 { - aliases[roomID] = append(aliases[roomID], alias) + if len(aliases[roomNID]) > 0 { + aliases[roomNID] = append(aliases[roomNID], alias) } else { - aliases[roomID] = []string{alias} + aliases[roomNID] = []string{alias} } } diff --git a/src/github.com/matrix-org/dendrite/roomserver/storage/rooms_table.go b/src/github.com/matrix-org/dendrite/roomserver/storage/rooms_table.go index 76b3f67f6..6966ef88a 100644 --- a/src/github.com/matrix-org/dendrite/roomserver/storage/rooms_table.go +++ b/src/github.com/matrix-org/dendrite/roomserver/storage/rooms_table.go @@ -53,6 +53,12 @@ const insertRoomNIDSQL = "" + const selectRoomNIDSQL = "" + "SELECT room_nid FROM roomserver_rooms WHERE room_id = $1" +const selectRoomIDSQL = "" + + "SELECT room_id FROM roomserver_rooms WHERE room_nid = $1" + +const selectRoomIDsSQL = "" + + "SELECT room_nid, room_id FROM roomserver_rooms WHERE room_nid = ANY($1)" + const selectLatestEventNIDsSQL = "" + "SELECT latest_event_nids, state_snapshot_nid FROM roomserver_rooms WHERE room_nid = $1" @@ -63,7 +69,7 @@ const selectVisibilityForRoomNIDSQL = "" + "SELECT visibility FROM roomserver_rooms WHERE room_nid = $1" const selectPublicRoomIDsSQL = "" + - "SELECT room_id FROM roomserver_rooms WHERE visibility = true" + "SELECT room_nid FROM roomserver_rooms WHERE visibility = true" const updateLatestEventNIDsSQL = "" + "UPDATE roomserver_rooms SET latest_event_nids = $2, last_event_sent_nid = $3, state_snapshot_nid = $4 WHERE room_nid = $1" @@ -74,6 +80,8 @@ const updateVisibilityForRoomNIDSQL = "" + type roomStatements struct { insertRoomNIDStmt *sql.Stmt selectRoomNIDStmt *sql.Stmt + selectRoomIDStmt *sql.Stmt + selectRoomIDsStmt *sql.Stmt selectLatestEventNIDsStmt *sql.Stmt selectLatestEventNIDsForUpdateStmt *sql.Stmt selectVisibilityForRoomNIDStmt *sql.Stmt @@ -90,6 +98,8 @@ func (s *roomStatements) prepare(db *sql.DB) (err error) { return statementList{ {&s.insertRoomNIDStmt, insertRoomNIDSQL}, {&s.selectRoomNIDStmt, selectRoomNIDSQL}, + {&s.selectRoomIDStmt, selectRoomIDSQL}, + {&s.selectRoomIDsStmt, selectRoomIDsSQL}, {&s.selectLatestEventNIDsStmt, selectLatestEventNIDsSQL}, {&s.selectLatestEventNIDsForUpdateStmt, selectLatestEventNIDsForUpdateSQL}, {&s.selectVisibilityForRoomNIDStmt, selectVisibilityForRoomNIDSQL}, @@ -111,6 +121,39 @@ func (s *roomStatements) selectRoomNID(roomID string) (types.RoomNID, error) { return types.RoomNID(roomNID), err } +func (s *roomStatements) selectRoomID(roomNID types.RoomNID) (string, error) { + var roomID string + err := s.selectRoomIDStmt.QueryRow(roomNID).Scan(&roomID) + return roomID, err +} + +func (s *roomStatements) selectRoomIDs(roomNIDs []types.RoomNID) (map[types.RoomNID]string, error) { + roomIDs := make(map[types.RoomNID]string) + + nIDs := []int64{} + for _, roomNID := range roomNIDs { + nIDs = append(nIDs, int64(roomNID)) + } + + rows, err := s.selectRoomIDsStmt.Query(pq.Int64Array(nIDs)) + if err != nil { + return roomIDs, err + } + + for rows.Next() { + var roomNID types.RoomNID + var roomID string + + if err := rows.Scan(&roomNID, &roomID); err != nil { + return roomIDs, err + } + + roomIDs[roomNID] = roomID + } + + return roomIDs, nil +} + func (s *roomStatements) selectLatestEventNIDs(roomNID types.RoomNID) ([]types.EventNID, types.StateSnapshotNID, error) { var nids pq.Int64Array var stateSnapshotNID int64 @@ -148,22 +191,22 @@ func (s *roomStatements) selectVisibilityForRoomNID(roomNID types.RoomNID) (bool return visibility, err } -func (s *roomStatements) selectPublicRoomIDs() ([]string, error) { - roomIDs := []string{} +func (s *roomStatements) selectPublicRoomNIDs() ([]types.RoomNID, error) { + roomNIDs := []types.RoomNID{} rows, err := s.selectPublicRoomIDsStmt.Query() if err != nil { - return roomIDs, err + return roomNIDs, err } for rows.Next() { - var roomID string + var roomID types.RoomNID if err = rows.Scan(&roomID); err != nil { - return roomIDs, err + return roomNIDs, err } - roomIDs = append(roomIDs, roomID) + roomNIDs = append(roomNIDs, roomID) } - return roomIDs, nil + return roomNIDs, nil } func (s *roomStatements) updateVisibilityForRoomNID(roomNID types.RoomNID, visibility bool) error { diff --git a/src/github.com/matrix-org/dendrite/roomserver/storage/storage.go b/src/github.com/matrix-org/dendrite/roomserver/storage/storage.go index abb5d75f0..7704183b5 100644 --- a/src/github.com/matrix-org/dendrite/roomserver/storage/storage.go +++ b/src/github.com/matrix-org/dendrite/roomserver/storage/storage.go @@ -353,24 +353,29 @@ func (d *Database) LatestEventIDs(roomNID types.RoomNID) ([]gomatrixserverlib.Ev return references, currentStateSnapshotNID, depth, nil } +// RoomID implements alias.RoomserverAliasAPIDB +func (d *Database) RoomID(roomNID types.RoomNID) (string, error) { + return d.statements.selectRoomID(roomNID) +} + // SetRoomAlias implements alias.RoomserverAliasAPIDB -func (d *Database) SetRoomAlias(alias string, roomID string) error { - return d.statements.insertRoomAlias(alias, roomID) +func (d *Database) SetRoomAlias(alias string, roomNID types.RoomNID) error { + return d.statements.insertRoomAlias(alias, roomNID) } -// GetRoomIDFromAlias implements alias.RoomserverAliasAPIDB -func (d *Database) GetRoomIDFromAlias(alias string) (string, error) { - return d.statements.selectRoomIDFromAlias(alias) +// GetRoomNIDFromAlias implements alias.RoomserverAliasAPIDB +func (d *Database) GetRoomNIDFromAlias(alias string) (types.RoomNID, error) { + return d.statements.selectRoomNIDFromAlias(alias) } -// GetAliasesFromRoomID implements alias.RoomserverAliasAPIDB -func (d *Database) GetAliasesFromRoomID(roomID string) ([]string, error) { - return d.statements.selectAliasesFromRoomID(roomID) +// GetAliasesFromRoomNID implements alias.RoomserverAliasAPIDB +func (d *Database) GetAliasesFromRoomNID(roomNID types.RoomNID) ([]string, error) { + return d.statements.selectAliasesFromRoomNID(roomNID) } -// GetAliasesFromRoomIDs implements publicroom.RoomserverPublicRoomAPIDB -func (d *Database) GetAliasesFromRoomIDs(roomIDs []string) (map[string][]string, error) { - return d.statements.selectAliasesFromRoomIDs(roomIDs) +// GetAliasesFromRoomNIDs implements publicroom.RoomserverPublicRoomAPIDB +func (d *Database) GetAliasesFromRoomNIDs(roomNIDs []types.RoomNID) (map[types.RoomNID][]string, error) { + return d.statements.selectAliasesFromRoomNIDs(roomNIDs) } // RemoveRoomAlias implements alias.RoomserverAliasAPIDB @@ -385,14 +390,19 @@ func (d *Database) StateEntriesForTuples( return d.statements.bulkSelectFilteredStateBlockEntries(stateBlockNIDs, stateKeyTuples) } +// RoomIDs implements publicroom.RoomserverPublicRoomAPIDB +func (d *Database) RoomIDs(roomNIDs []types.RoomNID) (map[types.RoomNID]string, error) { + return d.statements.selectRoomIDs(roomNIDs) +} + // IsRoomPublic implements publicroom.RoomserverPublicRoomAPIDB func (d *Database) IsRoomPublic(roomNID types.RoomNID) (bool, error) { return d.statements.selectVisibilityForRoomNID(roomNID) } -// GetPublicRoomIDs implements publicroom.RoomserverPublicRoomAPIDB -func (d *Database) GetPublicRoomIDs() ([]string, error) { - return d.statements.selectPublicRoomIDs() +// GetPublicRoomNIDs implements publicroom.RoomserverPublicRoomAPIDB +func (d *Database) GetPublicRoomNIDs() ([]types.RoomNID, error) { + return d.statements.selectPublicRoomNIDs() } // UpdateRoomVisibility implements publicroom.RoomserverPublicRoomAPIDB From eab010dff42a6dd3f4d43e7dc4228da4a7231887 Mon Sep 17 00:00:00 2001 From: Brendan Abolivier Date: Fri, 11 Aug 2017 17:42:15 +0100 Subject: [PATCH 16/18] Remove unused methods in the query API DB --- .../matrix-org/dendrite/roomserver/query/query.go | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/github.com/matrix-org/dendrite/roomserver/query/query.go b/src/github.com/matrix-org/dendrite/roomserver/query/query.go index 30b695fb4..7ec8dcb5b 100644 --- a/src/github.com/matrix-org/dendrite/roomserver/query/query.go +++ b/src/github.com/matrix-org/dendrite/roomserver/query/query.go @@ -40,18 +40,6 @@ type RoomserverQueryAPIDatabase interface { // Lookup the numeric IDs for a list of events. // Returns an error if there was a problem talking to the database. EventNIDs(eventIDs []string) (map[string]types.EventNID, error) - // Save a given room alias with the room ID it refers to. - // Returns an error if there was a problem talking to the database. - SetRoomAlias(alias string, roomID string) error - // Lookup the room ID a given alias refers to. - // Returns an error if there was a problem talking to the database. - GetRoomIDFromAlias(alias string) (string, error) - // Lookup all aliases referring to a given room ID. - // Returns an error if there was a problem talking to the database. - GetAliasesFromRoomID(roomID string) ([]string, error) - // Remove a given room alias. - // Returns an error if there was a problem talking to the database. - RemoveRoomAlias(alias string) error } // RoomserverQueryAPI is an implementation of api.RoomserverQueryAPI From 112f4ef920ebbb08641ad440d601220ec8107908 Mon Sep 17 00:00:00 2001 From: Brendan Abolivier Date: Fri, 11 Aug 2017 17:59:14 +0100 Subject: [PATCH 17/18] Count number of joined user in rooms --- .../roomserver/publicroom/public_room.go | 9 ++++- .../roomserver/storage/membership_table.go | 36 ++++++++++++++++++- .../dendrite/roomserver/storage/storage.go | 5 +++ 3 files changed, 48 insertions(+), 2 deletions(-) diff --git a/src/github.com/matrix-org/dendrite/roomserver/publicroom/public_room.go b/src/github.com/matrix-org/dendrite/roomserver/publicroom/public_room.go index 6fa68cd8a..a4da4fcde 100644 --- a/src/github.com/matrix-org/dendrite/roomserver/publicroom/public_room.go +++ b/src/github.com/matrix-org/dendrite/roomserver/publicroom/public_room.go @@ -54,6 +54,9 @@ type RoomserverPublicRoomAPIDatabase interface { // ordered by room NID (ie map[roomNID] = []alias) // Returns an error if the retrieval failed GetAliasesFromRoomNIDs(roomNIDs []types.RoomNID) (map[types.RoomNID][]string, error) + // Returns the number of joined user in roms identified by given room numeric IDs. + // Returns an error if the retrieval failed + CountJoinedMembersInRooms(roomNIDs []types.RoomNID) (map[types.RoomNID]int64, error) } // RoomserverPublicRoomAPI is an implementation of api.RoomserverPublicRoomAPI @@ -137,6 +140,10 @@ func (r *RoomserverPublicRoomAPI) GetPublicRooms( if err != nil { return err } + nbMembers, err := r.DB.CountJoinedMembersInRooms(roomNIDs) + if err != nil { + return err + } chunks := []api.PublicRoomsChunk{} // Iterate over the array of aliases instead of the array of rooms, because @@ -145,7 +152,7 @@ func (r *RoomserverPublicRoomAPI) GetPublicRooms( chunk := api.PublicRoomsChunk{ RoomID: roomIDs[roomNID], Aliases: as, - NumJoinedMembers: 0, + NumJoinedMembers: nbMembers[roomNID], WorldReadable: true, GuestCanJoin: true, } diff --git a/src/github.com/matrix-org/dendrite/roomserver/storage/membership_table.go b/src/github.com/matrix-org/dendrite/roomserver/storage/membership_table.go index 725e5b8d9..f209c0d2c 100644 --- a/src/github.com/matrix-org/dendrite/roomserver/storage/membership_table.go +++ b/src/github.com/matrix-org/dendrite/roomserver/storage/membership_table.go @@ -17,6 +17,7 @@ package storage import ( "database/sql" + "github.com/lib/pq" "github.com/matrix-org/dendrite/roomserver/types" ) @@ -33,7 +34,7 @@ const membershipSchema = ` -- and the room state tables. -- This table is updated in one of 3 ways: -- 1) The membership of a user changes within the current state of the room. --- 2) An invite is received outside of a room over federation. +-- 2) An invite is received outside of a room over federation. -- 3) An invite is rejected outside of a room over federation. CREATE TABLE IF NOT EXISTS roomserver_membership ( room_nid BIGINT NOT NULL, @@ -61,6 +62,11 @@ const selectMembershipForUpdateSQL = "" + "SELECT membership_nid FROM roomserver_membership" + " WHERE room_nid = $1 AND target_nid = $2 FOR UPDATE" +const countJoinedMembersInRoomsSQL = "" + + "SELECT room_nid, COUNT(*) FROM roomserver_membership" + + " WHERE room_nid = ANY($1) AND membership_nid = $2" + + " GROUP BY room_nid" + const updateMembershipSQL = "" + "UPDATE roomserver_membership SET sender_nid = $3, membership_nid = $4" + " WHERE room_nid = $1 AND target_nid = $2" @@ -68,6 +74,7 @@ const updateMembershipSQL = "" + type membershipStatements struct { insertMembershipStmt *sql.Stmt selectMembershipForUpdateStmt *sql.Stmt + countJoinedMembersInRoomsStmt *sql.Stmt updateMembershipStmt *sql.Stmt } @@ -80,6 +87,7 @@ func (s *membershipStatements) prepare(db *sql.DB) (err error) { return statementList{ {&s.insertMembershipStmt, insertMembershipSQL}, {&s.selectMembershipForUpdateStmt, selectMembershipForUpdateSQL}, + {&s.countJoinedMembersInRoomsStmt, countJoinedMembersInRoomsSQL}, {&s.updateMembershipStmt, updateMembershipSQL}, }.prepare(db) } @@ -100,6 +108,32 @@ func (s *membershipStatements) selectMembershipForUpdate( return } +func (s *membershipStatements) countJoinedMembersInRooms(roomNIDs []types.RoomNID) (map[types.RoomNID]int64, error) { + nIDs := []int64{} + for _, roomNID := range roomNIDs { + nIDs = append(nIDs, int64(roomNID)) + } + + rows, err := s.countJoinedMembersInRoomsStmt.Query(pq.Int64Array(nIDs), membershipStateJoin) + if err != nil { + return nil, err + } + + members := make(map[types.RoomNID]int64) + for rows.Next() { + var roomNID types.RoomNID + var count int64 + + if err = rows.Scan(&roomNID, &count); err != nil { + return members, err + } + + members[roomNID] = count + } + + return members, err +} + func (s *membershipStatements) updateMembership( txn *sql.Tx, roomNID types.RoomNID, targetUserNID types.EventStateKeyNID, senderUserNID types.EventStateKeyNID, membership membershipState, diff --git a/src/github.com/matrix-org/dendrite/roomserver/storage/storage.go b/src/github.com/matrix-org/dendrite/roomserver/storage/storage.go index 7704183b5..388f61cc2 100644 --- a/src/github.com/matrix-org/dendrite/roomserver/storage/storage.go +++ b/src/github.com/matrix-org/dendrite/roomserver/storage/storage.go @@ -410,6 +410,11 @@ func (d *Database) UpdateRoomVisibility(roomNID types.RoomNID, visibility bool) return d.statements.updateVisibilityForRoomNID(roomNID, visibility) } +// CountJoinedMembersInRooms implements publicroom.RoomserverPublicRoomAPIDB +func (d *Database) CountJoinedMembersInRooms(roomNIDs []types.RoomNID) (map[types.RoomNID]int64, error) { + return d.statements.countJoinedMembersInRooms(roomNIDs) +} + type membershipUpdater struct { transaction d *Database From d6dc8b5ea2f7ef5859d235222385a0c13f7c5f52 Mon Sep 17 00:00:00 2001 From: Brendan Abolivier Date: Fri, 11 Aug 2017 18:10:17 +0100 Subject: [PATCH 18/18] Add pagination --- .../dendrite/roomserver/publicroom/public_room.go | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/github.com/matrix-org/dendrite/roomserver/publicroom/public_room.go b/src/github.com/matrix-org/dendrite/roomserver/publicroom/public_room.go index a4da4fcde..d4943b18e 100644 --- a/src/github.com/matrix-org/dendrite/roomserver/publicroom/public_room.go +++ b/src/github.com/matrix-org/dendrite/roomserver/publicroom/public_room.go @@ -145,7 +145,7 @@ func (r *RoomserverPublicRoomAPI) GetPublicRooms( return err } - chunks := []api.PublicRoomsChunk{} + totalChunks := []api.PublicRoomsChunk{} // Iterate over the array of aliases instead of the array of rooms, because // a room must have at least one alias to be listed for roomNID, as := range aliases { @@ -156,16 +156,23 @@ func (r *RoomserverPublicRoomAPI) GetPublicRooms( WorldReadable: true, GuestCanJoin: true, } - chunks = append(chunks, chunk) + totalChunks = append(totalChunks, chunk) } - chunks = chunks[offset:] + chunks := totalChunks[offset:] if len(chunks) >= int(limit) { chunks = chunks[offset:limit] } response.Chunks = chunks + response.TotalRoomCountEstimate = int64(len(totalChunks)) + if offset > 0 { + response.PrevBatch = strconv.Itoa(int(offset) - 1) + } + if len(totalChunks) > int(limit) { + response.NextBatch = strconv.Itoa(int(offset) + int(limit)) + } return nil }