Implement public rooms APIs (#185)
* Move events contents to common * Basic database structure * Complete database update * Support visibility update and retrieval * Add HTTP methods for visibility update and retrieval * Add the database for the new component * Add a listener for the new component * Fix attribute update statements * Create public rooms component * Fix failing test * Add roomserver consumer * Fix a bug in aliases creation * Add a check on type * Implement public rooms directory * Use auth API for visibility update * Support filtering * Add component to monolith * Various fixes * Fix computation of next public rooms batch * Retrieve state events from the roomserver query API + avoid dupes on join * Split update of string or boolean attribute in two separate functions * Use event type to detect duplicate joins * Improve the joined members counter computation * Use event.RoomID()main
parent
fc86821a90
commit
b15ce900ab
|
@ -72,6 +72,7 @@ database:
|
||||||
room_server: "postgres://dendrite:itsasecret@localhost/dendrite_roomserver?sslmode=disable"
|
room_server: "postgres://dendrite:itsasecret@localhost/dendrite_roomserver?sslmode=disable"
|
||||||
server_key: "postgres://dendrite:itsasecret@localhost/dendrite_serverkey?sslmode=disable"
|
server_key: "postgres://dendrite:itsasecret@localhost/dendrite_serverkey?sslmode=disable"
|
||||||
federation_sender: "postgres://dendrite:itsasecret@localhost/dendrite_federationsender?sslmode=disable"
|
federation_sender: "postgres://dendrite:itsasecret@localhost/dendrite_federationsender?sslmode=disable"
|
||||||
|
public_rooms_api: "postgres://dendrite:itsasecret@localhost/dendrite_publicroomsapi?sslmode=disable"
|
||||||
|
|
||||||
# The TCP host:port pairs to bind the internal HTTP APIs to.
|
# The TCP host:port pairs to bind the internal HTTP APIs to.
|
||||||
# These shouldn't be exposed to the public internet.
|
# These shouldn't be exposed to the public internet.
|
||||||
|
@ -82,3 +83,4 @@ listen:
|
||||||
federation_api: "localhost:7772"
|
federation_api: "localhost:7772"
|
||||||
sync_api: "localhost:7773"
|
sync_api: "localhost:7773"
|
||||||
media_api: "localhost:7774"
|
media_api: "localhost:7774"
|
||||||
|
public_rooms_api: "localhost:7775"
|
||||||
|
|
|
@ -23,6 +23,7 @@ import (
|
||||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||||
"github.com/matrix-org/dendrite/clientapi/producers"
|
"github.com/matrix-org/dendrite/clientapi/producers"
|
||||||
|
"github.com/matrix-org/dendrite/common"
|
||||||
"github.com/matrix-org/dendrite/common/config"
|
"github.com/matrix-org/dendrite/common/config"
|
||||||
"github.com/matrix-org/dendrite/roomserver/api"
|
"github.com/matrix-org/dendrite/roomserver/api"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
@ -271,7 +272,7 @@ func buildMembershipEvents(
|
||||||
StateKey: &userID,
|
StateKey: &userID,
|
||||||
}
|
}
|
||||||
|
|
||||||
content := events.MemberContent{
|
content := common.MemberContent{
|
||||||
Membership: "join",
|
Membership: "join",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -265,20 +265,6 @@ 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{}{}, "", ""},
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
unstableMux.Handle("/thirdparty/protocols",
|
unstableMux.Handle("/thirdparty/protocols",
|
||||||
common.MakeAPI("thirdparty_protocols", func(req *http.Request) util.JSONResponse {
|
common.MakeAPI("thirdparty_protocols", func(req *http.Request) util.JSONResponse {
|
||||||
// TODO: Return the third party protcols
|
// TODO: Return the third party protcols
|
||||||
|
|
|
@ -24,10 +24,10 @@ import (
|
||||||
log "github.com/Sirupsen/logrus"
|
log "github.com/Sirupsen/logrus"
|
||||||
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
||||||
"github.com/matrix-org/dendrite/clientapi/auth/storage/accounts"
|
"github.com/matrix-org/dendrite/clientapi/auth/storage/accounts"
|
||||||
"github.com/matrix-org/dendrite/clientapi/events"
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||||
"github.com/matrix-org/dendrite/clientapi/producers"
|
"github.com/matrix-org/dendrite/clientapi/producers"
|
||||||
|
"github.com/matrix-org/dendrite/common"
|
||||||
"github.com/matrix-org/dendrite/common/config"
|
"github.com/matrix-org/dendrite/common/config"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
|
@ -132,7 +132,7 @@ func createRoom(req *http.Request, device *authtypes.Device,
|
||||||
return httputil.LogThenError(req, err)
|
return httputil.LogThenError(req, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
membershipContent := events.MemberContent{
|
membershipContent := common.MemberContent{
|
||||||
Membership: "join",
|
Membership: "join",
|
||||||
DisplayName: profile.DisplayName,
|
DisplayName: profile.DisplayName,
|
||||||
AvatarURL: profile.AvatarURL,
|
AvatarURL: profile.AvatarURL,
|
||||||
|
@ -159,16 +159,16 @@ func createRoom(req *http.Request, device *authtypes.Device,
|
||||||
// harder to reason about, hence sticking to a strict static ordering.
|
// harder to reason about, hence sticking to a strict static ordering.
|
||||||
// TODO: Synapse has txn/token ID on each event. Do we need to do this here?
|
// TODO: Synapse has txn/token ID on each event. Do we need to do this here?
|
||||||
eventsToMake := []fledglingEvent{
|
eventsToMake := []fledglingEvent{
|
||||||
{"m.room.create", "", events.CreateContent{Creator: userID}},
|
{"m.room.create", "", common.CreateContent{Creator: userID}},
|
||||||
{"m.room.member", userID, membershipContent},
|
{"m.room.member", userID, membershipContent},
|
||||||
{"m.room.power_levels", "", events.InitialPowerLevelsContent(userID)},
|
{"m.room.power_levels", "", common.InitialPowerLevelsContent(userID)},
|
||||||
// TODO: m.room.canonical_alias
|
// TODO: m.room.canonical_alias
|
||||||
{"m.room.join_rules", "", events.JoinRulesContent{"public"}}, // FIXME: Allow this to be changed
|
{"m.room.join_rules", "", common.JoinRulesContent{"public"}}, // FIXME: Allow this to be changed
|
||||||
{"m.room.history_visibility", "", events.HistoryVisibilityContent{"joined"}}, // FIXME: Allow this to be changed
|
{"m.room.history_visibility", "", common.HistoryVisibilityContent{"joined"}}, // FIXME: Allow this to be changed
|
||||||
{"m.room.guest_access", "", events.GuestAccessContent{"can_join"}}, // FIXME: Allow this to be changed
|
{"m.room.guest_access", "", common.GuestAccessContent{"can_join"}}, // FIXME: Allow this to be changed
|
||||||
// TODO: Other initial state items
|
// TODO: Other initial state items
|
||||||
{"m.room.name", "", events.NameContent{r.Name}}, // FIXME: Only send the name event if a name is supplied, to avoid sending a false room name removal event
|
{"m.room.name", "", common.NameContent{r.Name}}, // FIXME: Only send the name event if a name is supplied, to avoid sending a false room name removal event
|
||||||
{"m.room.topic", "", events.TopicContent{r.Topic}},
|
{"m.room.topic", "", common.TopicContent{r.Topic}},
|
||||||
// TODO: invite events
|
// TODO: invite events
|
||||||
// TODO: 3pid invite events
|
// TODO: 3pid invite events
|
||||||
// TODO: m.room.aliases
|
// TODO: m.room.aliases
|
||||||
|
|
|
@ -23,6 +23,7 @@ import (
|
||||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||||
"github.com/matrix-org/dendrite/clientapi/producers"
|
"github.com/matrix-org/dendrite/clientapi/producers"
|
||||||
|
"github.com/matrix-org/dendrite/common"
|
||||||
"github.com/matrix-org/dendrite/common/config"
|
"github.com/matrix-org/dendrite/common/config"
|
||||||
"github.com/matrix-org/dendrite/roomserver/api"
|
"github.com/matrix-org/dendrite/roomserver/api"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
@ -69,7 +70,7 @@ func SendMembership(
|
||||||
membership = "leave"
|
membership = "leave"
|
||||||
}
|
}
|
||||||
|
|
||||||
content := events.MemberContent{
|
content := common.MemberContent{
|
||||||
Membership: membership,
|
Membership: membership,
|
||||||
DisplayName: profile.DisplayName,
|
DisplayName: profile.DisplayName,
|
||||||
AvatarURL: profile.AvatarURL,
|
AvatarURL: profile.AvatarURL,
|
||||||
|
|
|
@ -47,12 +47,13 @@ Arguments:
|
||||||
`
|
`
|
||||||
|
|
||||||
var (
|
var (
|
||||||
syncServerURL = flag.String("sync-api-server-url", "", "The base URL of the listening 'dendrite-sync-api-server' process. E.g. 'http://localhost:4200'")
|
syncServerURL = flag.String("sync-api-server-url", "", "The base URL of the listening 'dendrite-sync-api-server' process. E.g. 'http://localhost:4200'")
|
||||||
clientAPIURL = flag.String("client-api-server-url", "", "The base URL of the listening 'dendrite-client-api-server' process. E.g. 'http://localhost:4321'")
|
clientAPIURL = flag.String("client-api-server-url", "", "The base URL of the listening 'dendrite-client-api-server' process. E.g. 'http://localhost:4321'")
|
||||||
mediaAPIURL = flag.String("media-api-server-url", "", "The base URL of the listening 'dendrite-media-api-server' process. E.g. 'http://localhost:7779'")
|
mediaAPIURL = flag.String("media-api-server-url", "", "The base URL of the listening 'dendrite-media-api-server' process. E.g. 'http://localhost:7779'")
|
||||||
bindAddress = flag.String("bind-address", ":8008", "The listening port for the proxy.")
|
publicRoomsAPIURL = flag.String("public-rooms-api-server-url", "", "The base URL of the listening 'dendrite-public-rooms-api-server' process. E.g. 'http://localhost:7775'")
|
||||||
certFile = flag.String("tls-cert", "", "The PEM formatted X509 certificate to use for TLS")
|
bindAddress = flag.String("bind-address", ":8008", "The listening port for the proxy.")
|
||||||
keyFile = flag.String("tls-key", "", "The PEM private key to use for TLS")
|
certFile = flag.String("tls-cert", "", "The PEM formatted X509 certificate to use for TLS")
|
||||||
|
keyFile = flag.String("tls-key", "", "The PEM private key to use for TLS")
|
||||||
)
|
)
|
||||||
|
|
||||||
func makeProxy(targetURL string) (*httputil.ReverseProxy, error) {
|
func makeProxy(targetURL string) (*httputil.ReverseProxy, error) {
|
||||||
|
@ -122,6 +123,12 @@ func main() {
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if *publicRoomsAPIURL == "" {
|
||||||
|
flag.Usage()
|
||||||
|
fmt.Fprintln(os.Stderr, "no --public-rooms-api-server-url specified.")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
syncProxy, err := makeProxy(*syncServerURL)
|
syncProxy, err := makeProxy(*syncServerURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
@ -134,8 +141,14 @@ func main() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
publicRoomsProxy, err := makeProxy(*publicRoomsAPIURL)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
http.Handle("/_matrix/client/r0/sync", syncProxy)
|
http.Handle("/_matrix/client/r0/sync", syncProxy)
|
||||||
|
http.Handle("/_matrix/client/r0/directory/list/", publicRoomsProxy)
|
||||||
|
http.Handle("/_matrix/client/r0/publicRooms", publicRoomsProxy)
|
||||||
http.Handle("/_matrix/media/v1/", mediaProxy)
|
http.Handle("/_matrix/media/v1/", mediaProxy)
|
||||||
http.Handle("/", clientProxy)
|
http.Handle("/", clientProxy)
|
||||||
|
|
||||||
|
@ -146,9 +159,11 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("Proxying requests to:")
|
fmt.Println("Proxying requests to:")
|
||||||
fmt.Println(" /_matrix/client/r0/sync => ", *syncServerURL+"/api/_matrix/client/r0/sync")
|
fmt.Println(" /_matrix/client/r0/sync => ", *syncServerURL+"/api/_matrix/client/r0/sync")
|
||||||
fmt.Println(" /_matrix/media/v1 => ", *mediaAPIURL+"/api/_matrix/media/v1")
|
fmt.Println(" /_matrix/client/r0/directory/list => ", *publicRoomsAPIURL+"/_matrix/client/r0/directory/list")
|
||||||
fmt.Println(" /* => ", *clientAPIURL+"/api/*")
|
fmt.Println(" /_matrix/client/r0/publicRooms => ", *publicRoomsAPIURL+"/_matrix/media/client/r0/publicRooms")
|
||||||
|
fmt.Println(" /_matrix/media/v1 => ", *mediaAPIURL+"/api/_matrix/media/v1")
|
||||||
|
fmt.Println(" /* => ", *clientAPIURL+"/api/*")
|
||||||
fmt.Println("Listening on ", *bindAddress)
|
fmt.Println("Listening on ", *bindAddress)
|
||||||
if *certFile != "" && *keyFile != "" {
|
if *certFile != "" && *keyFile != "" {
|
||||||
panic(srv.ListenAndServeTLS(*certFile, *keyFile))
|
panic(srv.ListenAndServeTLS(*certFile, *keyFile))
|
||||||
|
|
|
@ -52,6 +52,10 @@ import (
|
||||||
"github.com/matrix-org/dendrite/federationsender/queue"
|
"github.com/matrix-org/dendrite/federationsender/queue"
|
||||||
federationsender_storage "github.com/matrix-org/dendrite/federationsender/storage"
|
federationsender_storage "github.com/matrix-org/dendrite/federationsender/storage"
|
||||||
|
|
||||||
|
publicroomsapi_consumers "github.com/matrix-org/dendrite/publicroomsapi/consumers"
|
||||||
|
publicroomsapi_routing "github.com/matrix-org/dendrite/publicroomsapi/routing"
|
||||||
|
publicroomsapi_storage "github.com/matrix-org/dendrite/publicroomsapi/storage"
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
log "github.com/Sirupsen/logrus"
|
||||||
sarama "gopkg.in/Shopify/sarama.v1"
|
sarama "gopkg.in/Shopify/sarama.v1"
|
||||||
)
|
)
|
||||||
|
@ -119,6 +123,7 @@ type monolith struct {
|
||||||
mediaAPIDB *mediaapi_storage.Database
|
mediaAPIDB *mediaapi_storage.Database
|
||||||
syncAPIDB *syncapi_storage.SyncServerDatabase
|
syncAPIDB *syncapi_storage.SyncServerDatabase
|
||||||
federationSenderDB *federationsender_storage.Database
|
federationSenderDB *federationsender_storage.Database
|
||||||
|
publicRoomsAPIDB *publicroomsapi_storage.PublicRoomsServerDatabase
|
||||||
|
|
||||||
federation *gomatrixserverlib.FederationClient
|
federation *gomatrixserverlib.FederationClient
|
||||||
keyRing gomatrixserverlib.KeyRing
|
keyRing gomatrixserverlib.KeyRing
|
||||||
|
@ -171,6 +176,10 @@ func (m *monolith) setupDatabases() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Panicf("startup: failed to create federation sender database with data source %s : %s", m.cfg.Database.FederationSender, err)
|
log.Panicf("startup: failed to create federation sender database with data source %s : %s", m.cfg.Database.FederationSender, err)
|
||||||
}
|
}
|
||||||
|
m.publicRoomsAPIDB, err = publicroomsapi_storage.NewPublicRoomsServerDatabase(string(m.cfg.Database.PublicRoomsAPI))
|
||||||
|
if err != nil {
|
||||||
|
log.Panicf("startup: failed to setup public rooms api database with data source %s : %s", m.cfg.Database.PublicRoomsAPI, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *monolith) setupFederation() {
|
func (m *monolith) setupFederation() {
|
||||||
|
@ -290,6 +299,13 @@ func (m *monolith) setupConsumers() {
|
||||||
log.Panicf("startup: failed to start client API server consumer: %s", err)
|
log.Panicf("startup: failed to start client API server consumer: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
publicRoomsAPIConsumer := publicroomsapi_consumers.NewOutputRoomEvent(
|
||||||
|
m.cfg, m.kafkaConsumer(), m.publicRoomsAPIDB, m.queryAPI,
|
||||||
|
)
|
||||||
|
if err = publicRoomsAPIConsumer.Start(); err != nil {
|
||||||
|
log.Panicf("startup: failed to start room server consumer: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
federationSenderQueues := queue.NewOutgoingQueues(m.cfg.Matrix.ServerName, m.federation)
|
federationSenderQueues := queue.NewOutgoingQueues(m.cfg.Matrix.ServerName, m.federation)
|
||||||
|
|
||||||
federationSenderRoomConsumer := federationsender_consumers.NewOutputRoomEvent(
|
federationSenderRoomConsumer := federationsender_consumers.NewOutputRoomEvent(
|
||||||
|
@ -318,4 +334,6 @@ func (m *monolith) setupAPIs() {
|
||||||
federationapi_routing.Setup(
|
federationapi_routing.Setup(
|
||||||
m.api, *m.cfg, m.queryAPI, m.roomServerProducer, m.keyRing, m.federation,
|
m.api, *m.cfg, m.queryAPI, m.roomServerProducer, m.keyRing, m.federation,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
publicroomsapi_routing.Setup(m.api, m.deviceDB, m.publicRoomsAPIDB)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,85 @@
|
||||||
|
// 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 main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/matrix-org/dendrite/clientapi/auth/storage/devices"
|
||||||
|
"github.com/matrix-org/dendrite/common"
|
||||||
|
"github.com/matrix-org/dendrite/common/config"
|
||||||
|
"github.com/matrix-org/dendrite/publicroomsapi/consumers"
|
||||||
|
"github.com/matrix-org/dendrite/publicroomsapi/routing"
|
||||||
|
"github.com/matrix-org/dendrite/publicroomsapi/storage"
|
||||||
|
"github.com/matrix-org/dendrite/roomserver/api"
|
||||||
|
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
|
sarama "gopkg.in/Shopify/sarama.v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
var configPath = flag.String("config", "dendrite.yaml", "The path to the config file. For more information, see the config file in this repository.")
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
common.SetupLogging(os.Getenv("LOG_DIR"))
|
||||||
|
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
if *configPath == "" {
|
||||||
|
log.Fatal("--config must be supplied")
|
||||||
|
}
|
||||||
|
cfg, err := config.Load(*configPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Invalid config file: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
queryAPI := api.NewRoomserverQueryAPIHTTP(cfg.RoomServerURL(), nil)
|
||||||
|
|
||||||
|
db, err := storage.NewPublicRoomsServerDatabase(string(cfg.Database.PublicRoomsAPI))
|
||||||
|
if err != nil {
|
||||||
|
log.Panicf("startup: failed to create public rooms server database with data source %s : %s", cfg.Database.PublicRoomsAPI, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
deviceDB, err := devices.NewDatabase(string(cfg.Database.Device), cfg.Matrix.ServerName)
|
||||||
|
if err != nil {
|
||||||
|
log.Panicf("startup: failed to create device database with data source %s : %s", cfg.Database.Device, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
kafkaConsumer, err := sarama.NewConsumer(cfg.Kafka.Addresses, nil)
|
||||||
|
if err != nil {
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
log.ErrorKey: err,
|
||||||
|
"addresses": cfg.Kafka.Addresses,
|
||||||
|
}).Panic("Failed to setup kafka consumers")
|
||||||
|
}
|
||||||
|
|
||||||
|
roomConsumer := consumers.NewOutputRoomEvent(cfg, kafkaConsumer, db, queryAPI)
|
||||||
|
if err != nil {
|
||||||
|
log.Panicf("startup: failed to create room server consumer: %s", err)
|
||||||
|
}
|
||||||
|
if err = roomConsumer.Start(); err != nil {
|
||||||
|
log.Panicf("startup: failed to start room server consumer: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("Starting public rooms server on ", cfg.Listen.PublicRoomsAPI)
|
||||||
|
|
||||||
|
api := mux.NewRouter()
|
||||||
|
routing.Setup(api, deviceDB, db)
|
||||||
|
common.SetupHTTPAPI(http.DefaultServeMux, api)
|
||||||
|
|
||||||
|
log.Fatal(http.ListenAndServe(string(cfg.Listen.PublicRoomsAPI), nil))
|
||||||
|
}
|
|
@ -133,6 +133,9 @@ type Dendrite struct {
|
||||||
// The FederationSender database stores information used by the FederationSender
|
// The FederationSender database stores information used by the FederationSender
|
||||||
// It is only accessed by the FederationSender.
|
// It is only accessed by the FederationSender.
|
||||||
FederationSender DataSource `yaml:"federation_sender"`
|
FederationSender DataSource `yaml:"federation_sender"`
|
||||||
|
// The PublicRoomsAPI database stores information used to compute the public
|
||||||
|
// room directory. It is only accessed by the PublicRoomsAPI server.
|
||||||
|
PublicRoomsAPI DataSource `yaml:"public_rooms_api"`
|
||||||
} `yaml:"database"`
|
} `yaml:"database"`
|
||||||
|
|
||||||
// The internal addresses the components will listen on.
|
// The internal addresses the components will listen on.
|
||||||
|
@ -144,6 +147,7 @@ type Dendrite struct {
|
||||||
SyncAPI Address `yaml:"sync_api"`
|
SyncAPI Address `yaml:"sync_api"`
|
||||||
RoomServer Address `yaml:"room_server"`
|
RoomServer Address `yaml:"room_server"`
|
||||||
FederationSender Address `yaml:"federation_sender"`
|
FederationSender Address `yaml:"federation_sender"`
|
||||||
|
PublicRoomsAPI Address `yaml:"public_rooms_api"`
|
||||||
} `yaml:"listen"`
|
} `yaml:"listen"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package events
|
package common
|
||||||
|
|
||||||
// CreateContent is the event content for http://matrix.org/docs/spec/client_server/r0.2.0.html#m-room-create
|
// CreateContent is the event content for http://matrix.org/docs/spec/client_server/r0.2.0.html#m-room-create
|
||||||
type CreateContent struct {
|
type CreateContent struct {
|
||||||
|
@ -90,3 +90,29 @@ func InitialPowerLevelsContent(roomCreator string) PowerLevelContent {
|
||||||
Users: map[string]int{roomCreator: 100},
|
Users: map[string]int{roomCreator: 100},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AliasesContent is the event content for http://matrix.org/docs/spec/client_server/r0.2.0.html#m-room-aliases
|
||||||
|
type AliasesContent struct {
|
||||||
|
Aliases []string `json:"aliases"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CanonicalAliasContent is the event content for http://matrix.org/docs/spec/client_server/r0.2.0.html#m-room-canonical-alias
|
||||||
|
type CanonicalAliasContent struct {
|
||||||
|
Alias string `json:"alias"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AvatarContent is the event content for http://matrix.org/docs/spec/client_server/r0.2.0.html#m-room-avatar
|
||||||
|
type AvatarContent struct {
|
||||||
|
Info ImageInfo `json:"info,omitempty"`
|
||||||
|
URL string `json:"url"`
|
||||||
|
ThumbnailURL string `json:"thumbnail_url,omitempty"`
|
||||||
|
ThumbnailInfo ImageInfo `json:"thumbnail_info,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImageInfo implements the ImageInfo structure from http://matrix.org/docs/spec/client_server/r0.2.0.html#m-room-avatar
|
||||||
|
type ImageInfo struct {
|
||||||
|
Mimetype string `json:"mimetype"`
|
||||||
|
Height int64 `json:"h"`
|
||||||
|
Width int64 `json:"w"`
|
||||||
|
Size int64 `json:"size"`
|
||||||
|
}
|
|
@ -95,12 +95,14 @@ func MakeConfig(configDir, kafkaURI, database, host string, startPort int) (*con
|
||||||
cfg.Database.RoomServer = config.DataSource(database)
|
cfg.Database.RoomServer = config.DataSource(database)
|
||||||
cfg.Database.ServerKey = config.DataSource(database)
|
cfg.Database.ServerKey = config.DataSource(database)
|
||||||
cfg.Database.SyncAPI = config.DataSource(database)
|
cfg.Database.SyncAPI = config.DataSource(database)
|
||||||
|
cfg.Database.PublicRoomsAPI = config.DataSource(database)
|
||||||
|
|
||||||
cfg.Listen.ClientAPI = assignAddress()
|
cfg.Listen.ClientAPI = assignAddress()
|
||||||
cfg.Listen.FederationAPI = assignAddress()
|
cfg.Listen.FederationAPI = assignAddress()
|
||||||
cfg.Listen.MediaAPI = assignAddress()
|
cfg.Listen.MediaAPI = assignAddress()
|
||||||
cfg.Listen.RoomServer = assignAddress()
|
cfg.Listen.RoomServer = assignAddress()
|
||||||
cfg.Listen.SyncAPI = assignAddress()
|
cfg.Listen.SyncAPI = assignAddress()
|
||||||
|
cfg.Listen.PublicRoomsAPI = assignAddress()
|
||||||
|
|
||||||
return &cfg, port, nil
|
return &cfg, port, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -94,6 +94,7 @@ func StartProxy(bindAddr string, cfg *config.Dendrite) (*exec.Cmd, chan error) {
|
||||||
"--sync-api-server-url", "http://" + string(cfg.Listen.SyncAPI),
|
"--sync-api-server-url", "http://" + string(cfg.Listen.SyncAPI),
|
||||||
"--client-api-server-url", "http://" + string(cfg.Listen.ClientAPI),
|
"--client-api-server-url", "http://" + string(cfg.Listen.ClientAPI),
|
||||||
"--media-api-server-url", "http://" + string(cfg.Listen.MediaAPI),
|
"--media-api-server-url", "http://" + string(cfg.Listen.MediaAPI),
|
||||||
|
"--public-rooms-api-server-url", "http://" + string(cfg.Listen.PublicRoomsAPI),
|
||||||
"--tls-cert", "server.crt",
|
"--tls-cert", "server.crt",
|
||||||
"--tls-key", "server.key",
|
"--tls-key", "server.key",
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
# Public rooms API
|
||||||
|
|
||||||
|
This server is responsible for serving requests hitting `/publicRooms` and `/directory/list/room/{roomID}` as per:
|
||||||
|
|
||||||
|
https://matrix.org/docs/spec/client_server/r0.2.0.html#listing-rooms
|
|
@ -0,0 +1,101 @@
|
||||||
|
// 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 consumers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
|
"github.com/matrix-org/dendrite/common"
|
||||||
|
"github.com/matrix-org/dendrite/common/config"
|
||||||
|
"github.com/matrix-org/dendrite/publicroomsapi/storage"
|
||||||
|
"github.com/matrix-org/dendrite/roomserver/api"
|
||||||
|
sarama "gopkg.in/Shopify/sarama.v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
// OutputRoomEvent consumes events that originated in the room server.
|
||||||
|
type OutputRoomEvent struct {
|
||||||
|
roomServerConsumer *common.ContinualConsumer
|
||||||
|
db *storage.PublicRoomsServerDatabase
|
||||||
|
query api.RoomserverQueryAPI
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewOutputRoomEvent creates a new OutputRoomEvent consumer. Call Start() to begin consuming from room servers.
|
||||||
|
func NewOutputRoomEvent(
|
||||||
|
cfg *config.Dendrite,
|
||||||
|
kafkaConsumer sarama.Consumer,
|
||||||
|
store *storage.PublicRoomsServerDatabase,
|
||||||
|
queryAPI api.RoomserverQueryAPI,
|
||||||
|
) *OutputRoomEvent {
|
||||||
|
consumer := common.ContinualConsumer{
|
||||||
|
Topic: string(cfg.Kafka.Topics.OutputRoomEvent),
|
||||||
|
Consumer: kafkaConsumer,
|
||||||
|
PartitionStore: store,
|
||||||
|
}
|
||||||
|
s := &OutputRoomEvent{
|
||||||
|
roomServerConsumer: &consumer,
|
||||||
|
db: store,
|
||||||
|
query: queryAPI,
|
||||||
|
}
|
||||||
|
consumer.ProcessMessage = s.onMessage
|
||||||
|
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start consuming from room servers
|
||||||
|
func (s *OutputRoomEvent) Start() error {
|
||||||
|
return s.roomServerConsumer.Start()
|
||||||
|
}
|
||||||
|
|
||||||
|
// onMessage is called when the sync server receives a new event from the room server output log.
|
||||||
|
func (s *OutputRoomEvent) onMessage(msg *sarama.ConsumerMessage) error {
|
||||||
|
// Parse out the event JSON
|
||||||
|
var output api.OutputEvent
|
||||||
|
if err := json.Unmarshal(msg.Value, &output); err != nil {
|
||||||
|
// If the message was invalid, log it and move on to the next message in the stream
|
||||||
|
log.WithError(err).Errorf("roomserver output log: message parse failure")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if output.Type != api.OutputTypeNewRoomEvent {
|
||||||
|
log.WithField("type", output.Type).Debug(
|
||||||
|
"roomserver output log: ignoring unknown output type",
|
||||||
|
)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ev := output.NewRoomEvent.Event
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"event_id": ev.EventID(),
|
||||||
|
"room_id": ev.RoomID(),
|
||||||
|
"type": ev.Type(),
|
||||||
|
}).Info("received event from roomserver")
|
||||||
|
|
||||||
|
addQueryReq := api.QueryEventsByIDRequest{output.NewRoomEvent.AddsStateEventIDs}
|
||||||
|
var addQueryRes api.QueryEventsByIDResponse
|
||||||
|
if err := s.query.QueryEventsByID(&addQueryReq, &addQueryRes); err != nil {
|
||||||
|
log.Warn(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
remQueryReq := api.QueryEventsByIDRequest{output.NewRoomEvent.RemovesStateEventIDs}
|
||||||
|
var remQueryRes api.QueryEventsByIDResponse
|
||||||
|
if err := s.query.QueryEventsByID(&remQueryReq, &remQueryRes); err != nil {
|
||||||
|
log.Warn(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.db.UpdateRoomFromEvents(addQueryRes.Events, remQueryRes.Events)
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
// 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 directory
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||||
|
"github.com/matrix-org/dendrite/publicroomsapi/storage"
|
||||||
|
|
||||||
|
"github.com/matrix-org/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
type roomVisibility struct {
|
||||||
|
Visibility string `json:"visibility"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetVisibility implements GET /directory/list/room/{roomID}
|
||||||
|
func GetVisibility(
|
||||||
|
req *http.Request, publicRoomsDatabase *storage.PublicRoomsServerDatabase,
|
||||||
|
roomID string,
|
||||||
|
) util.JSONResponse {
|
||||||
|
isPublic, err := publicRoomsDatabase.GetRoomVisibility(roomID)
|
||||||
|
if err != nil {
|
||||||
|
return httputil.LogThenError(req, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var v roomVisibility
|
||||||
|
if isPublic {
|
||||||
|
v.Visibility = "public"
|
||||||
|
} else {
|
||||||
|
v.Visibility = "private"
|
||||||
|
}
|
||||||
|
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: 200,
|
||||||
|
JSON: v,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetVisibility implements PUT /directory/list/room/{roomID}
|
||||||
|
// TODO: Check if user has the power level to edit the room visibility
|
||||||
|
func SetVisibility(
|
||||||
|
req *http.Request, publicRoomsDatabase *storage.PublicRoomsServerDatabase,
|
||||||
|
roomID string,
|
||||||
|
) util.JSONResponse {
|
||||||
|
var v roomVisibility
|
||||||
|
if reqErr := httputil.UnmarshalJSONRequest(req, &v); reqErr != nil {
|
||||||
|
return *reqErr
|
||||||
|
}
|
||||||
|
|
||||||
|
isPublic := v.Visibility == "public"
|
||||||
|
if err := publicRoomsDatabase.SetRoomVisibility(isPublic, roomID); err != nil {
|
||||||
|
return httputil.LogThenError(req, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: 200,
|
||||||
|
JSON: struct{}{},
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,113 @@
|
||||||
|
// 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 directory
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||||
|
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||||
|
"github.com/matrix-org/dendrite/publicroomsapi/storage"
|
||||||
|
"github.com/matrix-org/dendrite/publicroomsapi/types"
|
||||||
|
"github.com/matrix-org/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
type publicRoomReq struct {
|
||||||
|
Since string `json:"since,omitempty"`
|
||||||
|
Limit int16 `json:"limit,omitempty"`
|
||||||
|
Filter filter `json:"filter,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type filter struct {
|
||||||
|
SearchTerms string `json:"generic_search_term,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type publicRoomRes struct {
|
||||||
|
Chunk []types.PublicRoom `json:"chunk"`
|
||||||
|
NextBatch string `json:"next_batch,omitempty"`
|
||||||
|
PrevBatch string `json:"prev_batch,omitempty"`
|
||||||
|
Estimate int64 `json:"total_room_count_estimate,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPublicRooms implements GET /publicRooms
|
||||||
|
func GetPublicRooms(
|
||||||
|
req *http.Request, publicRoomDatabase *storage.PublicRoomsServerDatabase,
|
||||||
|
) util.JSONResponse {
|
||||||
|
var limit int16
|
||||||
|
var offset int64
|
||||||
|
var request publicRoomReq
|
||||||
|
var response publicRoomRes
|
||||||
|
|
||||||
|
if fillErr := fillPublicRoomsReq(req, &request); fillErr != nil {
|
||||||
|
return *fillErr
|
||||||
|
}
|
||||||
|
|
||||||
|
limit = request.Limit
|
||||||
|
offset, err := strconv.ParseInt(request.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(request.Since) > 0 {
|
||||||
|
return httputil.LogThenError(req, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if response.Estimate, err = publicRoomDatabase.CountPublicRooms(); err != nil {
|
||||||
|
return httputil.LogThenError(req, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if offset > 0 {
|
||||||
|
response.PrevBatch = strconv.Itoa(int(offset) - 1)
|
||||||
|
}
|
||||||
|
nextIndex := int(offset) + int(limit)
|
||||||
|
if response.Estimate > int64(nextIndex) {
|
||||||
|
response.NextBatch = strconv.Itoa(nextIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
if response.Chunk, err = publicRoomDatabase.GetPublicRooms(offset, limit, request.Filter.SearchTerms); err != nil {
|
||||||
|
return httputil.LogThenError(req, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: 200,
|
||||||
|
JSON: response,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fillPublicRoomsReq fills the Limit, Since and Filter attributes of a GET or POST request
|
||||||
|
// on /publicRooms by parsing the incoming HTTP request
|
||||||
|
func fillPublicRoomsReq(httpReq *http.Request, request *publicRoomReq) *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
|
||||||
|
}
|
||||||
|
request.Limit = int16(limit)
|
||||||
|
request.Since = httpReq.FormValue("since")
|
||||||
|
return nil
|
||||||
|
} else if httpReq.Method == "POST" {
|
||||||
|
if reqErr := httputil.UnmarshalJSONRequest(httpReq, request); reqErr != nil {
|
||||||
|
return reqErr
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &util.JSONResponse{
|
||||||
|
Code: 405,
|
||||||
|
JSON: jsonerror.NotFound("Bad method"),
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
// 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 routing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
||||||
|
"github.com/matrix-org/dendrite/clientapi/auth/storage/devices"
|
||||||
|
"github.com/matrix-org/dendrite/common"
|
||||||
|
"github.com/matrix-org/dendrite/publicroomsapi/directory"
|
||||||
|
"github.com/matrix-org/dendrite/publicroomsapi/storage"
|
||||||
|
"github.com/matrix-org/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
const pathPrefixR0 = "/_matrix/client/r0"
|
||||||
|
|
||||||
|
// Setup configures the given mux with publicroomsapi server listeners
|
||||||
|
func Setup(apiMux *mux.Router, deviceDB *devices.Database, publicRoomsDB *storage.PublicRoomsServerDatabase) {
|
||||||
|
r0mux := apiMux.PathPrefix(pathPrefixR0).Subrouter()
|
||||||
|
r0mux.Handle("/directory/list/room/{roomID}",
|
||||||
|
common.MakeAPI("directory_list", func(req *http.Request) util.JSONResponse {
|
||||||
|
vars := mux.Vars(req)
|
||||||
|
return directory.GetVisibility(req, publicRoomsDB, 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 directory.SetVisibility(req, publicRoomsDB, vars["roomID"])
|
||||||
|
}),
|
||||||
|
).Methods("PUT", "OPTIONS")
|
||||||
|
r0mux.Handle("/publicRooms",
|
||||||
|
common.MakeAPI("public_rooms", func(req *http.Request) util.JSONResponse {
|
||||||
|
return directory.GetPublicRooms(req, publicRoomsDB)
|
||||||
|
}),
|
||||||
|
).Methods("GET", "POST", "OPTIONS")
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
// 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"
|
||||||
|
)
|
||||||
|
|
||||||
|
// a statementList is a list of SQL statements to prepare and a pointer to where to store the resulting prepared statement.
|
||||||
|
type statementList []struct {
|
||||||
|
statement **sql.Stmt
|
||||||
|
sql string
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepare the SQL for each statement in the list and assign the result to the prepared statement.
|
||||||
|
func (s statementList) prepare(db *sql.DB) (err error) {
|
||||||
|
for _, statement := range s {
|
||||||
|
if *statement.statement, err = db.Prepare(statement.sql); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,268 @@
|
||||||
|
// 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"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/lib/pq"
|
||||||
|
"github.com/matrix-org/dendrite/publicroomsapi/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
var editableAttributes = []string{
|
||||||
|
"aliases",
|
||||||
|
"canonical_alias",
|
||||||
|
"name",
|
||||||
|
"topic",
|
||||||
|
"world_readable",
|
||||||
|
"guest_can_join",
|
||||||
|
"avatar_url",
|
||||||
|
"visibility",
|
||||||
|
}
|
||||||
|
|
||||||
|
const publicRoomsSchema = `
|
||||||
|
-- Stores all of the rooms with data needed to create the server's room directory
|
||||||
|
CREATE TABLE IF NOT EXISTS publicroomsapi_public_rooms(
|
||||||
|
-- The room's ID
|
||||||
|
room_id TEXT NOT NULL PRIMARY KEY,
|
||||||
|
-- Number of joined members in the room
|
||||||
|
joined_members INTEGER NOT NULL DEFAULT 0,
|
||||||
|
-- Aliases of the room (empty array if none)
|
||||||
|
aliases TEXT[] NOT NULL DEFAULT '{}'::TEXT[],
|
||||||
|
-- Canonical alias of the room (empty string if none)
|
||||||
|
canonical_alias TEXT NOT NULL DEFAULT '',
|
||||||
|
-- Name of the room (empty string if none)
|
||||||
|
name TEXT NOT NULL DEFAULT '',
|
||||||
|
-- Topic of the room (empty string if none)
|
||||||
|
topic TEXT NOT NULL DEFAULT '',
|
||||||
|
-- Is the room world readable?
|
||||||
|
world_readable BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
-- Can guest join the room?
|
||||||
|
guest_can_join BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
-- URL of the room avatar (empty string if none)
|
||||||
|
avatar_url TEXT NOT NULL DEFAULT '',
|
||||||
|
-- Visibility of the room: true means the room is publicly visible, false
|
||||||
|
-- means the room is private
|
||||||
|
visibility BOOLEAN NOT NULL DEFAULT false
|
||||||
|
);
|
||||||
|
`
|
||||||
|
|
||||||
|
const countPublicRoomsSQL = "" +
|
||||||
|
"SELECT COUNT(*) FROM publicroomsapi_public_rooms" +
|
||||||
|
" WHERE visibility = true"
|
||||||
|
|
||||||
|
const selectPublicRoomsSQL = "" +
|
||||||
|
"SELECT room_id, joined_members, aliases, canonical_alias, name, topic, world_readable, guest_can_join, avatar_url" +
|
||||||
|
" FROM publicroomsapi_public_rooms WHERE visibility = true" +
|
||||||
|
" ORDER BY joined_members DESC" +
|
||||||
|
" OFFSET $1"
|
||||||
|
|
||||||
|
const selectPublicRoomsWithLimitSQL = "" +
|
||||||
|
"SELECT room_id, joined_members, aliases, canonical_alias, name, topic, world_readable, guest_can_join, avatar_url" +
|
||||||
|
" FROM publicroomsapi_public_rooms WHERE visibility = true" +
|
||||||
|
" ORDER BY joined_members DESC" +
|
||||||
|
" OFFSET $1 LIMIT $2"
|
||||||
|
|
||||||
|
const selectPublicRoomsWithFilterSQL = "" +
|
||||||
|
"SELECT room_id, joined_members, aliases, canonical_alias, name, topic, world_readable, guest_can_join, avatar_url" +
|
||||||
|
" FROM publicroomsapi_public_rooms" +
|
||||||
|
" WHERE visibility = true" +
|
||||||
|
" AND (LOWER(name) LIKE LOWER($1)" +
|
||||||
|
" OR LOWER(topic) LIKE LOWER($1)" +
|
||||||
|
" OR LOWER(ARRAY_TO_STRING(aliases, ',')) LIKE LOWER($1))" +
|
||||||
|
" ORDER BY joined_members DESC" +
|
||||||
|
" OFFSET $2"
|
||||||
|
|
||||||
|
const selectPublicRoomsWithLimitAndFilterSQL = "" +
|
||||||
|
"SELECT room_id, joined_members, aliases, canonical_alias, name, topic, world_readable, guest_can_join, avatar_url" +
|
||||||
|
" FROM publicroomsapi_public_rooms" +
|
||||||
|
" WHERE visibility = true" +
|
||||||
|
" AND (LOWER(name) LIKE LOWER($1)" +
|
||||||
|
" OR LOWER(topic) LIKE LOWER($1)" +
|
||||||
|
" OR LOWER(ARRAY_TO_STRING(aliases, ',')) LIKE LOWER($1))" +
|
||||||
|
" ORDER BY joined_members DESC" +
|
||||||
|
" OFFSET $2 LIMIT $3"
|
||||||
|
|
||||||
|
const selectRoomVisibilitySQL = "" +
|
||||||
|
"SELECT visibility FROM publicroomsapi_public_rooms" +
|
||||||
|
" WHERE room_id = $1"
|
||||||
|
|
||||||
|
const insertNewRoomSQL = "" +
|
||||||
|
"INSERT INTO publicroomsapi_public_rooms(room_id)" +
|
||||||
|
" VALUES ($1)"
|
||||||
|
|
||||||
|
const incrementJoinedMembersInRoomSQL = "" +
|
||||||
|
"UPDATE publicroomsapi_public_rooms" +
|
||||||
|
" SET joined_members = joined_members + 1" +
|
||||||
|
" WHERE room_id = $1"
|
||||||
|
|
||||||
|
const decrementJoinedMembersInRoomSQL = "" +
|
||||||
|
"UPDATE publicroomsapi_public_rooms" +
|
||||||
|
" SET joined_members = joined_members - 1" +
|
||||||
|
" WHERE room_id = $1"
|
||||||
|
|
||||||
|
const updateRoomAttributeSQL = "" +
|
||||||
|
"UPDATE publicroomsapi_public_rooms" +
|
||||||
|
" SET %s = $1" +
|
||||||
|
" WHERE room_id = $2"
|
||||||
|
|
||||||
|
type publicRoomsStatements struct {
|
||||||
|
countPublicRoomsStmt *sql.Stmt
|
||||||
|
selectPublicRoomsStmt *sql.Stmt
|
||||||
|
selectPublicRoomsWithLimitStmt *sql.Stmt
|
||||||
|
selectPublicRoomsWithFilterStmt *sql.Stmt
|
||||||
|
selectPublicRoomsWithLimitAndFilterStmt *sql.Stmt
|
||||||
|
selectRoomVisibilityStmt *sql.Stmt
|
||||||
|
insertNewRoomStmt *sql.Stmt
|
||||||
|
incrementJoinedMembersInRoomStmt *sql.Stmt
|
||||||
|
decrementJoinedMembersInRoomStmt *sql.Stmt
|
||||||
|
updateRoomAttributeStmts map[string]*sql.Stmt
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *publicRoomsStatements) prepare(db *sql.DB) (err error) {
|
||||||
|
_, err = db.Exec(publicRoomsSchema)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
stmts := statementList{
|
||||||
|
{&s.countPublicRoomsStmt, countPublicRoomsSQL},
|
||||||
|
{&s.selectPublicRoomsStmt, selectPublicRoomsSQL},
|
||||||
|
{&s.selectPublicRoomsWithLimitStmt, selectPublicRoomsWithLimitSQL},
|
||||||
|
{&s.selectPublicRoomsWithFilterStmt, selectPublicRoomsWithFilterSQL},
|
||||||
|
{&s.selectPublicRoomsWithLimitAndFilterStmt, selectPublicRoomsWithLimitAndFilterSQL},
|
||||||
|
{&s.selectRoomVisibilityStmt, selectRoomVisibilitySQL},
|
||||||
|
{&s.insertNewRoomStmt, insertNewRoomSQL},
|
||||||
|
{&s.incrementJoinedMembersInRoomStmt, incrementJoinedMembersInRoomSQL},
|
||||||
|
{&s.decrementJoinedMembersInRoomStmt, decrementJoinedMembersInRoomSQL},
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = stmts.prepare(db); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
s.updateRoomAttributeStmts = make(map[string]*sql.Stmt)
|
||||||
|
for _, editable := range editableAttributes {
|
||||||
|
stmt := fmt.Sprintf(updateRoomAttributeSQL, editable)
|
||||||
|
if s.updateRoomAttributeStmts[editable], err = db.Prepare(stmt); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *publicRoomsStatements) countPublicRooms() (nb int64, err error) {
|
||||||
|
err = s.countPublicRoomsStmt.QueryRow().Scan(&nb)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *publicRoomsStatements) selectPublicRooms(offset int64, limit int16, filter string) ([]types.PublicRoom, error) {
|
||||||
|
var rows *sql.Rows
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if len(filter) > 0 {
|
||||||
|
pattern := "%" + filter + "%"
|
||||||
|
if limit == 0 {
|
||||||
|
rows, err = s.selectPublicRoomsWithFilterStmt.Query(pattern, offset)
|
||||||
|
} else {
|
||||||
|
rows, err = s.selectPublicRoomsWithLimitAndFilterStmt.Query(pattern, offset, limit)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if limit == 0 {
|
||||||
|
rows, err = s.selectPublicRoomsStmt.Query(offset)
|
||||||
|
} else {
|
||||||
|
rows, err = s.selectPublicRoomsWithLimitStmt.Query(offset, limit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return []types.PublicRoom{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
rooms := []types.PublicRoom{}
|
||||||
|
for rows.Next() {
|
||||||
|
var r types.PublicRoom
|
||||||
|
var aliases pq.StringArray
|
||||||
|
|
||||||
|
err = rows.Scan(
|
||||||
|
&r.RoomID, &r.NumJoinedMembers, &aliases, &r.CanonicalAlias,
|
||||||
|
&r.Name, &r.Topic, &r.WorldReadable, &r.GuestCanJoin, &r.AvatarURL,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return rooms, err
|
||||||
|
}
|
||||||
|
|
||||||
|
r.Aliases = make([]string, len(aliases))
|
||||||
|
for i := range aliases {
|
||||||
|
r.Aliases[i] = aliases[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
rooms = append(rooms, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
return rooms, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *publicRoomsStatements) selectRoomVisibility(roomID string) (v bool, err error) {
|
||||||
|
err = s.selectRoomVisibilityStmt.QueryRow(roomID).Scan(&v)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *publicRoomsStatements) insertNewRoom(roomID string) error {
|
||||||
|
_, err := s.insertNewRoomStmt.Exec(roomID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *publicRoomsStatements) incrementJoinedMembersInRoom(roomID string) error {
|
||||||
|
_, err := s.incrementJoinedMembersInRoomStmt.Exec(roomID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *publicRoomsStatements) decrementJoinedMembersInRoom(roomID string) error {
|
||||||
|
_, err := s.decrementJoinedMembersInRoomStmt.Exec(roomID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *publicRoomsStatements) updateRoomAttribute(attrName string, attrValue attributeValue, roomID string) error {
|
||||||
|
isEditable := false
|
||||||
|
for _, editable := range editableAttributes {
|
||||||
|
if editable == attrName {
|
||||||
|
isEditable = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isEditable {
|
||||||
|
return errors.New("Cannot edit " + attrName)
|
||||||
|
}
|
||||||
|
|
||||||
|
var value interface{}
|
||||||
|
if attrName == "aliases" {
|
||||||
|
// Aliases need a special conversion
|
||||||
|
valueAsSlice, isSlice := attrValue.([]string)
|
||||||
|
if !isSlice {
|
||||||
|
// attrValue isn't a slice of strings
|
||||||
|
return errors.New("New list of aliases is of the wrong type")
|
||||||
|
}
|
||||||
|
value = pq.StringArray(valueAsSlice)
|
||||||
|
} else {
|
||||||
|
value = attrValue
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := s.updateRoomAttributeStmts[attrName].Exec(value, roomID)
|
||||||
|
return err
|
||||||
|
}
|
|
@ -0,0 +1,247 @@
|
||||||
|
// 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"
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/matrix-org/dendrite/common"
|
||||||
|
"github.com/matrix-org/dendrite/publicroomsapi/types"
|
||||||
|
|
||||||
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PublicRoomsServerDatabase represents a public rooms server database.
|
||||||
|
type PublicRoomsServerDatabase struct {
|
||||||
|
db *sql.DB
|
||||||
|
partitions common.PartitionOffsetStatements
|
||||||
|
statements publicRoomsStatements
|
||||||
|
}
|
||||||
|
|
||||||
|
type attributeValue interface{}
|
||||||
|
|
||||||
|
// NewPublicRoomsServerDatabase creates a new public rooms server database.
|
||||||
|
func NewPublicRoomsServerDatabase(dataSourceName string) (*PublicRoomsServerDatabase, error) {
|
||||||
|
var db *sql.DB
|
||||||
|
var err error
|
||||||
|
if db, err = sql.Open("postgres", dataSourceName); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
partitions := common.PartitionOffsetStatements{}
|
||||||
|
if err = partitions.Prepare(db, "publicroomsapi"); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
statements := publicRoomsStatements{}
|
||||||
|
if err = statements.prepare(db); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &PublicRoomsServerDatabase{db, partitions, statements}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// PartitionOffsets implements common.PartitionStorer
|
||||||
|
func (d *PublicRoomsServerDatabase) PartitionOffsets(topic string) ([]common.PartitionOffset, error) {
|
||||||
|
return d.partitions.SelectPartitionOffsets(topic)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPartitionOffset implements common.PartitionStorer
|
||||||
|
func (d *PublicRoomsServerDatabase) SetPartitionOffset(topic string, partition int32, offset int64) error {
|
||||||
|
return d.partitions.UpsertPartitionOffset(topic, partition, offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRoomVisibility returns the room visibility as a boolean: true if the room
|
||||||
|
// is publicly visible, false if not.
|
||||||
|
// Returns an error if the retrieval failed.
|
||||||
|
func (d *PublicRoomsServerDatabase) GetRoomVisibility(roomID string) (bool, error) {
|
||||||
|
return d.statements.selectRoomVisibility(roomID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetRoomVisibility updates the visibility attribute of a room. This attribute
|
||||||
|
// must be set to true if the room is publicly visible, false if not.
|
||||||
|
// Returns an error if the update failed.
|
||||||
|
func (d *PublicRoomsServerDatabase) SetRoomVisibility(visible bool, roomID string) error {
|
||||||
|
return d.statements.updateRoomAttribute("visibility", visible, roomID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CountPublicRooms returns the number of room set as publicly visible on the server.
|
||||||
|
// Returns an error if the retrieval failed.
|
||||||
|
func (d *PublicRoomsServerDatabase) CountPublicRooms() (int64, error) {
|
||||||
|
return d.statements.countPublicRooms()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPublicRooms returns an array containing the local rooms set as publicly visible, ordered by their number
|
||||||
|
// of joined members. This array can be limited by a given number of elements, and offset by a given value.
|
||||||
|
// If the limit is 0, doesn't limit the number of results. If the offset is 0 too, the array contains all
|
||||||
|
// the rooms set as publicly visible on the server.
|
||||||
|
// Returns an error if the retrieval failed.
|
||||||
|
func (d *PublicRoomsServerDatabase) GetPublicRooms(offset int64, limit int16, filter string) ([]types.PublicRoom, error) {
|
||||||
|
return d.statements.selectPublicRooms(offset, limit, filter)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateRoomFromEvents iterate over a slice of state events and call
|
||||||
|
// UpdateRoomFromEvent on each of them to update the database representation of
|
||||||
|
// the rooms updated by each event.
|
||||||
|
// The slice of events to remove is used to update the number of joined members
|
||||||
|
// for the room in the database.
|
||||||
|
// If the update triggered by one of the events failed, aborts the process and
|
||||||
|
// returns an error.
|
||||||
|
func (d *PublicRoomsServerDatabase) UpdateRoomFromEvents(
|
||||||
|
eventsToAdd []gomatrixserverlib.Event, eventsToRemove []gomatrixserverlib.Event,
|
||||||
|
) error {
|
||||||
|
for _, event := range eventsToAdd {
|
||||||
|
if err := d.UpdateRoomFromEvent(event); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, event := range eventsToRemove {
|
||||||
|
if event.Type() == "m.room.member" {
|
||||||
|
if err := d.updateNumJoinedUsers(event, true); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateRoomFromEvent updates the database representation of a room from a Matrix event, by
|
||||||
|
// checking the event's type to know which attribute to change and using the event's content
|
||||||
|
// to define the new value of the attribute.
|
||||||
|
// If the event doesn't match with any property used to compute the public room directory,
|
||||||
|
// does nothing.
|
||||||
|
// If something went wrong during the process, returns an error.
|
||||||
|
func (d *PublicRoomsServerDatabase) UpdateRoomFromEvent(event gomatrixserverlib.Event) error {
|
||||||
|
// Process the event according to its type
|
||||||
|
switch event.Type() {
|
||||||
|
case "m.room.create":
|
||||||
|
return d.statements.insertNewRoom(event.RoomID())
|
||||||
|
case "m.room.member":
|
||||||
|
return d.updateNumJoinedUsers(event, false)
|
||||||
|
case "m.room.aliases":
|
||||||
|
return d.updateRoomAliases(event)
|
||||||
|
case "m.room.canonical_alias":
|
||||||
|
var content common.CanonicalAliasContent
|
||||||
|
field := &(content.Alias)
|
||||||
|
attrName := "canonical_alias"
|
||||||
|
return d.updateStringAttribute(attrName, event, &content, field)
|
||||||
|
case "m.room.name":
|
||||||
|
var content common.NameContent
|
||||||
|
field := &(content.Name)
|
||||||
|
attrName := "name"
|
||||||
|
return d.updateStringAttribute(attrName, event, &content, field)
|
||||||
|
case "m.room.topic":
|
||||||
|
var content common.TopicContent
|
||||||
|
field := &(content.Topic)
|
||||||
|
attrName := "topic"
|
||||||
|
return d.updateStringAttribute(attrName, event, &content, field)
|
||||||
|
case "m.room.avatar":
|
||||||
|
var content common.AvatarContent
|
||||||
|
field := &(content.URL)
|
||||||
|
attrName := "avatar_url"
|
||||||
|
return d.updateStringAttribute(attrName, event, &content, field)
|
||||||
|
case "m.room.history_visibility":
|
||||||
|
var content common.HistoryVisibilityContent
|
||||||
|
field := &(content.HistoryVisibility)
|
||||||
|
attrName := "world_readable"
|
||||||
|
strForTrue := "world_readable"
|
||||||
|
return d.updateBooleanAttribute(attrName, event, &content, field, strForTrue)
|
||||||
|
case "m.room.guest_access":
|
||||||
|
var content common.GuestAccessContent
|
||||||
|
field := &(content.GuestAccess)
|
||||||
|
attrName := "guest_can_join"
|
||||||
|
strForTrue := "can_join"
|
||||||
|
return d.updateBooleanAttribute(attrName, event, &content, field, strForTrue)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the event type didn't match, return with no error
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateNumJoinedUsers updates the number of joined user in the database representation
|
||||||
|
// of a room using a given "m.room.member" Matrix event.
|
||||||
|
// If the membership property of the event isn't "join", ignores it and returs nil.
|
||||||
|
// If the remove parameter is set to false, increments the joined members counter in the
|
||||||
|
// database, if set to truem decrements it.
|
||||||
|
// Returns an error if the update failed.
|
||||||
|
func (d *PublicRoomsServerDatabase) updateNumJoinedUsers(
|
||||||
|
membershipEvent gomatrixserverlib.Event, remove bool,
|
||||||
|
) error {
|
||||||
|
membership, err := membershipEvent.Membership()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if membership != "join" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if remove {
|
||||||
|
return d.statements.decrementJoinedMembersInRoom(membershipEvent.RoomID())
|
||||||
|
}
|
||||||
|
return d.statements.incrementJoinedMembersInRoom(membershipEvent.RoomID())
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateStringAttribute updates a given string attribute in the database
|
||||||
|
// representation of a room using a given string data field from content of the
|
||||||
|
// Matrix event triggering the update.
|
||||||
|
// Returns an error if decoding the Matrix event's content or updating the attribute
|
||||||
|
// failed.
|
||||||
|
func (d *PublicRoomsServerDatabase) updateStringAttribute(
|
||||||
|
attrName string, event gomatrixserverlib.Event, content interface{},
|
||||||
|
field *string,
|
||||||
|
) error {
|
||||||
|
if err := json.Unmarshal(event.Content(), content); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return d.statements.updateRoomAttribute(attrName, *field, event.RoomID())
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateBooleanAttribute updates a given boolean attribute in the database
|
||||||
|
// representation of a room using a given string data field from content of the
|
||||||
|
// Matrix event triggering the update.
|
||||||
|
// The attribute is set to true if the field matches a given string, false if not.
|
||||||
|
// Returns an error if decoding the Matrix event's content or updating the attribute
|
||||||
|
// failed.
|
||||||
|
func (d *PublicRoomsServerDatabase) updateBooleanAttribute(
|
||||||
|
attrName string, event gomatrixserverlib.Event, content interface{},
|
||||||
|
field *string, strForTrue string,
|
||||||
|
) error {
|
||||||
|
if err := json.Unmarshal(event.Content(), content); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var attrValue bool
|
||||||
|
if *field == strForTrue {
|
||||||
|
attrValue = true
|
||||||
|
} else {
|
||||||
|
attrValue = false
|
||||||
|
}
|
||||||
|
|
||||||
|
return d.statements.updateRoomAttribute(attrName, attrValue, event.RoomID())
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateRoomAliases decodes the content of a "m.room.aliases" Matrix event and update the list of aliases of
|
||||||
|
// a given room with it.
|
||||||
|
// Returns an error if decoding the Matrix event or updating the list failed.
|
||||||
|
func (d *PublicRoomsServerDatabase) updateRoomAliases(aliasesEvent gomatrixserverlib.Event) error {
|
||||||
|
var content common.AliasesContent
|
||||||
|
if err := json.Unmarshal(aliasesEvent.Content(), &content); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return d.statements.updateRoomAttribute("aliases", content.Aliases, aliasesEvent.RoomID())
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
// 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 types
|
||||||
|
|
||||||
|
// PublicRoom represents a local public room
|
||||||
|
type PublicRoom struct {
|
||||||
|
RoomID string `json:"room_id"`
|
||||||
|
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"`
|
||||||
|
}
|
|
@ -27,7 +27,7 @@ CREATE TABLE IF NOT EXISTS roomserver_room_aliases (
|
||||||
room_id TEXT NOT NULL
|
room_id TEXT NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE UNIQUE INDEX IF NOT EXISTS roomserver_room_id_idx ON roomserver_room_aliases(room_id);
|
CREATE INDEX IF NOT EXISTS roomserver_room_id_idx ON roomserver_room_aliases(room_id);
|
||||||
`
|
`
|
||||||
|
|
||||||
const insertRoomAliasSQL = "" +
|
const insertRoomAliasSQL = "" +
|
||||||
|
|
Loading…
Reference in New Issue