2021-04-23 18:28:38 +00:00
// Copyright 2021 Dan Peleg <dan@globekeeper.com>
//
// 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/clientapi/userutil"
"github.com/matrix-org/dendrite/internal"
"github.com/matrix-org/dendrite/internal/sqlutil"
"github.com/matrix-org/dendrite/userapi/api"
"github.com/matrix-org/gomatrixserverlib"
2021-05-03 18:33:38 +00:00
"github.com/sirupsen/logrus"
2021-04-23 18:28:38 +00:00
)
const pushersSchema = `
-- Stores data about pushers .
CREATE TABLE IF NOT EXISTS pusher_pushers (
2021-05-03 18:33:38 +00:00
id SERIAL PRIMARY KEY ,
2021-04-23 18:28:38 +00:00
-- The Matrix user ID localpart for this pusher
2021-05-03 18:33:38 +00:00
localpart TEXT NOT NULL ,
-- The Session ID used to create the Pusher
session_id BIGINT DEFAULT NULL ,
-- This string determines which set of device specific rules this pusher executes .
profile_tag TEXT NOT NULL ,
2021-04-23 21:02:00 +00:00
-- The kind of pusher . "http" is a pusher that sends HTTP pokes .
2021-04-23 18:28:38 +00:00
kind TEXT ,
2021-04-23 21:02:00 +00:00
-- This is a reverse - DNS style identifier for the application . Max length , 64 chars .
app_id VARCHAR ( 64 ) ,
-- A string that will allow the user to identify what application owns this pusher .
2021-04-23 18:28:38 +00:00
app_display_name TEXT ,
2021-04-23 21:02:00 +00:00
-- A string that will allow the user to identify what device owns this pusher .
2021-04-23 18:28:38 +00:00
device_display_name TEXT ,
2021-05-03 18:33:38 +00:00
-- This is a unique identifier for this pusher .
-- The value you should use for this is the routing or destination address information for the notification , for example ,
-- the APNS token for APNS or the Registration ID for GCM . If your notification client has no such concept , use any unique identifier .
-- If the kind is "email" , this is the email address to send notifications to .
-- Max length , 512 bytes .
pushkey VARCHAR ( 512 ) NOT NULL ,
2021-04-23 21:02:00 +00:00
-- The preferred language for receiving notifications ( e . g . ' en ' or ' en - US ' )
lang TEXT ,
2021-05-05 07:03:01 +00:00
-- A dictionary of information for the pusher implementation itself .
data TEXT
2021-04-23 18:28:38 +00:00
) ;
2021-04-24 13:12:06 +00:00
-- Pushkey must be unique for a given user .
2021-05-03 18:33:38 +00:00
CREATE UNIQUE INDEX IF NOT EXISTS pusher_app_id_pushkey_localpart_idx ON pusher_pushers ( app_id , pushkey , localpart ) ;
2021-04-23 18:28:38 +00:00
`
2021-05-02 14:11:53 +00:00
const insertPusherSQL = "" +
2021-05-05 07:03:01 +00:00
"INSERT INTO pusher_pushers(localpart, session_id, pushkey, kind, app_id, app_display_name, device_display_name, profile_tag, lang, data) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)"
2021-05-02 14:11:53 +00:00
2021-04-23 18:28:38 +00:00
const selectPushersByLocalpartSQL = "" +
2021-05-05 07:03:01 +00:00
"SELECT session_id, pushkey, kind, app_id, app_display_name, device_display_name, profile_tag, lang, data FROM pusher_pushers WHERE localpart = $1"
2021-04-23 18:28:38 +00:00
2021-04-25 14:42:36 +00:00
const selectPusherByPushkeySQL = "" +
2021-05-05 07:03:01 +00:00
"SELECT session_id, pushkey, kind, app_id, app_display_name, device_display_name, profile_tag, lang, data FROM pusher_pushers WHERE localpart = $1 AND pushkey = $2"
2021-04-25 14:42:36 +00:00
2021-05-03 18:33:38 +00:00
const updatePusherSQL = "" +
2021-05-05 07:03:01 +00:00
"UPDATE pusher_pushers SET kind = $1, app_id = $2, app_display_name = $3, device_display_name = $4, profile_tag = $5, lang = $6, data = $7 WHERE localpart = $8 AND pushkey = $9"
2021-05-02 14:11:53 +00:00
2021-05-02 14:10:40 +00:00
const deletePusherSQL = "" +
2021-05-03 18:33:38 +00:00
"DELETE FROM pusher_pushers WHERE app_id = $1 AND pushkey = $2 AND localpart = $3"
2021-05-02 14:11:53 +00:00
2021-04-23 18:28:38 +00:00
type pushersStatements struct {
2021-05-02 14:11:53 +00:00
insertPusherStmt * sql . Stmt
2021-04-23 18:28:38 +00:00
selectPushersByLocalpartStmt * sql . Stmt
2021-04-25 14:42:36 +00:00
selectPusherByPushkeyStmt * sql . Stmt
2021-05-03 18:33:38 +00:00
updatePusherStmt * sql . Stmt
2021-05-02 14:10:40 +00:00
deletePusherStmt * sql . Stmt
2021-04-23 18:28:38 +00:00
serverName gomatrixserverlib . ServerName
}
func ( s * pushersStatements ) execSchema ( db * sql . DB ) error {
_ , err := db . Exec ( pushersSchema )
return err
}
func ( s * pushersStatements ) prepare ( db * sql . DB , server gomatrixserverlib . ServerName ) ( err error ) {
2021-05-02 14:11:53 +00:00
if s . insertPusherStmt , err = db . Prepare ( insertPusherSQL ) ; err != nil {
return
}
2021-04-23 18:28:38 +00:00
if s . selectPushersByLocalpartStmt , err = db . Prepare ( selectPushersByLocalpartSQL ) ; err != nil {
return
}
2021-04-25 14:42:36 +00:00
if s . selectPusherByPushkeyStmt , err = db . Prepare ( selectPusherByPushkeySQL ) ; err != nil {
2021-04-23 21:02:00 +00:00
return
}
2021-05-03 18:33:38 +00:00
if s . updatePusherStmt , err = db . Prepare ( updatePusherSQL ) ; err != nil {
return
}
2021-05-02 14:10:40 +00:00
if s . deletePusherStmt , err = db . Prepare ( deletePusherSQL ) ; err != nil {
return
}
2021-04-23 18:28:38 +00:00
s . serverName = server
return
}
2021-05-02 14:11:53 +00:00
// insertPusher creates a new pusher.
// Returns an error if the user already has a pusher with the given pusher pushkey.
// Returns nil error success.
func ( s * pushersStatements ) insertPusher (
2021-05-03 18:33:38 +00:00
ctx context . Context , txn * sql . Tx , session_id int64 ,
2021-05-05 07:03:01 +00:00
pushkey , kind , appid , appdisplayname , devicedisplayname , profiletag , lang , data , localpart string ,
2021-05-02 14:11:53 +00:00
) error {
stmt := sqlutil . TxStmt ( txn , s . insertPusherStmt )
2021-05-05 07:03:01 +00:00
_ , err := stmt . ExecContext ( ctx , localpart , session_id , pushkey , kind , appid , appdisplayname , devicedisplayname , profiletag , lang , data )
2021-05-03 18:33:38 +00:00
logrus . Debugf ( "🥳 Created pusher %d" , session_id )
2021-05-02 14:10:40 +00:00
return err
}
2021-04-23 18:28:38 +00:00
func ( s * pushersStatements ) selectPushersByLocalpart (
2021-04-23 21:02:00 +00:00
ctx context . Context , txn * sql . Tx , localpart string ,
2021-04-23 18:28:38 +00:00
) ( [ ] api . Pusher , error ) {
pushers := [ ] api . Pusher { }
2021-04-23 21:02:00 +00:00
rows , err := sqlutil . TxStmt ( txn , s . selectPushersByLocalpartStmt ) . QueryContext ( ctx , localpart )
2021-04-23 18:28:38 +00:00
if err != nil {
return pushers , err
}
defer internal . CloseAndLogIfError ( ctx , rows , "selectPushersByLocalpart: rows.close() failed" )
for rows . Next ( ) {
var pusher api . Pusher
2021-05-03 18:33:38 +00:00
var sessionid sql . NullInt64
2021-05-05 07:03:01 +00:00
var pushkey , kind , appid , appdisplayname , devicedisplayname , profiletag , lang , data sql . NullString
err = rows . Scan ( & sessionid , & pushkey , & kind , & appid , & appdisplayname , & devicedisplayname , & profiletag , & lang , & data )
2021-04-23 18:28:38 +00:00
if err != nil {
return pushers , err
}
2021-05-03 18:33:38 +00:00
if sessionid . Valid {
pusher . SessionID = sessionid . Int64
}
2021-04-23 18:28:38 +00:00
if pushkey . Valid {
pusher . PushKey = pushkey . String
}
if kind . Valid {
pusher . Kind = kind . String
}
if appid . Valid {
pusher . AppID = appid . String
}
if appdisplayname . Valid {
pusher . AppDisplayName = appdisplayname . String
}
if devicedisplayname . Valid {
pusher . DeviceDisplayName = devicedisplayname . String
}
if profiletag . Valid {
pusher . ProfileTag = profiletag . String
}
2021-04-24 13:12:06 +00:00
if lang . Valid {
pusher . Language = lang . String
2021-04-23 18:28:38 +00:00
}
2021-05-05 07:03:01 +00:00
if data . Valid {
pusher . Data = data . String
2021-04-23 18:28:38 +00:00
}
pusher . UserID = userutil . MakeUserID ( localpart , s . serverName )
pushers = append ( pushers , pusher )
}
2021-05-03 18:33:38 +00:00
logrus . Debugf ( "🤓 Database returned %d pushers" , len ( pushers ) )
2021-04-23 18:28:38 +00:00
return pushers , rows . Err ( )
}
2021-04-25 14:42:36 +00:00
func ( s * pushersStatements ) selectPusherByPushkey (
ctx context . Context , localpart , pushkey string ,
) ( * api . Pusher , error ) {
var pusher api . Pusher
2021-05-03 18:33:38 +00:00
var sessionid sql . NullInt64
2021-05-05 07:03:01 +00:00
var key , kind , appid , appdisplayname , devicedisplayname , profiletag , lang , data sql . NullString
2021-04-25 14:42:36 +00:00
stmt := s . selectPusherByPushkeyStmt
2021-05-05 07:03:01 +00:00
err := stmt . QueryRowContext ( ctx , localpart , pushkey ) . Scan ( & sessionid , & key , & kind , & appid , & appdisplayname , & devicedisplayname , & profiletag , & lang , & data )
2021-04-25 14:42:36 +00:00
if err == nil {
2021-05-03 18:33:38 +00:00
if sessionid . Valid {
pusher . SessionID = sessionid . Int64
}
2021-04-25 14:42:36 +00:00
if key . Valid {
pusher . PushKey = key . String
}
if kind . Valid {
pusher . Kind = kind . String
}
if appid . Valid {
pusher . AppID = appid . String
}
if appdisplayname . Valid {
pusher . AppDisplayName = appdisplayname . String
}
if devicedisplayname . Valid {
pusher . DeviceDisplayName = devicedisplayname . String
}
if profiletag . Valid {
pusher . ProfileTag = profiletag . String
}
if lang . Valid {
pusher . Language = lang . String
}
2021-05-05 07:03:01 +00:00
if data . Valid {
pusher . Data = data . String
2021-04-25 14:42:36 +00:00
}
pusher . UserID = userutil . MakeUserID ( localpart , s . serverName )
}
return & pusher , err
}
2021-05-03 18:33:38 +00:00
func ( s * pushersStatements ) updatePusher (
2021-05-05 07:03:01 +00:00
ctx context . Context , txn * sql . Tx , pushkey , kind , appid , appdisplayname , devicedisplayname , profiletag , lang , data , localpart string ,
2021-05-03 18:33:38 +00:00
) error {
stmt := sqlutil . TxStmt ( txn , s . updatePusherStmt )
2021-05-05 07:03:01 +00:00
_ , err := stmt . ExecContext ( ctx , kind , appid , appdisplayname , devicedisplayname , profiletag , lang , data , localpart , pushkey )
2021-05-03 18:33:38 +00:00
return err
}
// deletePusher removes a single pusher by pushkey and user localpart.
func ( s * pushersStatements ) deletePusher (
ctx context . Context , txn * sql . Tx , appid , pushkey , localpart string ,
) error {
stmt := sqlutil . TxStmt ( txn , s . deletePusherStmt )
_ , err := stmt . ExecContext ( ctx , appid , pushkey , localpart )
return err
}