From c7e36ba2a1cec9454b0451c13390f8f4c071ae8f Mon Sep 17 00:00:00 2001 From: Brendan Abolivier Date: Fri, 28 Jul 2017 11:31:43 +0100 Subject: [PATCH] Implement room aliases (#167) * Add database functions to interact with aliases * Save room alias * Check if alias already exists * Implement local alias lookup * Implement alias removal * Move alias API * Handle case with no alias set for a room * Apply requested changes --- .../dendrite/clientapi/readers/directory.go | 114 +++++++- .../dendrite/clientapi/routing/routing.go | 19 +- .../cmd/dendrite-client-api-server/main.go | 4 +- .../dendrite/cmd/dendrite-room-server/main.go | 20 +- .../dendrite/roomserver/alias/alias.go | 256 ++++++++++++++++++ .../dendrite/roomserver/api/alias.go | 129 +++++++++ .../dendrite/roomserver/query/query.go | 14 +- .../roomserver/storage/room_aliases_table.go | 100 +++++++ .../dendrite/roomserver/storage/sql.go | 5 + .../dendrite/roomserver/storage/storage.go | 20 ++ 10 files changed, 663 insertions(+), 18 deletions(-) create mode 100644 src/github.com/matrix-org/dendrite/roomserver/alias/alias.go create mode 100644 src/github.com/matrix-org/dendrite/roomserver/api/alias.go create mode 100644 src/github.com/matrix-org/dendrite/roomserver/storage/room_aliases_table.go diff --git a/src/github.com/matrix-org/dendrite/clientapi/readers/directory.go b/src/github.com/matrix-org/dendrite/clientapi/readers/directory.go index 336f29fed..4c25a45cd 100644 --- a/src/github.com/matrix-org/dendrite/clientapi/readers/directory.go +++ b/src/github.com/matrix-org/dendrite/clientapi/readers/directory.go @@ -15,13 +15,13 @@ package readers import ( - "fmt" "net/http" "github.com/matrix-org/dendrite/clientapi/auth/authtypes" "github.com/matrix-org/dendrite/clientapi/httputil" "github.com/matrix-org/dendrite/clientapi/jsonerror" "github.com/matrix-org/dendrite/common/config" + "github.com/matrix-org/dendrite/roomserver/api" "github.com/matrix-org/gomatrix" "github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/util" @@ -34,6 +34,7 @@ func DirectoryRoom( roomAlias string, federation *gomatrixserverlib.FederationClient, cfg *config.Dendrite, + aliasAPI api.RoomserverAliasAPI, ) util.JSONResponse { _, domain, err := gomatrixserverlib.SplitID('#', roomAlias) if err != nil { @@ -43,11 +44,30 @@ func DirectoryRoom( } } + var resp gomatrixserverlib.RespDirectory + if domain == cfg.Matrix.ServerName { - // TODO: Implement lookup up local room aliases. - panic(fmt.Errorf("Looking up local room aliases is not implemented")) + queryReq := api.GetAliasRoomIDRequest{Alias: roomAlias} + var queryRes api.GetAliasRoomIDResponse + if err = aliasAPI.GetAliasRoomID(&queryReq, &queryRes); err != nil { + return httputil.LogThenError(req, err) + } + + if len(queryRes.RoomID) > 0 { + // TODO: List servers that are aware of this room alias + resp = gomatrixserverlib.RespDirectory{ + RoomID: queryRes.RoomID, + Servers: []gomatrixserverlib.ServerName{}, + } + } else { + // If the response doesn't contain a non-empty string, return an error + return util.JSONResponse{ + Code: 404, + JSON: jsonerror.NotFound("Room alias " + roomAlias + " not found."), + } + } } else { - resp, err := federation.LookupRoomAlias(domain, roomAlias) + resp, err = federation.LookupRoomAlias(domain, roomAlias) if err != nil { switch x := err.(type) { case gomatrix.HTTPError: @@ -62,10 +82,88 @@ func DirectoryRoom( // TODO: Return 504 if the remote server timed out. return httputil.LogThenError(req, err) } + } - return util.JSONResponse{ - Code: 200, - JSON: resp, - } + return util.JSONResponse{ + Code: 200, + JSON: resp, + } +} + +// SetLocalAlias implements PUT /directory/room/{roomAlias} +// TODO: Check if the user has the power level to set an alias +func SetLocalAlias( + req *http.Request, + device *authtypes.Device, + alias string, + cfg *config.Dendrite, + aliasAPI api.RoomserverAliasAPI, +) util.JSONResponse { + _, domain, err := gomatrixserverlib.SplitID('#', alias) + if err != nil { + return util.JSONResponse{ + Code: 400, + JSON: jsonerror.BadJSON("Room alias must be in the form '#localpart:domain'"), + } + } + + if domain != cfg.Matrix.ServerName { + return util.JSONResponse{ + Code: 403, + JSON: jsonerror.Forbidden("Alias must be on local homeserver"), + } + } + + var r struct { + RoomID string `json:"room_id"` + } + if resErr := httputil.UnmarshalJSONRequest(req, &r); resErr != nil { + return *resErr + } + + queryReq := api.SetRoomAliasRequest{ + UserID: device.UserID, + RoomID: r.RoomID, + Alias: alias, + } + var queryRes api.SetRoomAliasResponse + if err := aliasAPI.SetRoomAlias(&queryReq, &queryRes); err != nil { + return httputil.LogThenError(req, err) + } + + if queryRes.AliasExists { + return util.JSONResponse{ + Code: 409, + JSON: jsonerror.Unknown("The alias " + alias + " already exists."), + } + } + + return util.JSONResponse{ + Code: 200, + JSON: struct{}{}, + } +} + +// RemoveLocalAlias implements DELETE /directory/room/{roomAlias} +// TODO: Check if the user has the power level to remove an alias +func RemoveLocalAlias( + req *http.Request, + device *authtypes.Device, + alias string, + cfg *config.Dendrite, + aliasAPI api.RoomserverAliasAPI, +) util.JSONResponse { + queryReq := api.RemoveRoomAliasRequest{ + Alias: alias, + UserID: device.UserID, + } + var queryRes api.RemoveRoomAliasResponse + if err := aliasAPI.RemoveRoomAlias(&queryReq, &queryRes); err != nil { + return httputil.LogThenError(req, err) + } + + return util.JSONResponse{ + Code: 200, + JSON: struct{}{}, } } 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 be587f1d7..d8e3fc41b 100644 --- a/src/github.com/matrix-org/dendrite/clientapi/routing/routing.go +++ b/src/github.com/matrix-org/dendrite/clientapi/routing/routing.go @@ -42,6 +42,7 @@ const pathPrefixUnstable = "/_matrix/client/unstable" func Setup( servMux *http.ServeMux, httpClient *http.Client, cfg config.Dendrite, producer *producers.RoomserverProducer, queryAPI api.RoomserverQueryAPI, + aliasAPI api.RoomserverAliasAPI, accountDB *accounts.Database, deviceDB *devices.Database, federation *gomatrixserverlib.FederationClient, @@ -109,9 +110,23 @@ func Setup( r0mux.Handle("/directory/room/{roomAlias}", common.MakeAuthAPI("directory_room", deviceDB, func(req *http.Request, device *authtypes.Device) util.JSONResponse { vars := mux.Vars(req) - return readers.DirectoryRoom(req, device, vars["roomAlias"], federation, &cfg) + return readers.DirectoryRoom(req, device, vars["roomAlias"], federation, &cfg, aliasAPI) }), - ) + ).Methods("GET") + + r0mux.Handle("/directory/room/{roomAlias}", + common.MakeAuthAPI("directory_room", deviceDB, func(req *http.Request, device *authtypes.Device) util.JSONResponse { + vars := mux.Vars(req) + return readers.SetLocalAlias(req, device, vars["roomAlias"], &cfg, aliasAPI) + }), + ).Methods("PUT", "OPTIONS") + + r0mux.Handle("/directory/room/{roomAlias}", + common.MakeAuthAPI("directory_room", deviceDB, func(req *http.Request, device *authtypes.Device) util.JSONResponse { + vars := mux.Vars(req) + return readers.RemoveLocalAlias(req, device, vars["roomAlias"], &cfg, aliasAPI) + }), + ).Methods("DELETE") r0mux.Handle("/logout", common.MakeAuthAPI("logout", deviceDB, func(req *http.Request, device *authtypes.Device) util.JSONResponse { 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 eb1218e7c..0f8c035f6 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 @@ -52,6 +52,7 @@ func main() { log.Info("config: ", cfg) queryAPI := api.NewRoomserverQueryAPIHTTP(cfg.RoomServerURL(), nil) + aliasAPI := api.NewRoomserverAliasAPIHTTP(cfg.RoomServerURL(), nil) roomserverProducer := producers.NewRoomserverProducer(cfg.RoomServerURL()) userUpdateProducer, err := producers.NewUserUpdateProducer( @@ -97,7 +98,8 @@ func main() { log.Info("Starting client API server on ", cfg.Listen.ClientAPI) routing.Setup( http.DefaultServeMux, http.DefaultClient, *cfg, roomserverProducer, - queryAPI, accountDB, deviceDB, federation, keyRing, userUpdateProducer, + queryAPI, aliasAPI, accountDB, deviceDB, federation, keyRing, + userUpdateProducer, ) log.Fatal(http.ListenAndServe(string(cfg.Listen.ClientAPI), nil)) } diff --git a/src/github.com/matrix-org/dendrite/cmd/dendrite-room-server/main.go b/src/github.com/matrix-org/dendrite/cmd/dendrite-room-server/main.go index 715a40740..8737c986c 100644 --- a/src/github.com/matrix-org/dendrite/cmd/dendrite-room-server/main.go +++ b/src/github.com/matrix-org/dendrite/cmd/dendrite-room-server/main.go @@ -23,6 +23,7 @@ import ( log "github.com/Sirupsen/logrus" "github.com/matrix-org/dendrite/common" "github.com/matrix-org/dendrite/common/config" + "github.com/matrix-org/dendrite/roomserver/alias" "github.com/matrix-org/dendrite/roomserver/input" "github.com/matrix-org/dendrite/roomserver/query" "github.com/matrix-org/dendrite/roomserver/storage" @@ -58,12 +59,6 @@ func main() { panic(err) } - queryAPI := query.RoomserverQueryAPI{ - DB: db, - } - - queryAPI.SetupHTTP(http.DefaultServeMux) - inputAPI := input.RoomserverInputAPI{ DB: db, Producer: kafkaProducer, @@ -72,6 +67,19 @@ func main() { inputAPI.SetupHTTP(http.DefaultServeMux) + queryAPI := query.RoomserverQueryAPI{db} + + queryAPI.SetupHTTP(http.DefaultServeMux) + + aliasAPI := alias.RoomserverAliasAPI{ + DB: db, + Cfg: cfg, + InputAPI: inputAPI, + QueryAPI: queryAPI, + } + + aliasAPI.SetupHTTP(http.DefaultServeMux) + http.DefaultServeMux.Handle("/metrics", prometheus.Handler()) log.Info("Started room server on ", cfg.Listen.RoomServer) diff --git a/src/github.com/matrix-org/dendrite/roomserver/alias/alias.go b/src/github.com/matrix-org/dendrite/roomserver/alias/alias.go new file mode 100644 index 000000000..e630343e7 --- /dev/null +++ b/src/github.com/matrix-org/dendrite/roomserver/alias/alias.go @@ -0,0 +1,256 @@ +// 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 alias + +import ( + "encoding/json" + "fmt" + "net/http" + "time" + + "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/input" + "github.com/matrix-org/dendrite/roomserver/query" + "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. + // 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 +} + +// RoomserverAliasAPI is an implementation of api.RoomserverAliasAPI +type RoomserverAliasAPI struct { + DB RoomserverAliasAPIDatabase + Cfg *config.Dendrite + InputAPI input.RoomserverInputAPI + QueryAPI query.RoomserverQueryAPI +} + +// SetRoomAlias implements api.RoomserverAliasAPI +func (r *RoomserverAliasAPI) SetRoomAlias( + request *api.SetRoomAliasRequest, + response *api.SetRoomAliasResponse, +) error { + // Check if the alias isn't already referring to a room + roomID, err := r.DB.GetRoomIDFromAlias(request.Alias) + if err != nil { + return err + } + if len(roomID) > 0 { + // If the alias already exists, stop the process + response.AliasExists = true + return nil + } + response.AliasExists = false + + // Save the new alias + if err := r.DB.SetRoomAlias(request.Alias, request.RoomID); 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 { + return err + } + + return nil +} + +// GetAliasRoomID implements api.RoomserverAliasAPI +func (r *RoomserverAliasAPI) GetAliasRoomID( + request *api.GetAliasRoomIDRequest, + response *api.GetAliasRoomIDResponse, +) error { + // Lookup the room ID in the database + roomID, err := r.DB.GetRoomIDFromAlias(request.Alias) + if err != nil { + return err + } + + response.RoomID = roomID + return nil +} + +// RemoveRoomAlias implements api.RoomserverAliasAPI +func (r *RoomserverAliasAPI) RemoveRoomAlias( + request *api.RemoveRoomAliasRequest, + response *api.RemoveRoomAliasResponse, +) error { + // Lookup the room ID in the database + roomID, err := r.DB.GetRoomIDFromAlias(request.Alias) + if err != nil { + return err + } + + // Remove the dalias from the database + if err := r.DB.RemoveRoomAlias(request.Alias); err != nil { + return err + } + + // Send an updated m.room.aliases event + if err := r.sendUpdatedAliasesEvent(request.UserID, roomID); err != nil { + return err + } + + return nil +} + +type roomAliasesContent struct { + Aliases []string `json:"aliases"` +} + +// 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 { + serverName := string(r.Cfg.Matrix.ServerName) + + builder := gomatrixserverlib.EventBuilder{ + Sender: userID, + RoomID: roomID, + Type: "m.room.aliases", + StateKey: &serverName, + } + + // Retrieve the updated list of aliases, marhal it and set it as the + // event's content + aliases, err := r.DB.GetAliasesFromRoomID(roomID) + if err != nil { + return err + } + content := roomAliasesContent{Aliases: aliases} + rawContent, err := json.Marshal(content) + if err != nil { + return err + } + err = builder.SetContent(json.RawMessage(rawContent)) + if err != nil { + return err + } + + // Get needed state events and depth + eventsNeeded, err := gomatrixserverlib.StateNeededForEventBuilder(&builder) + if err != nil { + return err + } + req := api.QueryLatestEventsAndStateRequest{ + RoomID: roomID, + StateToFetch: eventsNeeded.Tuples(), + } + var res api.QueryLatestEventsAndStateResponse + if err = r.QueryAPI.QueryLatestEventsAndState(&req, &res); err != nil { + return err + } + builder.Depth = res.Depth + builder.PrevEvents = res.LatestEvents + + // Add auth events + authEvents := gomatrixserverlib.NewAuthEvents(nil) + for i := range res.StateEvents { + authEvents.AddEvent(&res.StateEvents[i]) + } + refs, err := eventsNeeded.AuthEventReferences(&authEvents) + if err != nil { + return err + } + builder.AuthEvents = refs + + // Build the event + eventID := fmt.Sprintf("$%s:%s", util.RandomString(16), r.Cfg.Matrix.ServerName) + now := time.Now() + event, err := builder.Build(eventID, now, r.Cfg.Matrix.ServerName, r.Cfg.Matrix.KeyID, r.Cfg.Matrix.PrivateKey) + if err != nil { + return err + } + + // Create the request + ire := api.InputRoomEvent{ + Kind: api.KindNew, + Event: event, + AuthEventIDs: event.AuthEventIDs(), + SendAsServer: serverName, + } + inputReq := api.InputRoomEventsRequest{ + InputRoomEvents: []api.InputRoomEvent{ire}, + } + var inputRes api.InputRoomEventsResponse + + // Send the request + if err := r.InputAPI.InputRoomEvents(&inputReq, &inputRes); err != nil { + return err + } + + return nil +} + +// SetupHTTP adds the RoomserverAliasAPI handlers to the http.ServeMux. +func (r *RoomserverAliasAPI) SetupHTTP(servMux *http.ServeMux) { + servMux.Handle( + api.RoomserverSetRoomAliasPath, + common.MakeAPI("setRoomAlias", func(req *http.Request) util.JSONResponse { + var request api.SetRoomAliasRequest + var response api.SetRoomAliasResponse + if err := json.NewDecoder(req.Body).Decode(&request); err != nil { + return util.ErrorResponse(err) + } + if err := r.SetRoomAlias(&request, &response); err != nil { + return util.ErrorResponse(err) + } + return util.JSONResponse{Code: 200, JSON: &response} + }), + ) + servMux.Handle( + api.RoomserverGetAliasRoomIDPath, + common.MakeAPI("getAliasRoomID", func(req *http.Request) util.JSONResponse { + var request api.GetAliasRoomIDRequest + var response api.GetAliasRoomIDResponse + if err := json.NewDecoder(req.Body).Decode(&request); err != nil { + return util.ErrorResponse(err) + } + if err := r.GetAliasRoomID(&request, &response); err != nil { + return util.ErrorResponse(err) + } + return util.JSONResponse{Code: 200, JSON: &response} + }), + ) + servMux.Handle( + api.RoomserverRemoveRoomAliasPath, + common.MakeAPI("removeRoomAlias", func(req *http.Request) util.JSONResponse { + var request api.RemoveRoomAliasRequest + var response api.RemoveRoomAliasResponse + if err := json.NewDecoder(req.Body).Decode(&request); err != nil { + return util.ErrorResponse(err) + } + if err := r.RemoveRoomAlias(&request, &response); err != nil { + return util.ErrorResponse(err) + } + return util.JSONResponse{Code: 200, JSON: &response} + }), + ) +} diff --git a/src/github.com/matrix-org/dendrite/roomserver/api/alias.go b/src/github.com/matrix-org/dendrite/roomserver/api/alias.go new file mode 100644 index 000000000..bb65c3ae3 --- /dev/null +++ b/src/github.com/matrix-org/dendrite/roomserver/api/alias.go @@ -0,0 +1,129 @@ +// 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" +) + +// SetRoomAliasRequest is a request to SetRoomAlias +type SetRoomAliasRequest struct { + // ID of the user setting the alias + UserID string `json:"user_id"` + // New alias for the room + Alias string `json:"alias"` + // The room ID the alias is referring to + RoomID string `json:"room_id"` +} + +// SetRoomAliasResponse is a response to SetRoomAlias +type SetRoomAliasResponse struct { + // Does the alias already refer to a room? + AliasExists bool `json:"alias_exists"` +} + +// GetAliasRoomIDRequest is a request to GetAliasRoomID +type GetAliasRoomIDRequest struct { + // Alias we want to lookup + Alias string `json:"alias"` +} + +// GetAliasRoomIDResponse is a response to GetAliasRoomID +type GetAliasRoomIDResponse struct { + // The room ID the alias refers to + RoomID string `json:"room_id"` +} + +// RemoveRoomAliasRequest is a request to RemoveRoomAlias +type RemoveRoomAliasRequest struct { + // ID of the user removing the alias + UserID string `json:"user_id"` + // The room alias to remove + Alias string `json:"alias"` +} + +// RemoveRoomAliasResponse is a response to RemoveRoomAlias +type RemoveRoomAliasResponse struct{} + +// RoomserverAliasAPI is used to save, lookup or remove a room alias +type RoomserverAliasAPI interface { + // Set a room alias + SetRoomAlias( + req *SetRoomAliasRequest, + response *SetRoomAliasResponse, + ) error + + // Get the room ID for an alias + GetAliasRoomID( + req *GetAliasRoomIDRequest, + response *GetAliasRoomIDResponse, + ) error + + // Remove a room alias + RemoveRoomAlias( + req *RemoveRoomAliasRequest, + response *RemoveRoomAliasResponse, + ) error +} + +// RoomserverSetRoomAliasPath is the HTTP path for the SetRoomAlias API. +const RoomserverSetRoomAliasPath = "/api/roomserver/setRoomAlias" + +// RoomserverGetAliasRoomIDPath is the HTTP path for the GetAliasRoomID API. +const RoomserverGetAliasRoomIDPath = "/api/roomserver/getAliasRoomID" + +// RoomserverRemoveRoomAliasPath is the HTTP path for the RemoveRoomAlias API. +const RoomserverRemoveRoomAliasPath = "/api/roomserver/removeRoomAlias" + +// NewRoomserverAliasAPIHTTP creates a RoomserverAliasAPI implemented by talking to a HTTP POST API. +// If httpClient is nil then it uses the http.DefaultClient +func NewRoomserverAliasAPIHTTP(roomserverURL string, httpClient *http.Client) RoomserverAliasAPI { + if httpClient == nil { + httpClient = http.DefaultClient + } + return &httpRoomserverAliasAPI{roomserverURL, httpClient} +} + +type httpRoomserverAliasAPI struct { + roomserverURL string + httpClient *http.Client +} + +// SetRoomAlias implements RoomserverAliasAPI +func (h *httpRoomserverAliasAPI) SetRoomAlias( + request *SetRoomAliasRequest, + response *SetRoomAliasResponse, +) error { + apiURL := h.roomserverURL + RoomserverSetRoomAliasPath + return postJSON(h.httpClient, apiURL, request, response) +} + +// GetAliasRoomID implements RoomserverAliasAPI +func (h *httpRoomserverAliasAPI) GetAliasRoomID( + request *GetAliasRoomIDRequest, + response *GetAliasRoomIDResponse, +) error { + // RemoveRoomAlias implements RoomserverAliasAPI + apiURL := h.roomserverURL + RoomserverGetAliasRoomIDPath + return postJSON(h.httpClient, apiURL, request, response) +} + +func (h *httpRoomserverAliasAPI) RemoveRoomAlias( + request *RemoveRoomAliasRequest, + response *RemoveRoomAliasResponse, +) error { + apiURL := h.roomserverURL + RoomserverRemoveRoomAliasPath + return postJSON(h.httpClient, apiURL, request, response) +} 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 142df90e3..30b695fb4 100644 --- a/src/github.com/matrix-org/dendrite/roomserver/query/query.go +++ b/src/github.com/matrix-org/dendrite/roomserver/query/query.go @@ -40,9 +40,21 @@ 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 RoomserverQueryAPI +// RoomserverQueryAPI is an implementation of api.RoomserverQueryAPI type RoomserverQueryAPI struct { DB RoomserverQueryAPIDatabase } 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 new file mode 100644 index 000000000..8b56b6d14 --- /dev/null +++ b/src/github.com/matrix-org/dendrite/roomserver/storage/room_aliases_table.go @@ -0,0 +1,100 @@ +// 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 storage + +import ( + "database/sql" +) + +const roomAliasesSchema = ` +-- Stores room aliases and room IDs they refer to +CREATE TABLE IF NOT EXISTS room_aliases ( + -- Alias of the room + alias TEXT NOT NULL PRIMARY KEY, + -- Room ID the alias refers to + room_id TEXT NOT NULL +); + +CREATE UNIQUE INDEX IF NOT EXISTS room_id_idx ON room_aliases(room_id); +` + +const insertRoomAliasSQL = "" + + "INSERT INTO room_aliases (alias, room_id) VALUES ($1, $2)" + +const selectRoomIDFromAliasSQL = "" + + "SELECT room_id FROM room_aliases WHERE alias = $1" + +const selectAliasesFromRoomIDSQL = "" + + "SELECT alias FROM room_aliases WHERE room_id = $1" + +const deleteRoomAliasSQL = "" + + "DELETE FROM room_aliases WHERE alias = $1" + +type roomAliasesStatements struct { + insertRoomAliasStmt *sql.Stmt + selectRoomIDFromAliasStmt *sql.Stmt + selectAliasesFromRoomIDStmt *sql.Stmt + deleteRoomAliasStmt *sql.Stmt +} + +func (s *roomAliasesStatements) prepare(db *sql.DB) (err error) { + _, err = db.Exec(roomAliasesSchema) + if err != nil { + return + } + return statementList{ + {&s.insertRoomAliasStmt, insertRoomAliasSQL}, + {&s.selectRoomIDFromAliasStmt, selectRoomIDFromAliasSQL}, + {&s.selectAliasesFromRoomIDStmt, selectAliasesFromRoomIDSQL}, + {&s.deleteRoomAliasStmt, deleteRoomAliasSQL}, + }.prepare(db) +} + +func (s *roomAliasesStatements) insertRoomAlias(alias string, roomID string) (err error) { + _, err = s.insertRoomAliasStmt.Exec(alias, roomID) + return +} + +func (s *roomAliasesStatements) selectRoomIDFromAlias(alias string) (roomID string, err error) { + err = s.selectRoomIDFromAliasStmt.QueryRow(alias).Scan(&roomID) + if err == sql.ErrNoRows { + return "", nil + } + return +} + +func (s *roomAliasesStatements) selectAliasesFromRoomID(roomID string) (aliases []string, err error) { + aliases = []string{} + rows, err := s.selectAliasesFromRoomIDStmt.Query(roomID) + if err != nil { + return + } + + for rows.Next() { + var alias string + if err = rows.Scan(&alias); err != nil { + return + } + + aliases = append(aliases, 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/sql.go b/src/github.com/matrix-org/dendrite/roomserver/storage/sql.go index fcb414f02..1f247b554 100644 --- a/src/github.com/matrix-org/dendrite/roomserver/storage/sql.go +++ b/src/github.com/matrix-org/dendrite/roomserver/storage/sql.go @@ -27,6 +27,7 @@ type statements struct { stateSnapshotStatements stateBlockStatements previousEventStatements + roomAliasesStatements } func (s *statements) prepare(db *sql.DB) error { @@ -64,5 +65,9 @@ func (s *statements) prepare(db *sql.DB) error { return err } + if err = s.roomAliasesStatements.prepare(db); err != nil { + return err + } + return nil } 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 50e2f44d6..3f99e7d85 100644 --- a/src/github.com/matrix-org/dendrite/roomserver/storage/storage.go +++ b/src/github.com/matrix-org/dendrite/roomserver/storage/storage.go @@ -355,6 +355,26 @@ func (d *Database) LatestEventIDs(roomNID types.RoomNID) ([]gomatrixserverlib.Ev return references, currentStateSnapshotNID, depth, nil } +// SetRoomAlias implements alias.RoomserverAliasAPIDB +func (d *Database) SetRoomAlias(alias string, roomID string) error { + return d.statements.insertRoomAlias(alias, roomID) +} + +// GetRoomIDFromAlias implements alias.RoomserverAliasAPIDB +func (d *Database) GetRoomIDFromAlias(alias string) (string, error) { + return d.statements.selectRoomIDFromAlias(alias) +} + +// GetAliasesFromRoomID implements alias.RoomserverAliasAPIDB +func (d *Database) GetAliasesFromRoomID(roomID string) ([]string, error) { + return d.statements.selectAliasesFromRoomID(roomID) +} + +// RemoveRoomAlias implements alias.RoomserverAliasAPIDB +func (d *Database) RemoveRoomAlias(alias string) error { + return d.statements.deleteRoomAlias(alias) +} + // StateEntriesForTuples implements state.RoomStateDatabase func (d *Database) StateEntriesForTuples( stateBlockNIDs []types.StateBlockNID, stateKeyTuples []types.StateKeyTuple,