From e0f9c663e2608cba21d1b31e5a8f3a6ddb84ae93 Mon Sep 17 00:00:00 2001 From: Till Faelligen Date: Wed, 28 Jul 2021 16:32:32 +0200 Subject: [PATCH] Add presence storage Add internal API to set/retrieve the presence status --- userapi/api/api.go | 31 ++++++ userapi/internal/api.go | 41 +++++++ userapi/inthttp/client.go | 34 ++++-- userapi/inthttp/server.go | 13 +++ userapi/storage/presence/interface.go | 37 +++++++ .../presence/postgres/presence_table.go | 104 ++++++++++++++++++ userapi/storage/presence/postgres/storage.go | 58 ++++++++++ .../presence/sqlite3/presence_table.go | 103 +++++++++++++++++ userapi/storage/presence/sqlite3/storage.go | 72 ++++++++++++ userapi/storage/presence/storage.go | 36 ++++++ userapi/storage/presence/storage_wasm.go | 37 +++++++ userapi/types/presence.go | 40 +++++++ userapi/userapi.go | 6 + 13 files changed, 604 insertions(+), 8 deletions(-) create mode 100644 userapi/storage/presence/interface.go create mode 100644 userapi/storage/presence/postgres/presence_table.go create mode 100644 userapi/storage/presence/postgres/storage.go create mode 100644 userapi/storage/presence/sqlite3/presence_table.go create mode 100644 userapi/storage/presence/sqlite3/storage.go create mode 100644 userapi/storage/presence/storage.go create mode 100644 userapi/storage/presence/storage_wasm.go create mode 100644 userapi/types/presence.go diff --git a/userapi/api/api.go b/userapi/api/api.go index 407350123..1fb8b7b73 100644 --- a/userapi/api/api.go +++ b/userapi/api/api.go @@ -19,12 +19,14 @@ import ( "encoding/json" "github.com/matrix-org/dendrite/clientapi/auth/authtypes" + "github.com/matrix-org/dendrite/userapi/types" "github.com/matrix-org/gomatrixserverlib" ) // UserInternalAPI is the internal API for information about users and devices. type UserInternalAPI interface { InputAccountData(ctx context.Context, req *InputAccountDataRequest, res *InputAccountDataResponse) error + InputPresenceData(ctx context.Context, req *InputPresenceRequest, res *InputPresenceResponse) error PerformAccountCreation(ctx context.Context, req *PerformAccountCreationRequest, res *PerformAccountCreationResponse) error PerformPasswordUpdate(ctx context.Context, req *PerformPasswordUpdateRequest, res *PerformPasswordUpdateResponse) error PerformDeviceCreation(ctx context.Context, req *PerformDeviceCreationRequest, res *PerformDeviceCreationResponse) error @@ -40,6 +42,7 @@ type UserInternalAPI interface { QueryDeviceInfos(ctx context.Context, req *QueryDeviceInfosRequest, res *QueryDeviceInfosResponse) error QuerySearchProfiles(ctx context.Context, req *QuerySearchProfilesRequest, res *QuerySearchProfilesResponse) error QueryOpenIDToken(ctx context.Context, req *QueryOpenIDTokenRequest, res *QueryOpenIDTokenResponse) error + QueryPresenceForUser(ctx context.Context, req *QueryPresenceForUserRequest, res *QueryPresenceForUserResponse) error } // InputAccountDataRequest is the request for InputAccountData @@ -249,6 +252,34 @@ type QueryOpenIDTokenResponse struct { ExpiresAtMS int64 } +// InputPresenceRequest is the request for presence data +type InputPresenceRequest struct { + UserID string + DisplayName string + AvatarURL string + Presence types.PresenceStatus + StatusMsg string + LastActiveTS int64 +} + +// InputPresenceResponse is the response for InputPresenceRequest +type InputPresenceResponse struct{} + +// QueryPresenceForUserRequest is the request for QueryPresenceForUserRequest +type QueryPresenceForUserRequest struct { + UserID string +} + +// QueryPresenceForUserResponse is the response for QueryPresenceForUserRequest +type QueryPresenceForUserResponse struct { + PresenceStatus types.PresenceStatus `json:"-"` + Presence string `json:"presence"` + StatusMsg string `json:"status_msg,omitempty"` + LastActiveTS gomatrixserverlib.Timestamp `json:"-"` + LastActiveAgo int64 `json:"last_active_ago,omitempty"` + CurrentlyActive bool `json:"currently_active,omitempty"` +} + // Device represents a client's device (mobile, web, etc) type Device struct { ID string diff --git a/userapi/internal/api.go b/userapi/internal/api.go index 21933c1c4..57abda410 100644 --- a/userapi/internal/api.go +++ b/userapi/internal/api.go @@ -29,6 +29,7 @@ import ( "github.com/matrix-org/dendrite/userapi/api" "github.com/matrix-org/dendrite/userapi/storage/accounts" "github.com/matrix-org/dendrite/userapi/storage/devices" + "github.com/matrix-org/dendrite/userapi/storage/presence" "github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/util" "github.com/sirupsen/logrus" @@ -37,6 +38,7 @@ import ( type UserInternalAPI struct { AccountDB accounts.Database DeviceDB devices.Database + PresenceDB presence.Database ServerName gomatrixserverlib.ServerName // AppServices is the list of all registered AS AppServices []config.ApplicationService @@ -57,6 +59,11 @@ func (a *UserInternalAPI) InputAccountData(ctx context.Context, req *api.InputAc return a.AccountDB.SaveAccountData(ctx, local, req.RoomID, req.DataType, req.AccountData) } +func (a *UserInternalAPI) InputPresenceData(ctx context.Context, req *api.InputPresenceRequest, res *api.InputPresenceResponse) error { + _, err := a.PresenceDB.UpsertPresence(ctx, req.UserID, req.StatusMsg, req.Presence, req.LastActiveTS) + return err +} + func (a *UserInternalAPI) PerformAccountCreation(ctx context.Context, req *api.PerformAccountCreationRequest, res *api.PerformAccountCreationResponse) error { if req.AccountType == api.AccountTypeGuest { acc, err := a.AccountDB.CreateGuestAccount(ctx) @@ -442,3 +449,37 @@ func (a *UserInternalAPI) QueryOpenIDToken(ctx context.Context, req *api.QueryOp return nil } + +// QueryPresenceForUser gets the current presence status, if set. +func (a *UserInternalAPI) QueryPresenceForUser(ctx context.Context, req *api.QueryPresenceForUserRequest, res *api.QueryPresenceForUserResponse) error { + local, domain, err := gomatrixserverlib.SplitID('@', req.UserID) + if err != nil { + return err + } + var maxLastSeen int64 + // If it's a local user, we can check the devices for possible updated timestamps + if domain == a.ServerName { + devs, err := a.DeviceDB.GetDevicesByLocalpart(ctx, local) + if err != nil { + return err + } + for _, dev := range devs { + if dev.LastSeenTS > maxLastSeen { + maxLastSeen = dev.LastSeenTS + } + } + } + + p, err := a.PresenceDB.GetPresenceForUser(ctx, req.UserID) + if err != nil { + return err + } + + res.PresenceStatus = p.Presence + res.StatusMsg = p.StatusMsg + res.LastActiveTS = p.LastActiveTS + if maxLastSeen > p.LastActiveTS.Time().Unix() { + res.LastActiveAgo = maxLastSeen + } + return nil +} diff --git a/userapi/inthttp/client.go b/userapi/inthttp/client.go index 1cb5ef0a8..8d4acbbc3 100644 --- a/userapi/inthttp/client.go +++ b/userapi/inthttp/client.go @@ -26,7 +26,8 @@ import ( // HTTP paths for the internal HTTP APIs const ( - InputAccountDataPath = "/userapi/inputAccountData" + InputAccountDataPath = "/userapi/inputAccountData" + InputPresenceDataPath = "/userapi/inputPresenceData" PerformDeviceCreationPath = "/userapi/performDeviceCreation" PerformAccountCreationPath = "/userapi/performAccountCreation" @@ -37,13 +38,14 @@ const ( PerformAccountDeactivationPath = "/userapi/performAccountDeactivation" PerformOpenIDTokenCreationPath = "/userapi/performOpenIDTokenCreation" - QueryProfilePath = "/userapi/queryProfile" - QueryAccessTokenPath = "/userapi/queryAccessToken" - QueryDevicesPath = "/userapi/queryDevices" - QueryAccountDataPath = "/userapi/queryAccountData" - QueryDeviceInfosPath = "/userapi/queryDeviceInfos" - QuerySearchProfilesPath = "/userapi/querySearchProfiles" - QueryOpenIDTokenPath = "/userapi/queryOpenIDToken" + QueryProfilePath = "/userapi/queryProfile" + QueryAccessTokenPath = "/userapi/queryAccessToken" + QueryDevicesPath = "/userapi/queryDevices" + QueryAccountDataPath = "/userapi/queryAccountData" + QueryDeviceInfosPath = "/userapi/queryDeviceInfos" + QuerySearchProfilesPath = "/userapi/querySearchProfiles" + QueryOpenIDTokenPath = "/userapi/queryOpenIDToken" + QueryPresenceForUserPath = "/userapi/queryPresenceForUser" ) // NewUserAPIClient creates a UserInternalAPI implemented by talking to a HTTP POST API. @@ -74,6 +76,14 @@ func (h *httpUserInternalAPI) InputAccountData(ctx context.Context, req *api.Inp return httputil.PostJSON(ctx, span, h.httpClient, apiURL, req, res) } +func (h *httpUserInternalAPI) InputPresenceData(ctx context.Context, req *api.InputPresenceRequest, res *api.InputPresenceResponse) error { + span, ctx := opentracing.StartSpanFromContext(ctx, "InputPresenceData") + defer span.Finish() + + apiURL := h.apiURL + InputPresenceDataPath + return httputil.PostJSON(ctx, span, h.httpClient, apiURL, req, res) +} + func (h *httpUserInternalAPI) PerformAccountCreation( ctx context.Context, request *api.PerformAccountCreationRequest, @@ -225,3 +235,11 @@ func (h *httpUserInternalAPI) QueryOpenIDToken(ctx context.Context, req *api.Que apiURL := h.apiURL + QueryOpenIDTokenPath return httputil.PostJSON(ctx, span, h.httpClient, apiURL, req, res) } + +func (h *httpUserInternalAPI) QueryPresenceForUser(ctx context.Context, req *api.QueryPresenceForUserRequest, res *api.QueryPresenceForUserResponse) error { + span, ctx := opentracing.StartSpanFromContext(ctx, "QueryPresenceForUser") + defer span.Finish() + + apiURL := h.apiURL + QueryPresenceForUserPath + return httputil.PostJSON(ctx, span, h.httpClient, apiURL, req, res) +} diff --git a/userapi/inthttp/server.go b/userapi/inthttp/server.go index 1c1cfdcd1..51e3aad38 100644 --- a/userapi/inthttp/server.go +++ b/userapi/inthttp/server.go @@ -234,4 +234,17 @@ func AddRoutes(internalAPIMux *mux.Router, s api.UserInternalAPI) { return util.JSONResponse{Code: http.StatusOK, JSON: &response} }), ) + internalAPIMux.Handle(InputPresenceDataPath, + httputil.MakeInternalAPI("inputPresenceDataPath", func(req *http.Request) util.JSONResponse { + request := api.InputPresenceRequest{} + response := api.InputPresenceResponse{} + if err := json.NewDecoder(req.Body).Decode(&request); err != nil { + return util.MessageResponse(http.StatusBadRequest, err.Error()) + } + if err := s.InputPresenceData(req.Context(), &request, &response); err != nil { + return util.ErrorResponse(err) + } + return util.JSONResponse{Code: http.StatusOK, JSON: &response} + }), + ) } diff --git a/userapi/storage/presence/interface.go b/userapi/storage/presence/interface.go new file mode 100644 index 000000000..75a5d1ad1 --- /dev/null +++ b/userapi/storage/presence/interface.go @@ -0,0 +1,37 @@ +// Copyright 2021 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package presence + +import ( + "context" + + "github.com/matrix-org/dendrite/eduserver/api" + "github.com/matrix-org/dendrite/userapi/types" +) + +type Database interface { + // UpsertPresence creates/updates the presence status of a user. + UpsertPresence( + ctx context.Context, + userID, statusMsg string, + presence types.PresenceStatus, + lastActiveTS int64, + ) (pos int64, err error) + // GetPresenceForUser gets the presence status of a user. + GetPresenceForUser( + ctx context.Context, + userID string, + ) (presence api.OutputPresence, err error) +} diff --git a/userapi/storage/presence/postgres/presence_table.go b/userapi/storage/presence/postgres/presence_table.go new file mode 100644 index 000000000..d3218f48a --- /dev/null +++ b/userapi/storage/presence/postgres/presence_table.go @@ -0,0 +1,104 @@ +// Copyright 2021 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package postgres + +import ( + "context" + "database/sql" + + "github.com/matrix-org/dendrite/eduserver/api" + "github.com/matrix-org/dendrite/internal/sqlutil" + "github.com/matrix-org/dendrite/userapi/types" +) + +const presenceSchema = ` +CREATE SEQUENCE IF NOT EXISTS presence_presence_id; + +-- Stores data about presence +CREATE TABLE IF NOT EXISTS presence_presences ( + -- The ID + id BIGINT PRIMARY KEY DEFAULT nextval('presence_presence_id'), + -- The Matrix user ID + user_id TEXT NOT NULL, + -- The actual presence + presence INT NOT NULL, + -- The status message + status_msg TEXT, + -- The last time an action was received by this user + last_active_ts BIGINT NOT NULL, + CONSTRAINT presence_presences_unique UNIQUE (user_id) +); +CREATE INDEX IF NOT EXISTS presence_presences_user_id ON presence_presences(user_id); +` + +const upsertPresenceSQL = "" + + "INSERT INTO presence_presences" + + " (user_id, presence, status_msg, last_active_ts)" + + " VALUES ($1, $2, $3, $4)" + + " ON CONFLICT (user_id)" + + " DO UPDATE SET id = nextval('presence_presences_user_id')," + + " presence = $2, status_msg = $3, last_active_ts = $4" + + " RETURNING id" + +const selectPresenceForUserSQL = "" + + "SELECT presence, status_msg, last_active_ts" + + " FROM presence_presences" + + " WHERE user_id = $1 LIMIT 1" + +type presenceStatements struct { + db *sql.DB + upsertPresenceStmt *sql.Stmt + selectPresenceForUsersStmt *sql.Stmt +} + +func (p *presenceStatements) execSchema(db *sql.DB) error { + _, err := db.Exec(presenceSchema) + return err +} + +func (p *presenceStatements) prepare(db *sql.DB) (err error) { + if p.upsertPresenceStmt, err = db.Prepare(upsertPresenceSQL); err != nil { + return + } + if p.selectPresenceForUsersStmt, err = db.Prepare(selectPresenceForUserSQL); err != nil { + return + } + return +} + +// UpsertPresence creates/updates a presence status. +func (p *presenceStatements) UpsertPresence( + ctx context.Context, + txn *sql.Tx, userID, + statusMsg string, + presence types.PresenceStatus, + lastActiveTS int64, +) (pos int64, err error) { + stmt := sqlutil.TxStmt(txn, p.upsertPresenceStmt) + err = stmt.QueryRowContext(ctx, userID, presence, statusMsg, lastActiveTS).Scan(&pos) + return +} + +// GetPresenceForUser returns the current presence of a user. +func (p *presenceStatements) GetPresenceForUser( + ctx context.Context, txn *sql.Tx, + userID string, +) (presence api.OutputPresence, err error) { + presence.UserID = userID + stmt := sqlutil.TxStmt(txn, p.selectPresenceForUsersStmt) + + err = stmt.QueryRowContext(ctx, userID).Scan(&presence.Presence, &presence.StatusMsg, &presence.LastActiveTS) + return +} diff --git a/userapi/storage/presence/postgres/storage.go b/userapi/storage/presence/postgres/storage.go new file mode 100644 index 000000000..bb56d263c --- /dev/null +++ b/userapi/storage/presence/postgres/storage.go @@ -0,0 +1,58 @@ +package postgres + +import ( + "context" + "database/sql" + + "github.com/matrix-org/dendrite/eduserver/api" + "github.com/matrix-org/dendrite/internal/sqlutil" + "github.com/matrix-org/dendrite/setup/config" + "github.com/matrix-org/dendrite/userapi/types" +) + +// Database represents an account database +type Database struct { + db *sql.DB + writer sqlutil.Writer + sqlutil.PartitionOffsetStatements + presence presenceStatements +} + +func NewDatabase(dbProperties *config.DatabaseOptions) (*Database, error) { + db, err := sqlutil.Open(dbProperties) + if err != nil { + return nil, err + } + d := &Database{ + db: db, + writer: sqlutil.NewDummyWriter(), + } + + if err = d.presence.execSchema(db); err != nil { + return nil, err + } + if err = d.PartitionOffsetStatements.Prepare(db, d.writer, "presence"); err != nil { + return nil, err + } + if err = d.presence.prepare(db); err != nil { + return nil, err + } + return d, nil +} + +func (d *Database) UpsertPresence( + ctx context.Context, + userID, statusMsg string, + presence types.PresenceStatus, + lastActiveTS int64, +) (pos int64, err error) { + err = sqlutil.WithTransaction(d.db, func(txn *sql.Tx) error { + pos, err = d.presence.UpsertPresence(ctx, txn, userID, statusMsg, presence, lastActiveTS) + return err + }) + return +} + +func (d *Database) GetPresenceForUser(ctx context.Context, userID string) (api.OutputPresence, error) { + return d.presence.GetPresenceForUser(ctx, nil, userID) +} diff --git a/userapi/storage/presence/sqlite3/presence_table.go b/userapi/storage/presence/sqlite3/presence_table.go new file mode 100644 index 000000000..606b07bed --- /dev/null +++ b/userapi/storage/presence/sqlite3/presence_table.go @@ -0,0 +1,103 @@ +// Copyright 2021 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package sqlite3 + +import ( + "context" + "database/sql" + + "github.com/matrix-org/dendrite/eduserver/api" + "github.com/matrix-org/dendrite/internal/sqlutil" + "github.com/matrix-org/dendrite/userapi/types" +) + +const presenceSchema = ` +-- Stores data about presence +CREATE TABLE IF NOT EXISTS presence_presences ( + -- The ID + id INTEGER PRIMARY KEY AUTOINCREMENT, + -- The Matrix user ID + user_id TEXT NOT NULL, + -- The actual presence + presence INT NOT NULL, + -- The status message + status_msg TEXT, + -- The last time an action was received by this user + last_active_ts BIGINT NOT NULL, + CONSTRAINT presence_presences_unique UNIQUE (user_id) +); +CREATE INDEX IF NOT EXISTS presence_presences_id ON presence_presences(id); +CREATE INDEX IF NOT EXISTS presence_presences_user_id ON presence_presences(user_id); +` + +const upsertPresenceSQL = "" + + "INSERT INTO presence_presences" + + " (user_id, presence, status_msg, last_active_ts)" + + " VALUES ($1, $2, $3, $4)" + + " ON CONFLICT (user_id)" + + " DO UPDATE SET id = rowid+1," + + " presence = $5, status_msg = $6, last_active_ts = $7" + + " RETURNING id" + +const selectPresenceForUserSQL = "" + + "SELECT presence, status_msg, last_active_ts" + + " FROM presence_presences" + + " WHERE user_id = $1 LIMIT 1" + +type presenceStatements struct { + db *sql.DB + upsertPresenceStmt *sql.Stmt + selectPresenceForUsersStmt *sql.Stmt +} + +func (p *presenceStatements) execSchema(db *sql.DB) error { + _, err := db.Exec(presenceSchema) + return err +} + +func (p *presenceStatements) prepare(db *sql.DB) (err error) { + if p.upsertPresenceStmt, err = db.Prepare(upsertPresenceSQL); err != nil { + return + } + if p.selectPresenceForUsersStmt, err = db.Prepare(selectPresenceForUserSQL); err != nil { + return + } + return +} + +// UpsertPresence creates/updates a presence status. +func (p *presenceStatements) UpsertPresence( + ctx context.Context, + txn *sql.Tx, userID, + statusMsg string, + presence types.PresenceStatus, + lastActiveTS int64, +) (pos int64, err error) { + stmt := sqlutil.TxStmt(txn, p.upsertPresenceStmt) + err = stmt.QueryRowContext(ctx, userID, presence, statusMsg, lastActiveTS, presence, statusMsg, lastActiveTS).Scan(&pos) + return +} + +// GetPresenceForUser returns the current presence of a user. +func (p *presenceStatements) GetPresenceForUser( + ctx context.Context, txn *sql.Tx, + userID string, +) (presence api.OutputPresence, err error) { + presence.UserID = userID + stmt := sqlutil.TxStmt(txn, p.selectPresenceForUsersStmt) + + err = stmt.QueryRowContext(ctx, userID).Scan(&presence.Presence, &presence.StatusMsg, &presence.LastActiveTS) + return +} diff --git a/userapi/storage/presence/sqlite3/storage.go b/userapi/storage/presence/sqlite3/storage.go new file mode 100644 index 000000000..45bbded71 --- /dev/null +++ b/userapi/storage/presence/sqlite3/storage.go @@ -0,0 +1,72 @@ +// Copyright 2021 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package sqlite3 + +import ( + "context" + "database/sql" + + "github.com/matrix-org/dendrite/eduserver/api" + "github.com/matrix-org/dendrite/internal/sqlutil" + "github.com/matrix-org/dendrite/setup/config" + "github.com/matrix-org/dendrite/userapi/types" +) + +// Database represents an account database +type Database struct { + db *sql.DB + writer sqlutil.Writer + sqlutil.PartitionOffsetStatements + presence presenceStatements +} + +func NewDatabase(dbProperties *config.DatabaseOptions) (*Database, error) { + db, err := sqlutil.Open(dbProperties) + if err != nil { + return nil, err + } + d := &Database{ + db: db, + writer: sqlutil.NewExclusiveWriter(), + } + + if err = d.presence.execSchema(db); err != nil { + return nil, err + } + if err = d.PartitionOffsetStatements.Prepare(db, d.writer, "presence"); err != nil { + return nil, err + } + if err = d.presence.prepare(db); err != nil { + return nil, err + } + return d, nil +} + +func (d *Database) UpsertPresence( + ctx context.Context, + userID, statusMsg string, + presence types.PresenceStatus, + lastActiveTS int64, +) (pos int64, err error) { + err = sqlutil.WithTransaction(d.db, func(txn *sql.Tx) error { + pos, err = d.presence.UpsertPresence(ctx, txn, userID, statusMsg, presence, lastActiveTS) + return err + }) + return +} + +func (d *Database) GetPresenceForUser(ctx context.Context, userID string) (api.OutputPresence, error) { + return d.presence.GetPresenceForUser(ctx, nil, userID) +} diff --git a/userapi/storage/presence/storage.go b/userapi/storage/presence/storage.go new file mode 100644 index 000000000..dc869f0a4 --- /dev/null +++ b/userapi/storage/presence/storage.go @@ -0,0 +1,36 @@ +// Copyright 2021 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package presence + +import ( + "fmt" + + "github.com/matrix-org/dendrite/setup/config" + "github.com/matrix-org/dendrite/userapi/storage/presence/postgres" + "github.com/matrix-org/dendrite/userapi/storage/presence/sqlite3" +) + +// NewDatabase opens a new Postgres or Sqlite database (based on dataSourceName scheme) +// and sets postgres connection parameters +func NewDatabase(dbProperties *config.DatabaseOptions) (Database, error) { + switch { + case dbProperties.ConnectionString.IsSQLite(): + return sqlite3.NewDatabase(dbProperties) + case dbProperties.ConnectionString.IsPostgres(): + return postgres.NewDatabase(dbProperties) + default: + return nil, fmt.Errorf("unexpected database type") + } +} diff --git a/userapi/storage/presence/storage_wasm.go b/userapi/storage/presence/storage_wasm.go new file mode 100644 index 000000000..0763b5217 --- /dev/null +++ b/userapi/storage/presence/storage_wasm.go @@ -0,0 +1,37 @@ +// Copyright 2021 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package devices + +import ( + "fmt" + + "github.com/matrix-org/dendrite/setup/config" + "github.com/matrix-org/dendrite/userapi/storage/presence/sqlite3" + "github.com/matrix-org/gomatrixserverlib" +) + +func NewDatabase( + dbProperties *config.DatabaseOptions, + serverName gomatrixserverlib.ServerName, +) (Database, error) { + switch { + case dbProperties.ConnectionString.IsSQLite(): + return sqlite3.NewDatabase(dbProperties) + case dbProperties.ConnectionString.IsPostgres(): + return nil, fmt.Errorf("can't use Postgres implementation") + default: + return nil, fmt.Errorf("unexpected database type") + } +} diff --git a/userapi/types/presence.go b/userapi/types/presence.go new file mode 100644 index 000000000..6dd0fb64b --- /dev/null +++ b/userapi/types/presence.go @@ -0,0 +1,40 @@ +// Copyright 2021 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package types + +import "strings" + +type PresenceStatus int + +//go:generate stringer -type=PresenceStatus -output=presence_string.go -linecomment +const ( + Unknown PresenceStatus = iota - 1 // unknown + Online // online + Offline // offline + Unavailable // unavailable +) + +// ToPresenceStatus tries to convert the given string to a PresenceStatus +func ToPresenceStatus(v string) PresenceStatus { + switch strings.ToLower(v) { + case "online": + return Online + case "offline": + return Offline + case "unavailable": + return Unavailable + } + return Unknown +} diff --git a/userapi/userapi.go b/userapi/userapi.go index 74702020a..4fe9e727f 100644 --- a/userapi/userapi.go +++ b/userapi/userapi.go @@ -23,6 +23,7 @@ import ( "github.com/matrix-org/dendrite/userapi/inthttp" "github.com/matrix-org/dendrite/userapi/storage/accounts" "github.com/matrix-org/dendrite/userapi/storage/devices" + "github.com/matrix-org/dendrite/userapi/storage/presence" "github.com/sirupsen/logrus" ) @@ -41,10 +42,15 @@ func NewInternalAPI( if err != nil { logrus.WithError(err).Panicf("failed to connect to device db") } + presenceDB, err := presence.NewDatabase(&cfg.PresenceDatabase) + if err != nil { + logrus.WithError(err).WithField("connString", cfg.PresenceDatabase.ConnectionString).Panicf("failed to connect to presence db") + } return &internal.UserInternalAPI{ AccountDB: accountDB, DeviceDB: deviceDB, + PresenceDB: presenceDB, ServerName: cfg.Matrix.ServerName, AppServices: appServices, KeyAPI: keyAPI,