|
|
package database
import ( "database/sql" "github.com/matrix-org/go-neb/types" "time" )
// A ServiceDB stores the configuration for the services
type ServiceDB struct { db *sql.DB }
// A single global instance of the service DB.
// XXX: I can't think of any way of doing this without one without creating
// cyclical dependencies somewhere -- Kegan
var globalServiceDB *ServiceDB
// SetServiceDB sets the global service DB instance.
func SetServiceDB(db *ServiceDB) { globalServiceDB = db }
// GetServiceDB gets the global service DB instance.
func GetServiceDB() *ServiceDB { return globalServiceDB }
// Open a SQL database to use as a ServiceDB. This will automatically create
// the necessary database tables if they aren't already present.
func Open(databaseType, databaseURL string) (serviceDB *ServiceDB, err error) { db, err := sql.Open(databaseType, databaseURL) if err != nil { return } if _, err = db.Exec(schemaSQL); err != nil { return } serviceDB = &ServiceDB{db: db} return }
// StoreMatrixClientConfig stores the Matrix client config for a bot service.
// If a config already exists then it will be updated, otherwise a new config
// will be inserted. The previous config is returned.
func (d *ServiceDB) StoreMatrixClientConfig(config types.ClientConfig) (oldConfig types.ClientConfig, err error) { err = runTransaction(d.db, func(txn *sql.Tx) error { oldConfig, err = selectMatrixClientConfigTxn(txn, config.UserID) now := time.Now() if err == nil { return updateMatrixClientConfigTxn(txn, now, config) } else if err == sql.ErrNoRows { return insertMatrixClientConfigTxn(txn, now, config) } else { return err } }) return }
// LoadMatrixClientConfigs loads all Matrix client configs from the database.
func (d *ServiceDB) LoadMatrixClientConfigs() (configs []types.ClientConfig, err error) { err = runTransaction(d.db, func(txn *sql.Tx) error { configs, err = selectMatrixClientConfigsTxn(txn) return err }) return }
// LoadMatrixClientConfig loads a Matrix client config from the database.
// Returns sql.ErrNoRows if the client isn't in the database.
func (d *ServiceDB) LoadMatrixClientConfig(userID string) (config types.ClientConfig, err error) { err = runTransaction(d.db, func(txn *sql.Tx) error { config, err = selectMatrixClientConfigTxn(txn, userID) return err }) return }
// UpdateNextBatch updates the next_batch token for the given user.
func (d *ServiceDB) UpdateNextBatch(userID, nextBatch string) (err error) { err = runTransaction(d.db, func(txn *sql.Tx) error { return updateNextBatchTxn(txn, userID, nextBatch) }) return }
// LoadNextBatch loads the next_batch token for the given user.
func (d *ServiceDB) LoadNextBatch(userID string) (nextBatch string, err error) { err = runTransaction(d.db, func(txn *sql.Tx) error { nextBatch, err = selectNextBatchTxn(txn, userID) return err }) return }
// LoadService loads a service from the database.
// Returns sql.ErrNoRows if the service isn't in the database.
func (d *ServiceDB) LoadService(serviceID string) (service types.Service, err error) { err = runTransaction(d.db, func(txn *sql.Tx) error { service, err = selectServiceTxn(txn, serviceID) return err }) return }
// DeleteService deletes the given service from the database.
func (d *ServiceDB) DeleteService(serviceID string) (err error) { err = runTransaction(d.db, func(txn *sql.Tx) error { return deleteServiceTxn(txn, serviceID) }) return }
// LoadServicesForUser loads all the bot services configured for a given user.
// Returns an empty list if there aren't any services configured.
func (d *ServiceDB) LoadServicesForUser(serviceUserID string) (services []types.Service, err error) { err = runTransaction(d.db, func(txn *sql.Tx) error { services, err = selectServicesForUserTxn(txn, serviceUserID) if err != nil { return err } return nil }) return }
// StoreService stores a service into the database either by inserting a new
// service or updating an existing service. Returns the old service if there
// was one.
func (d *ServiceDB) StoreService(service types.Service) (oldService types.Service, err error) { err = runTransaction(d.db, func(txn *sql.Tx) error { oldService, err = selectServiceTxn(txn, service.ServiceID()) if err == sql.ErrNoRows { return insertServiceTxn(txn, time.Now(), service) } else if err != nil { return err } else { return updateServiceTxn(txn, time.Now(), service) } }) return }
// LoadAuthRealm loads an AuthRealm from the database.
// Returns sql.ErrNoRows if the realm isn't in the database.
func (d *ServiceDB) LoadAuthRealm(realmID string) (realm types.AuthRealm, err error) { err = runTransaction(d.db, func(txn *sql.Tx) error { realm, err = selectRealmTxn(txn, realmID) return err }) return }
// LoadAuthRealmsByType loads all auth realms with the given type from the database.
// The realms are ordered based on their realm ID.
// Returns an empty list if there are no realms with that type.
func (d *ServiceDB) LoadAuthRealmsByType(realmType string) (realms []types.AuthRealm, err error) { err = runTransaction(d.db, func(txn *sql.Tx) error { realms, err = selectRealmsByTypeTxn(txn, realmType) return err }) return }
// StoreAuthRealm stores the given AuthRealm, clobbering based on the realm ID.
// This function updates the time added/updated values. The previous realm, if any, is
// returned.
func (d *ServiceDB) StoreAuthRealm(realm types.AuthRealm) (old types.AuthRealm, err error) { err = runTransaction(d.db, func(txn *sql.Tx) error { old, err = selectRealmTxn(txn, realm.ID()) if err == sql.ErrNoRows { return insertRealmTxn(txn, time.Now(), realm) } else if err != nil { return err } else { return updateRealmTxn(txn, time.Now(), realm) } }) return }
// StoreAuthSession stores the given AuthSession, clobbering based on the tuple of
// user ID and realm ID. This function updates the time added/updated values.
// The previous session, if any, is returned.
func (d *ServiceDB) StoreAuthSession(session types.AuthSession) (old types.AuthSession, err error) { err = runTransaction(d.db, func(txn *sql.Tx) error { old, err = selectAuthSessionByUserTxn(txn, session.RealmID(), session.UserID()) if err == sql.ErrNoRows { return insertAuthSessionTxn(txn, time.Now(), session) } else if err != nil { return err } else { return updateAuthSessionTxn(txn, time.Now(), session) } }) return }
// LoadAuthSessionByUser loads an AuthSession from the database based on the given
// realm and user ID.
// Returns sql.ErrNoRows if the session isn't in the database.
func (d *ServiceDB) LoadAuthSessionByUser(realmID, userID string) (session types.AuthSession, err error) { err = runTransaction(d.db, func(txn *sql.Tx) error { session, err = selectAuthSessionByUserTxn(txn, realmID, userID) return err }) return }
// LoadAuthSessionByID loads an AuthSession from the database based on the given
// realm and session ID.
// Returns sql.ErrNoRows if the session isn't in the database.
func (d *ServiceDB) LoadAuthSessionByID(realmID, sessionID string) (session types.AuthSession, err error) { err = runTransaction(d.db, func(txn *sql.Tx) error { session, err = selectAuthSessionByIDTxn(txn, realmID, sessionID) return err }) return }
// LoadBotOptions loads bot options from the database.
// Returns sql.ErrNoRows if the bot options isn't in the database.
func (d *ServiceDB) LoadBotOptions(userID, roomID string) (opts types.BotOptions, err error) { err = runTransaction(d.db, func(txn *sql.Tx) error { opts, err = selectBotOptionsTxn(txn, userID, roomID) return err }) return }
// StoreBotOptions stores a BotOptions into the database either by inserting a new
// bot options or updating an existing bot options. Returns the old bot options if there
// was one.
func (d *ServiceDB) StoreBotOptions(opts types.BotOptions) (oldOpts types.BotOptions, err error) { err = runTransaction(d.db, func(txn *sql.Tx) error { oldOpts, err = selectBotOptionsTxn(txn, opts.UserID, opts.RoomID) if err == sql.ErrNoRows { return insertBotOptionsTxn(txn, time.Now(), opts) } else if err != nil { return err } else { return updateBotOptionsTxn(txn, time.Now(), opts) } }) return }
func runTransaction(db *sql.DB, fn func(txn *sql.Tx) error) (err error) { txn, err := db.Begin() if err != nil { return } defer func() { if r := recover(); r != nil { txn.Rollback() panic(r) } else if err != nil { txn.Rollback() } else { err = txn.Commit() } }() err = fn(txn) return }
|