move server/* to scnserver/*
This commit is contained in:
239
scnserver/db/channels.go
Normal file
239
scnserver/db/channels.go
Normal file
@@ -0,0 +1,239 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"blackforestbytes.com/simplecloudnotifier/models"
|
||||
"database/sql"
|
||||
"gogs.mikescher.com/BlackForestBytes/goext/sq"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (db *Database) GetChannelByName(ctx TxContext, userid models.UserID, chanName string) (*models.Channel, error) {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rows, err := tx.Query(ctx, "SELECT * FROM channels WHERE owner_user_id = :uid AND name = :nam LIMIT 1", sq.PP{
|
||||
"uid": userid,
|
||||
"nam": chanName,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
channel, err := models.DecodeChannel(rows)
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &channel, nil
|
||||
}
|
||||
|
||||
func (db *Database) GetChannelByNameAndSendKey(ctx TxContext, chanName string, sendKey string) (*models.Channel, error) {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rows, err := tx.Query(ctx, "SELECT * FROM channels WHERE name = :chan_name OR send_key = :send_key LIMIT 1", sq.PP{
|
||||
"chan_name": chanName,
|
||||
"send_key": sendKey,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
channel, err := models.DecodeChannel(rows)
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &channel, nil
|
||||
}
|
||||
|
||||
func (db *Database) CreateChannel(ctx TxContext, userid models.UserID, name string, subscribeKey string, sendKey string) (models.Channel, error) {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return models.Channel{}, err
|
||||
}
|
||||
|
||||
now := time.Now().UTC()
|
||||
|
||||
res, err := tx.Exec(ctx, "INSERT INTO channels (owner_user_id, name, subscribe_key, send_key, timestamp_created) VALUES (:ouid, :nam, :subkey, :sendkey, :ts)", sq.PP{
|
||||
"ouid": userid,
|
||||
"nam": name,
|
||||
"subkey": subscribeKey,
|
||||
"sendkey": sendKey,
|
||||
"ts": time2DB(now),
|
||||
})
|
||||
if err != nil {
|
||||
return models.Channel{}, err
|
||||
}
|
||||
|
||||
liid, err := res.LastInsertId()
|
||||
if err != nil {
|
||||
return models.Channel{}, err
|
||||
}
|
||||
|
||||
return models.Channel{
|
||||
ChannelID: models.ChannelID(liid),
|
||||
OwnerUserID: userid,
|
||||
Name: name,
|
||||
SubscribeKey: subscribeKey,
|
||||
SendKey: sendKey,
|
||||
TimestampCreated: now,
|
||||
TimestampLastSent: nil,
|
||||
MessagesSent: 0,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (db *Database) ListChannelsByOwner(ctx TxContext, userid models.UserID) ([]models.Channel, error) {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rows, err := tx.Query(ctx, "SELECT * FROM channels WHERE owner_user_id = :ouid", sq.PP{"ouid": userid})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data, err := models.DecodeChannels(rows)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (db *Database) ListChannelsBySubscriber(ctx TxContext, userid models.UserID, confirmed bool) ([]models.Channel, error) {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
confCond := ""
|
||||
if confirmed {
|
||||
confCond = " AND sub.confirmed = 1"
|
||||
}
|
||||
|
||||
rows, err := tx.Query(ctx, "SELECT * FROM channels LEFT JOIN subscriptions sub on channels.channel_id = sub.channel_id WHERE sub.subscriber_user_id = :suid "+confCond, sq.PP{
|
||||
"suid": userid,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data, err := models.DecodeChannels(rows)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (db *Database) ListChannelsByAccess(ctx TxContext, userid models.UserID, confirmed bool) ([]models.Channel, error) {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
confCond := "OR sub.subscriber_user_id = ?"
|
||||
if confirmed {
|
||||
confCond = "OR (sub.subscriber_user_id = ? AND sub.confirmed = 1)"
|
||||
}
|
||||
|
||||
rows, err := tx.Query(ctx, "SELECT * FROM channels LEFT JOIN subscriptions sub on channels.channel_id = sub.channel_id WHERE owner_user_id = :ouid "+confCond, sq.PP{
|
||||
"ouid": userid,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data, err := models.DecodeChannels(rows)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (db *Database) GetChannel(ctx TxContext, userid models.UserID, channelid models.ChannelID) (models.Channel, error) {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return models.Channel{}, err
|
||||
}
|
||||
|
||||
rows, err := tx.Query(ctx, "SELECT * FROM channels WHERE owner_user_id = :ouid AND channel_id = :cid LIMIT 1", sq.PP{
|
||||
"ouid": userid,
|
||||
"cid": channelid,
|
||||
})
|
||||
if err != nil {
|
||||
return models.Channel{}, err
|
||||
}
|
||||
|
||||
client, err := models.DecodeChannel(rows)
|
||||
if err != nil {
|
||||
return models.Channel{}, err
|
||||
}
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func (db *Database) IncChannelMessageCounter(ctx TxContext, channel models.Channel) error {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = tx.Exec(ctx, "UPDATE channels SET messages_sent = :ctr, timestamp_lastsent = :ts WHERE channel_id = :cid", sq.PP{
|
||||
"ctr": channel.MessagesSent + 1,
|
||||
"cid": time2DB(time.Now()),
|
||||
"ts": channel.ChannelID,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *Database) UpdateChannelSendKey(ctx TxContext, channelid models.ChannelID, newkey string) error {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = tx.Exec(ctx, "UPDATE channels SET send_key = :key WHERE channel_id = :cid", sq.PP{
|
||||
"key": newkey,
|
||||
"cid": channelid,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *Database) UpdateChannelSubscribeKey(ctx TxContext, channelid models.ChannelID, newkey string) error {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = tx.Exec(ctx, "UPDATE channels SET subscribe_key = :key WHERE channel_id = :cid", sq.PP{
|
||||
"key": newkey,
|
||||
"cid": channelid,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
127
scnserver/db/clients.go
Normal file
127
scnserver/db/clients.go
Normal file
@@ -0,0 +1,127 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"blackforestbytes.com/simplecloudnotifier/models"
|
||||
"gogs.mikescher.com/BlackForestBytes/goext/langext"
|
||||
"gogs.mikescher.com/BlackForestBytes/goext/sq"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (db *Database) CreateClient(ctx TxContext, userid models.UserID, ctype models.ClientType, fcmToken string, agentModel string, agentVersion string) (models.Client, error) {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return models.Client{}, err
|
||||
}
|
||||
|
||||
now := time.Now().UTC()
|
||||
|
||||
res, err := tx.Exec(ctx, "INSERT INTO clients (user_id, type, fcm_token, timestamp_created, agent_model, agent_version) VALUES (:uid, :typ, :fcm, :ts, :am, :av)", sq.PP{
|
||||
"uid": userid,
|
||||
"typ": string(ctype),
|
||||
"fcm": fcmToken,
|
||||
"ts": time2DB(now),
|
||||
"am": agentModel,
|
||||
"av": agentVersion,
|
||||
})
|
||||
if err != nil {
|
||||
return models.Client{}, err
|
||||
}
|
||||
|
||||
liid, err := res.LastInsertId()
|
||||
if err != nil {
|
||||
return models.Client{}, err
|
||||
}
|
||||
|
||||
return models.Client{
|
||||
ClientID: models.ClientID(liid),
|
||||
UserID: userid,
|
||||
Type: ctype,
|
||||
FCMToken: langext.Ptr(fcmToken),
|
||||
TimestampCreated: now,
|
||||
AgentModel: agentModel,
|
||||
AgentVersion: agentVersion,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (db *Database) ClearFCMTokens(ctx TxContext, fcmtoken string) error {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = tx.Exec(ctx, "DELETE FROM clients WHERE fcm_token = :fcm", sq.PP{"fcm": fcmtoken})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *Database) ListClients(ctx TxContext, userid models.UserID) ([]models.Client, error) {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rows, err := tx.Query(ctx, "SELECT * FROM clients WHERE user_id = :uid", sq.PP{"uid": userid})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data, err := models.DecodeClients(rows)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (db *Database) GetClient(ctx TxContext, userid models.UserID, clientid models.ClientID) (models.Client, error) {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return models.Client{}, err
|
||||
}
|
||||
|
||||
rows, err := tx.Query(ctx, "SELECT * FROM clients WHERE user_id = :uid AND client_id = :cid LIMIT 1", sq.PP{
|
||||
"uid": userid,
|
||||
"cid": clientid,
|
||||
})
|
||||
if err != nil {
|
||||
return models.Client{}, err
|
||||
}
|
||||
|
||||
client, err := models.DecodeClient(rows)
|
||||
if err != nil {
|
||||
return models.Client{}, err
|
||||
}
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func (db *Database) DeleteClient(ctx TxContext, clientid models.ClientID) error {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = tx.Exec(ctx, "DELETE FROM clients WHERE client_id = :cid", sq.PP{"cid": clientid})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *Database) DeleteClientsByFCM(ctx TxContext, fcmtoken string) error {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = tx.Exec(ctx, "DELETE FROM clients WHERE fcm_token = :fcm", sq.PP{"fcm": fcmtoken})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
15
scnserver/db/context.go
Normal file
15
scnserver/db/context.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"gogs.mikescher.com/BlackForestBytes/goext/sq"
|
||||
"time"
|
||||
)
|
||||
|
||||
type TxContext interface {
|
||||
Deadline() (deadline time.Time, ok bool)
|
||||
Done() <-chan struct{}
|
||||
Err() error
|
||||
Value(key any) any
|
||||
|
||||
GetOrCreateTransaction(db *Database) (sq.Tx, error)
|
||||
}
|
145
scnserver/db/cursortoken/token.go
Normal file
145
scnserver/db/cursortoken/token.go
Normal file
@@ -0,0 +1,145 @@
|
||||
package cursortoken
|
||||
|
||||
import (
|
||||
"encoding/base32"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Mode string
|
||||
|
||||
const (
|
||||
CTMStart = "START"
|
||||
CTMNormal = "NORMAL"
|
||||
CTMEnd = "END"
|
||||
)
|
||||
|
||||
type CursorToken struct {
|
||||
Mode Mode
|
||||
Timestamp int64
|
||||
Id int64
|
||||
Direction string
|
||||
FilterHash string
|
||||
}
|
||||
|
||||
type cursorTokenSerialize struct {
|
||||
Timestamp *int64 `json:"ts,omitempty"`
|
||||
Id *int64 `json:"id,omitempty"`
|
||||
Direction *string `json:"dir,omitempty"`
|
||||
FilterHash *string `json:"f,omitempty"`
|
||||
}
|
||||
|
||||
func Start() CursorToken {
|
||||
return CursorToken{
|
||||
Mode: CTMStart,
|
||||
Timestamp: 0,
|
||||
Id: 0,
|
||||
Direction: "",
|
||||
FilterHash: "",
|
||||
}
|
||||
}
|
||||
|
||||
func End() CursorToken {
|
||||
return CursorToken{
|
||||
Mode: CTMEnd,
|
||||
Timestamp: 0,
|
||||
Id: 0,
|
||||
Direction: "",
|
||||
FilterHash: "",
|
||||
}
|
||||
}
|
||||
|
||||
func Normal(ts time.Time, id int64, dir string, filter string) CursorToken {
|
||||
return CursorToken{
|
||||
Mode: CTMNormal,
|
||||
Timestamp: ts.UnixMilli(),
|
||||
Id: id,
|
||||
Direction: dir,
|
||||
FilterHash: filter,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CursorToken) Token() string {
|
||||
if c.Mode == CTMStart {
|
||||
return "@start"
|
||||
}
|
||||
if c.Mode == CTMEnd {
|
||||
return "@end"
|
||||
}
|
||||
|
||||
// We kinda manually implement omitempty for the CursorToken here
|
||||
// because omitempty does not work for time.Time and otherwise we would always
|
||||
// get weird time values when decoding a token that initially didn't have an Timestamp set
|
||||
// For this usecase we treat Unix=0 as an empty timestamp
|
||||
|
||||
sertok := cursorTokenSerialize{}
|
||||
|
||||
if c.Id != 0 {
|
||||
sertok.Id = &c.Id
|
||||
}
|
||||
|
||||
if c.Timestamp != 0 {
|
||||
sertok.Timestamp = &c.Timestamp
|
||||
}
|
||||
|
||||
if c.Direction != "" {
|
||||
sertok.Direction = &c.Direction
|
||||
}
|
||||
|
||||
if c.FilterHash != "" {
|
||||
sertok.FilterHash = &c.FilterHash
|
||||
}
|
||||
|
||||
body, err := json.Marshal(sertok)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return "tok_" + base32.StdEncoding.EncodeToString(body)
|
||||
}
|
||||
|
||||
func Decode(tok string) (CursorToken, error) {
|
||||
if tok == "" {
|
||||
return Start(), nil
|
||||
}
|
||||
if strings.ToLower(tok) == "@start" {
|
||||
return Start(), nil
|
||||
}
|
||||
if strings.ToLower(tok) == "@end" {
|
||||
return End(), nil
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(tok, "tok_") {
|
||||
return CursorToken{}, errors.New("could not decode token, missing prefix")
|
||||
}
|
||||
|
||||
body, err := base32.StdEncoding.DecodeString(tok[len("tok_"):])
|
||||
if err != nil {
|
||||
return CursorToken{}, err
|
||||
}
|
||||
|
||||
var tokenDeserialize cursorTokenSerialize
|
||||
err = json.Unmarshal(body, &tokenDeserialize)
|
||||
if err != nil {
|
||||
return CursorToken{}, err
|
||||
}
|
||||
|
||||
token := CursorToken{Mode: CTMNormal}
|
||||
|
||||
if tokenDeserialize.Timestamp != nil {
|
||||
token.Timestamp = *tokenDeserialize.Timestamp
|
||||
}
|
||||
if tokenDeserialize.Id != nil {
|
||||
token.Id = *tokenDeserialize.Id
|
||||
}
|
||||
if tokenDeserialize.Direction != nil {
|
||||
token.Direction = *tokenDeserialize.Direction
|
||||
}
|
||||
if tokenDeserialize.FilterHash != nil {
|
||||
token.FilterHash = *tokenDeserialize.FilterHash
|
||||
}
|
||||
|
||||
return token, nil
|
||||
}
|
117
scnserver/db/database.go
Normal file
117
scnserver/db/database.go
Normal file
@@ -0,0 +1,117 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
server "blackforestbytes.com/simplecloudnotifier"
|
||||
"blackforestbytes.com/simplecloudnotifier/db/schema"
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/jmoiron/sqlx"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
"github.com/rs/zerolog/log"
|
||||
"gogs.mikescher.com/BlackForestBytes/goext/langext"
|
||||
"gogs.mikescher.com/BlackForestBytes/goext/sq"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Database struct {
|
||||
db sq.DB
|
||||
}
|
||||
|
||||
func NewDatabase(conf server.Config) (*Database, error) {
|
||||
url := fmt.Sprintf("file:%s?_journal=%s&_timeout=%d&_fk=%s", conf.DBFile, conf.DBJournal, conf.DBTimeout.Milliseconds(), langext.FormatBool(conf.DBCheckForeignKeys, "true", "false"))
|
||||
|
||||
xdb, err := sqlx.Open("sqlite3", url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if conf.DBSingleConn {
|
||||
xdb.SetMaxOpenConns(1)
|
||||
} else {
|
||||
xdb.SetMaxOpenConns(5)
|
||||
xdb.SetMaxIdleConns(5)
|
||||
xdb.SetConnMaxLifetime(60 * time.Minute)
|
||||
xdb.SetConnMaxIdleTime(60 * time.Minute)
|
||||
}
|
||||
|
||||
qqdb := sq.NewDB(xdb)
|
||||
|
||||
scndb := &Database{qqdb}
|
||||
|
||||
qqdb.SetListener(scndb)
|
||||
|
||||
return scndb, nil
|
||||
}
|
||||
|
||||
func (db *Database) Migrate(ctx context.Context) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 24*time.Second)
|
||||
defer cancel()
|
||||
|
||||
currschema, err := db.ReadSchema(ctx)
|
||||
if currschema == 0 {
|
||||
|
||||
_, err = db.db.Exec(ctx, schema.Schema3, sq.PP{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = db.WriteMetaInt(ctx, "schema", 3)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
} else if currschema == 1 {
|
||||
return errors.New("cannot autom. upgrade schema 1")
|
||||
} else if currschema == 2 {
|
||||
return errors.New("cannot autom. upgrade schema 2") //TODO
|
||||
} else if currschema == 3 {
|
||||
return nil // current
|
||||
} else {
|
||||
return errors.New(fmt.Sprintf("Unknown DB schema: %d", currschema))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (db *Database) Ping(ctx context.Context) error {
|
||||
return db.db.Ping(ctx)
|
||||
}
|
||||
|
||||
func (db *Database) BeginTx(ctx context.Context) (sq.Tx, error) {
|
||||
return db.db.BeginTransaction(ctx, sql.LevelDefault)
|
||||
}
|
||||
|
||||
func (db *Database) OnQuery(txID *uint16, sql string, _ *sq.PP) {
|
||||
if txID == nil {
|
||||
log.Debug().Msg(fmt.Sprintf("[SQL-QUERY] %s", fmtSQLPrint(sql)))
|
||||
} else {
|
||||
log.Debug().Msg(fmt.Sprintf("[SQL-TX<%d>-QUERY] %s", *txID, fmtSQLPrint(sql)))
|
||||
}
|
||||
}
|
||||
|
||||
func (db *Database) OnExec(txID *uint16, sql string, _ *sq.PP) {
|
||||
if txID == nil {
|
||||
log.Debug().Msg(fmt.Sprintf("[SQL-EXEC] %s", fmtSQLPrint(sql)))
|
||||
} else {
|
||||
log.Debug().Msg(fmt.Sprintf("[SQL-TX<%d>-EXEC] %s", *txID, fmtSQLPrint(sql)))
|
||||
}
|
||||
}
|
||||
|
||||
func (db *Database) OnPing() {
|
||||
log.Debug().Msg("[SQL-PING]")
|
||||
}
|
||||
|
||||
func (db *Database) OnTxBegin(txid uint16) {
|
||||
log.Debug().Msg(fmt.Sprintf("[SQL-TX<%d>-START]", txid))
|
||||
}
|
||||
|
||||
func (db *Database) OnTxCommit(txid uint16) {
|
||||
log.Debug().Msg(fmt.Sprintf("[SQL-TX<%d>-COMMIT]", txid))
|
||||
}
|
||||
|
||||
func (db *Database) OnTxRollback(txid uint16) {
|
||||
log.Debug().Msg(fmt.Sprintf("[SQL-TX<%d>-ROLLBACK]", txid))
|
||||
}
|
187
scnserver/db/deliveries.go
Normal file
187
scnserver/db/deliveries.go
Normal file
@@ -0,0 +1,187 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
scn "blackforestbytes.com/simplecloudnotifier"
|
||||
"blackforestbytes.com/simplecloudnotifier/models"
|
||||
"gogs.mikescher.com/BlackForestBytes/goext/langext"
|
||||
"gogs.mikescher.com/BlackForestBytes/goext/sq"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (db *Database) CreateRetryDelivery(ctx TxContext, client models.Client, msg models.Message) (models.Delivery, error) {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return models.Delivery{}, err
|
||||
}
|
||||
|
||||
now := time.Now().UTC()
|
||||
next := scn.NextDeliveryTimestamp(now)
|
||||
|
||||
res, err := tx.Exec(ctx, "INSERT INTO deliveries (scn_message_id, receiver_user_id, receiver_client_id, timestamp_created, timestamp_finalized, status, fcm_message_id, next_delivery) VALUES (:mid, :ruid, :rcid, :tsc, :tsf, :stat, :fcm, :next)", sq.PP{
|
||||
"mid": msg.SCNMessageID,
|
||||
"ruid": client.UserID,
|
||||
"rcid": client.ClientID,
|
||||
"tsc": time2DB(now),
|
||||
"tsf": nil,
|
||||
"stat": models.DeliveryStatusRetry,
|
||||
"fcm": nil,
|
||||
"next": time2DB(next),
|
||||
})
|
||||
if err != nil {
|
||||
return models.Delivery{}, err
|
||||
}
|
||||
|
||||
liid, err := res.LastInsertId()
|
||||
if err != nil {
|
||||
return models.Delivery{}, err
|
||||
}
|
||||
|
||||
return models.Delivery{
|
||||
DeliveryID: models.DeliveryID(liid),
|
||||
SCNMessageID: msg.SCNMessageID,
|
||||
ReceiverUserID: client.UserID,
|
||||
ReceiverClientID: client.ClientID,
|
||||
TimestampCreated: now,
|
||||
TimestampFinalized: nil,
|
||||
Status: models.DeliveryStatusRetry,
|
||||
RetryCount: 0,
|
||||
NextDelivery: langext.Ptr(next),
|
||||
FCMMessageID: nil,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (db *Database) CreateSuccessDelivery(ctx TxContext, client models.Client, msg models.Message, fcmDelivID string) (models.Delivery, error) {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return models.Delivery{}, err
|
||||
}
|
||||
|
||||
now := time.Now().UTC()
|
||||
|
||||
res, err := tx.Exec(ctx, "INSERT INTO deliveries (scn_message_id, receiver_user_id, receiver_client_id, timestamp_created, timestamp_finalized, status, fcm_message_id, next_delivery) VALUES (:mid, :ruid, :rcid, :tsc, :tsf, :stat, :fcm, :next)", sq.PP{
|
||||
"mid": msg.SCNMessageID,
|
||||
"ruid": client.UserID,
|
||||
"rcid": client.ClientID,
|
||||
"tsc": time2DB(now),
|
||||
"tsf": time2DB(now),
|
||||
"stat": models.DeliveryStatusSuccess,
|
||||
"fcm": fcmDelivID,
|
||||
"next": nil,
|
||||
})
|
||||
if err != nil {
|
||||
return models.Delivery{}, err
|
||||
}
|
||||
|
||||
liid, err := res.LastInsertId()
|
||||
if err != nil {
|
||||
return models.Delivery{}, err
|
||||
}
|
||||
|
||||
return models.Delivery{
|
||||
DeliveryID: models.DeliveryID(liid),
|
||||
SCNMessageID: msg.SCNMessageID,
|
||||
ReceiverUserID: client.UserID,
|
||||
ReceiverClientID: client.ClientID,
|
||||
TimestampCreated: now,
|
||||
TimestampFinalized: langext.Ptr(now),
|
||||
Status: models.DeliveryStatusSuccess,
|
||||
RetryCount: 0,
|
||||
NextDelivery: nil,
|
||||
FCMMessageID: langext.Ptr(fcmDelivID),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (db *Database) ListRetrieableDeliveries(ctx TxContext, pageSize int) ([]models.Delivery, error) {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rows, err := tx.Query(ctx, "SELECT * FROM deliveries WHERE status = 'RETRY' AND next_delivery < :next LIMIT :lim", sq.PP{
|
||||
"next": time2DB(time.Now()),
|
||||
"lim": pageSize,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data, err := models.DecodeDeliveries(rows)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (db *Database) SetDeliverySuccess(ctx TxContext, delivery models.Delivery, fcmDelivID string) error {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = tx.Exec(ctx, "UPDATE deliveries SET status = 'SUCCESS', next_delivery = NULL, retry_count = :rc, timestamp_finalized = :ts, fcm_message_id = :fcm WHERE delivery_id = :did", sq.PP{
|
||||
"rc": delivery.RetryCount + 1,
|
||||
"ts": time2DB(time.Now()),
|
||||
"fcm": fcmDelivID,
|
||||
"did": delivery.DeliveryID,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *Database) SetDeliveryFailed(ctx TxContext, delivery models.Delivery) error {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = tx.Exec(ctx, "UPDATE deliveries SET status = 'FAILED', next_delivery = NULL, retry_count = :rc, timestamp_finalized = :ts WHERE delivery_id = :did",
|
||||
sq.PP{
|
||||
"rc": delivery.RetryCount + 1,
|
||||
"ts": time2DB(time.Now()),
|
||||
"did": delivery.DeliveryID,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *Database) SetDeliveryRetry(ctx TxContext, delivery models.Delivery) error {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = tx.Exec(ctx, "UPDATE deliveries SET status = 'RETRY', next_delivery = :next, retry_count = :rc WHERE delivery_id = :did", sq.PP{
|
||||
"next": scn.NextDeliveryTimestamp(time.Now()),
|
||||
"rc": delivery.RetryCount + 1,
|
||||
"did": delivery.DeliveryID,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *Database) CancelPendingDeliveries(ctx TxContext, scnMessageID models.SCNMessageID) error {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = tx.Exec(ctx, "UPDATE deliveries SET status = 'FAILED', next_delivery = NULL, timestamp_finalized = :ts WHERE scn_message_id = :mid AND status = 'RETRY'", sq.PP{
|
||||
"ts": time.Now(),
|
||||
"mid": scnMessageID,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
162
scnserver/db/messages.go
Normal file
162
scnserver/db/messages.go
Normal file
@@ -0,0 +1,162 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"blackforestbytes.com/simplecloudnotifier/db/cursortoken"
|
||||
"blackforestbytes.com/simplecloudnotifier/models"
|
||||
"database/sql"
|
||||
"gogs.mikescher.com/BlackForestBytes/goext/sq"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (db *Database) GetMessageByUserMessageID(ctx TxContext, usrMsgId string) (*models.Message, error) {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rows, err := tx.Query(ctx, "SELECT * FROM messages WHERE usr_message_id = :umid LIMIT 1", sq.PP{"umid": usrMsgId})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
msg, err := models.DecodeMessage(rows)
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &msg, nil
|
||||
}
|
||||
|
||||
func (db *Database) GetMessage(ctx TxContext, scnMessageID models.SCNMessageID, allowDeleted bool) (models.Message, error) {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return models.Message{}, err
|
||||
}
|
||||
|
||||
var sqlcmd string
|
||||
if allowDeleted {
|
||||
sqlcmd = "SELECT * FROM messages WHERE scn_message_id = :mid LIMIT 1"
|
||||
} else {
|
||||
sqlcmd = "SELECT * FROM messages WHERE scn_message_id = :mid AND deleted=0 LIMIT 1"
|
||||
}
|
||||
|
||||
rows, err := tx.Query(ctx, sqlcmd, sq.PP{"mid": scnMessageID})
|
||||
if err != nil {
|
||||
return models.Message{}, err
|
||||
}
|
||||
|
||||
msg, err := models.DecodeMessage(rows)
|
||||
if err != nil {
|
||||
return models.Message{}, err
|
||||
}
|
||||
|
||||
return msg, nil
|
||||
}
|
||||
|
||||
func (db *Database) CreateMessage(ctx TxContext, senderUserID models.UserID, channel models.Channel, timestampSend *time.Time, title string, content *string, priority int, userMsgId *string, senderIP string, senderName *string) (models.Message, error) {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return models.Message{}, err
|
||||
}
|
||||
|
||||
now := time.Now().UTC()
|
||||
|
||||
res, err := tx.Exec(ctx, "INSERT INTO messages (sender_user_id, owner_user_id, channel_name, channel_id, timestamp_real, timestamp_client, title, content, priority, usr_message_id, sender_ip, sender_name) VALUES (:suid, :ouid, :cnam, :cid, :tsr, :tsc, :tit, :cnt, :prio, :umid, :ip, :snam)", sq.PP{
|
||||
"suid": senderUserID,
|
||||
"ouid": channel.OwnerUserID,
|
||||
"cnam": channel.Name,
|
||||
"cid": channel.ChannelID,
|
||||
"tsr": time2DB(now),
|
||||
"tsc": time2DBOpt(timestampSend),
|
||||
"tit": title,
|
||||
"cnt": content,
|
||||
"prio": priority,
|
||||
"umid": userMsgId,
|
||||
"ip": senderIP,
|
||||
"snam": senderName,
|
||||
})
|
||||
if err != nil {
|
||||
return models.Message{}, err
|
||||
}
|
||||
|
||||
liid, err := res.LastInsertId()
|
||||
if err != nil {
|
||||
return models.Message{}, err
|
||||
}
|
||||
|
||||
return models.Message{
|
||||
SCNMessageID: models.SCNMessageID(liid),
|
||||
SenderUserID: senderUserID,
|
||||
OwnerUserID: channel.OwnerUserID,
|
||||
ChannelName: channel.Name,
|
||||
ChannelID: channel.ChannelID,
|
||||
SenderIP: senderIP,
|
||||
SenderName: senderName,
|
||||
TimestampReal: now,
|
||||
TimestampClient: timestampSend,
|
||||
Title: title,
|
||||
Content: content,
|
||||
Priority: priority,
|
||||
UserMessageID: userMsgId,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (db *Database) DeleteMessage(ctx TxContext, scnMessageID models.SCNMessageID) error {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = tx.Exec(ctx, "UPDATE messages SET deleted=1 WHERE scn_message_id = :mid AND deleted=0", sq.PP{"mid": scnMessageID})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *Database) ListMessages(ctx TxContext, filter models.MessageFilter, pageSize int, inTok cursortoken.CursorToken) ([]models.Message, cursortoken.CursorToken, error) {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return nil, cursortoken.CursorToken{}, err
|
||||
}
|
||||
|
||||
if inTok.Mode == cursortoken.CTMEnd {
|
||||
return make([]models.Message, 0), cursortoken.End(), nil
|
||||
}
|
||||
|
||||
pageCond := "1=1"
|
||||
if inTok.Mode == cursortoken.CTMNormal {
|
||||
pageCond = "timestamp_real < :tokts OR (timestamp_real = :tokts AND scn_message_id < :tokid )"
|
||||
}
|
||||
|
||||
filterCond, filterJoin, prepParams, err := filter.SQL()
|
||||
|
||||
orderClause := "ORDER BY COALESCE(timestamp_client, timestamp_real) DESC LIMIT :lim"
|
||||
|
||||
sqlQuery := "SELECT " + "messages.*" + " FROM messages " + filterJoin + " WHERE ( " + pageCond + " ) AND ( " + filterCond + " ) " + orderClause
|
||||
|
||||
prepParams["lim"] = pageSize + 1
|
||||
prepParams["tokts"] = inTok.Timestamp
|
||||
prepParams["tokid"] = inTok.Id
|
||||
|
||||
rows, err := tx.Query(ctx, sqlQuery, prepParams)
|
||||
if err != nil {
|
||||
return nil, cursortoken.CursorToken{}, err
|
||||
}
|
||||
|
||||
data, err := models.DecodeMessages(rows)
|
||||
if err != nil {
|
||||
return nil, cursortoken.CursorToken{}, err
|
||||
}
|
||||
|
||||
if len(data) <= pageSize {
|
||||
return data, cursortoken.End(), nil
|
||||
} else {
|
||||
outToken := cursortoken.Normal(data[pageSize-1].Timestamp(), data[pageSize-1].SCNMessageID.IntID(), "DESC", filter.Hash())
|
||||
return data[0:pageSize], outToken, nil
|
||||
}
|
||||
}
|
242
scnserver/db/meta.go
Normal file
242
scnserver/db/meta.go
Normal file
@@ -0,0 +1,242 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"gogs.mikescher.com/BlackForestBytes/goext/langext"
|
||||
"gogs.mikescher.com/BlackForestBytes/goext/sq"
|
||||
)
|
||||
|
||||
func (db *Database) ReadSchema(ctx context.Context) (retval int, reterr error) {
|
||||
|
||||
r1, err := db.db.Query(ctx, "SELECT name FROM sqlite_master WHERE type = :typ AND name = :name", sq.PP{"typ": "table", "name": "meta"})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer func() {
|
||||
err = r1.Close()
|
||||
if err != nil {
|
||||
// overwrite return values
|
||||
retval = 0
|
||||
reterr = err
|
||||
}
|
||||
}()
|
||||
|
||||
if !r1.Next() {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
err = r1.Close()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
r2, err := db.db.Query(ctx, "SELECT value_int FROM meta WHERE meta_key = :key", sq.PP{"key": "schema"})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer func() {
|
||||
err = r2.Close()
|
||||
if err != nil {
|
||||
// overwrite return values
|
||||
retval = 0
|
||||
reterr = err
|
||||
}
|
||||
}()
|
||||
|
||||
if !r2.Next() {
|
||||
return 0, errors.New("no schema entry in meta table")
|
||||
}
|
||||
|
||||
var dbschema int
|
||||
err = r2.Scan(&dbschema)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
err = r2.Close()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return dbschema, nil
|
||||
}
|
||||
|
||||
func (db *Database) WriteMetaString(ctx context.Context, key string, value string) error {
|
||||
_, err := db.db.Exec(ctx, "INSERT INTO meta (meta_key, value_txt) VALUES (:key, :val) ON CONFLICT(meta_key) DO UPDATE SET value_txt = :val", sq.PP{
|
||||
"key": key,
|
||||
"val": value,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *Database) WriteMetaInt(ctx context.Context, key string, value int64) error {
|
||||
_, err := db.db.Exec(ctx, "INSERT INTO meta (meta_key, value_int) VALUES (:key, :val) ON CONFLICT(meta_key) DO UPDATE SET value_int = :val", sq.PP{
|
||||
"key": key,
|
||||
"val": value,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *Database) WriteMetaReal(ctx context.Context, key string, value float64) error {
|
||||
_, err := db.db.Exec(ctx, "INSERT INTO meta (meta_key, value_real) VALUES (:key, :val) ON CONFLICT(meta_key) DO UPDATE SET value_real = :val", sq.PP{
|
||||
"key": key,
|
||||
"val": value,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *Database) WriteMetaBlob(ctx context.Context, key string, value []byte) error {
|
||||
_, err := db.db.Exec(ctx, "INSERT INTO meta (meta_key, value_blob) VALUES (:key, :val) ON CONFLICT(meta_key) DO UPDATE SET value_blob = :val", sq.PP{
|
||||
"key": key,
|
||||
"val": value,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *Database) ReadMetaString(ctx context.Context, key string) (retval *string, reterr error) {
|
||||
r2, err := db.db.Query(ctx, "SELECT value_txt FROM meta WHERE meta_key = :key", sq.PP{"key": key})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
err = r2.Close()
|
||||
if err != nil {
|
||||
// overwrite return values
|
||||
retval = nil
|
||||
reterr = err
|
||||
}
|
||||
}()
|
||||
if !r2.Next() {
|
||||
return nil, errors.New("no matching entry in meta table")
|
||||
}
|
||||
|
||||
var value string
|
||||
err = r2.Scan(&value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = r2.Close()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return langext.Ptr(value), nil
|
||||
}
|
||||
|
||||
func (db *Database) ReadMetaInt(ctx context.Context, key string) (retval *int64, reterr error) {
|
||||
r2, err := db.db.Query(ctx, "SELECT value_int FROM meta WHERE meta_key = :key", sq.PP{"key": key})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
err = r2.Close()
|
||||
if err != nil {
|
||||
// overwrite return values
|
||||
retval = nil
|
||||
reterr = err
|
||||
}
|
||||
}()
|
||||
|
||||
if !r2.Next() {
|
||||
return nil, errors.New("no matching entry in meta table")
|
||||
}
|
||||
|
||||
var value int64
|
||||
err = r2.Scan(&value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = r2.Close()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return langext.Ptr(value), nil
|
||||
}
|
||||
|
||||
func (db *Database) ReadMetaReal(ctx context.Context, key string) (retval *float64, reterr error) {
|
||||
r2, err := db.db.Query(ctx, "SELECT value_real FROM meta WHERE meta_key = :key", sq.PP{"key": key})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
err = r2.Close()
|
||||
if err != nil {
|
||||
// overwrite return values
|
||||
retval = nil
|
||||
reterr = err
|
||||
}
|
||||
}()
|
||||
|
||||
if !r2.Next() {
|
||||
return nil, errors.New("no matching entry in meta table")
|
||||
}
|
||||
|
||||
var value float64
|
||||
err = r2.Scan(&value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = r2.Close()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return langext.Ptr(value), nil
|
||||
}
|
||||
|
||||
func (db *Database) ReadMetaBlob(ctx context.Context, key string) (retval *[]byte, reterr error) {
|
||||
r2, err := db.db.Query(ctx, "SELECT value_blob FROM meta WHERE meta_key = :key", sq.PP{"key": key})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
err = r2.Close()
|
||||
if err != nil {
|
||||
// overwrite return values
|
||||
retval = nil
|
||||
reterr = err
|
||||
}
|
||||
}()
|
||||
|
||||
if !r2.Next() {
|
||||
return nil, errors.New("no matching entry in meta table")
|
||||
}
|
||||
|
||||
var value []byte
|
||||
err = r2.Scan(&value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = r2.Close()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return langext.Ptr(value), nil
|
||||
}
|
||||
|
||||
func (db *Database) DeleteMeta(ctx context.Context, key string) error {
|
||||
_, err := db.db.Exec(ctx, "DELETE FROM meta WHERE meta_key = :key", sq.PP{"key": key})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
12
scnserver/db/schema/assets.go
Normal file
12
scnserver/db/schema/assets.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package schema
|
||||
|
||||
import _ "embed"
|
||||
|
||||
//go:embed schema_1.ddl
|
||||
var Schema1 string
|
||||
|
||||
//go:embed schema_2.ddl
|
||||
var Schema2 string
|
||||
|
||||
//go:embed schema_3.ddl
|
||||
var Schema3 string
|
38
scnserver/db/schema/schema_1.ddl
Normal file
38
scnserver/db/schema/schema_1.ddl
Normal file
@@ -0,0 +1,38 @@
|
||||
DROP TABLE IF EXISTS `users`;
|
||||
CREATE TABLE `users`
|
||||
(
|
||||
`user_id` INT(11) NOT NULL AUTO_INCREMENT,
|
||||
`user_key` VARCHAR(64) NOT NULL,
|
||||
`fcm_token` VARCHAR(256) NULL DEFAULT NULL,
|
||||
`messages_sent` INT(11) NOT NULL DEFAULT '0',
|
||||
`timestamp_created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`timestamp_accessed` DATETIME NULL DEFAULT NULL,
|
||||
|
||||
`quota_today` INT(11) NOT NULL DEFAULT '0',
|
||||
`quota_day` DATE NULL DEFAULT NULL,
|
||||
|
||||
`is_pro` BIT NOT NULL DEFAULT 0,
|
||||
`pro_token` VARCHAR(256) NULL DEFAULT NULL,
|
||||
|
||||
PRIMARY KEY (`user_id`)
|
||||
);
|
||||
|
||||
DROP TABLE IF EXISTS `messages`;
|
||||
CREATE TABLE `messages`
|
||||
(
|
||||
`scn_message_id` INT(11) NOT NULL AUTO_INCREMENT,
|
||||
`sender_user_id` INT(11) NOT NULL,
|
||||
|
||||
`timestamp_real` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`ack` TINYINT(1) NOT NULL DEFAULT 0,
|
||||
|
||||
`title` VARCHAR(256) NOT NULL,
|
||||
`content` LONGTEXT NULL,
|
||||
`priority` INT(11) NOT NULL,
|
||||
`sendtime` BIGINT UNSIGNED NOT NULL,
|
||||
|
||||
`fcm_message_id` VARCHAR(256) NULL,
|
||||
`usr_message_id` VARCHAR(256) NULL,
|
||||
|
||||
PRIMARY KEY (`scn_message_id`)
|
||||
);
|
47
scnserver/db/schema/schema_2.ddl
Normal file
47
scnserver/db/schema/schema_2.ddl
Normal file
@@ -0,0 +1,47 @@
|
||||
CREATE TABLE `users`
|
||||
(
|
||||
`user_id` INTEGER AUTO_INCREMENT,
|
||||
`user_key` TEXT NOT NULL,
|
||||
`fcm_token` TEXT NULL DEFAULT NULL,
|
||||
`messages_sent` INTEGER NOT NULL DEFAULT '0',
|
||||
`timestamp_created` TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`timestamp_accessed` TEXT NULL DEFAULT NULL,
|
||||
|
||||
`quota_today` INTEGER NOT NULL DEFAULT '0',
|
||||
`quota_day` TEXT NULL DEFAULT NULL,
|
||||
|
||||
`is_pro` INTEGER NOT NULL DEFAULT 0,
|
||||
`pro_token` TEXT NULL DEFAULT NULL,
|
||||
|
||||
PRIMARY KEY (`user_id`)
|
||||
);
|
||||
|
||||
CREATE TABLE `messages`
|
||||
(
|
||||
`scn_message_id` INTEGER AUTO_INCREMENT,
|
||||
`sender_user_id` INTEGER NOT NULL,
|
||||
|
||||
`timestamp_real` TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`ack` INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
`title` TEXT NOT NULL,
|
||||
`content` TEXT NULL,
|
||||
`priority` INTEGER NOT NULL,
|
||||
`sendtime` INTEGER NOT NULL,
|
||||
|
||||
`fcm_message_id` TEXT NULL,
|
||||
`usr_message_id` TEXT NULL,
|
||||
|
||||
PRIMARY KEY (`scn_message_id`)
|
||||
);
|
||||
|
||||
CREATE TABLE `meta`
|
||||
(
|
||||
`key` TEXT NOT NULL,
|
||||
`value_int` INTEGER NULL,
|
||||
`value_txt` TEXT NULL,
|
||||
|
||||
PRIMARY KEY (`key`)
|
||||
);
|
||||
|
||||
INSERT INTO meta (key, value_int) VALUES ('schema', 2)
|
170
scnserver/db/schema/schema_3.ddl
Normal file
170
scnserver/db/schema/schema_3.ddl
Normal file
@@ -0,0 +1,170 @@
|
||||
CREATE TABLE users
|
||||
(
|
||||
user_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
|
||||
username TEXT NULL DEFAULT NULL,
|
||||
|
||||
send_key TEXT NOT NULL,
|
||||
read_key TEXT NOT NULL,
|
||||
admin_key TEXT NOT NULL,
|
||||
|
||||
timestamp_created INTEGER NOT NULL,
|
||||
timestamp_lastread INTEGER NULL DEFAULT NULL,
|
||||
timestamp_lastsent INTEGER NULL DEFAULT NULL,
|
||||
|
||||
messages_sent INTEGER NOT NULL DEFAULT '0',
|
||||
|
||||
quota_used INTEGER NOT NULL DEFAULT '0',
|
||||
quota_used_day TEXT NULL DEFAULT NULL,
|
||||
|
||||
is_pro INTEGER CHECK(is_pro IN (0, 1)) NOT NULL DEFAULT 0,
|
||||
pro_token TEXT NULL DEFAULT NULL
|
||||
) STRICT;
|
||||
CREATE UNIQUE INDEX "idx_users_protoken" ON users (pro_token) WHERE pro_token IS NOT NULL;
|
||||
|
||||
|
||||
CREATE TABLE clients
|
||||
(
|
||||
client_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
|
||||
user_id INTEGER NOT NULL,
|
||||
type TEXT CHECK(type IN ('ANDROID', 'IOS')) NOT NULL,
|
||||
fcm_token TEXT NULL,
|
||||
|
||||
timestamp_created INTEGER NOT NULL,
|
||||
|
||||
agent_model TEXT NOT NULL,
|
||||
agent_version TEXT NOT NULL
|
||||
) STRICT;
|
||||
CREATE INDEX "idx_clients_userid" ON clients (user_id);
|
||||
CREATE UNIQUE INDEX "idx_clients_fcmtoken" ON clients (fcm_token);
|
||||
|
||||
|
||||
CREATE TABLE channels
|
||||
(
|
||||
channel_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
|
||||
owner_user_id INTEGER NOT NULL,
|
||||
|
||||
name TEXT NOT NULL,
|
||||
|
||||
subscribe_key TEXT NOT NULL,
|
||||
send_key TEXT NOT NULL,
|
||||
|
||||
timestamp_created INTEGER NOT NULL,
|
||||
timestamp_lastsent INTEGER NULL DEFAULT NULL,
|
||||
|
||||
messages_sent INTEGER NOT NULL DEFAULT '0'
|
||||
) STRICT;
|
||||
CREATE UNIQUE INDEX "idx_channels_identity" ON channels (owner_user_id, name);
|
||||
|
||||
CREATE TABLE subscriptions
|
||||
(
|
||||
subscription_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
|
||||
subscriber_user_id INTEGER NOT NULL,
|
||||
channel_owner_user_id INTEGER NOT NULL,
|
||||
channel_name TEXT NOT NULL,
|
||||
channel_id INTEGER NOT NULL,
|
||||
|
||||
timestamp_created INTEGER NOT NULL,
|
||||
|
||||
confirmed INTEGER CHECK(confirmed IN (0, 1)) NOT NULL
|
||||
) STRICT;
|
||||
CREATE UNIQUE INDEX "idx_subscriptions_ref" ON subscriptions (subscriber_user_id, channel_owner_user_id, channel_name);
|
||||
|
||||
|
||||
CREATE TABLE messages
|
||||
(
|
||||
scn_message_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
sender_user_id INTEGER NOT NULL,
|
||||
owner_user_id INTEGER NOT NULL,
|
||||
channel_name TEXT NOT NULL,
|
||||
channel_id INTEGER NOT NULL,
|
||||
sender_ip TEXT NOT NULL,
|
||||
sender_name TEXT NULL,
|
||||
|
||||
timestamp_real INTEGER NOT NULL,
|
||||
timestamp_client INTEGER NULL,
|
||||
|
||||
title TEXT NOT NULL,
|
||||
content TEXT NULL,
|
||||
priority INTEGER CHECK(priority IN (0, 1, 2)) NOT NULL,
|
||||
usr_message_id TEXT NULL,
|
||||
|
||||
deleted INTEGER CHECK(deleted IN (0, 1)) NOT NULL DEFAULT '0'
|
||||
) STRICT;
|
||||
CREATE INDEX "idx_messages_owner_channel" ON messages (owner_user_id, channel_name COLLATE BINARY);
|
||||
CREATE INDEX "idx_messages_owner_channel_nc" ON messages (owner_user_id, channel_name COLLATE NOCASE);
|
||||
CREATE INDEX "idx_messages_channel" ON messages (channel_name COLLATE BINARY);
|
||||
CREATE INDEX "idx_messages_channel_nc" ON messages (channel_name COLLATE NOCASE);
|
||||
CREATE UNIQUE INDEX "idx_messages_idempotency" ON messages (owner_user_id, usr_message_id COLLATE BINARY);
|
||||
CREATE INDEX "idx_messages_senderip" ON messages (sender_ip COLLATE BINARY);
|
||||
CREATE INDEX "idx_messages_sendername" ON messages (sender_name COLLATE BINARY);
|
||||
CREATE INDEX "idx_messages_sendername_nc" ON messages (sender_name COLLATE NOCASE);
|
||||
CREATE INDEX "idx_messages_title" ON messages (title COLLATE BINARY);
|
||||
CREATE INDEX "idx_messages_title_nc" ON messages (title COLLATE NOCASE);
|
||||
CREATE INDEX "idx_messages_deleted" ON messages (deleted);
|
||||
|
||||
|
||||
CREATE VIRTUAL TABLE messages_fts USING fts5
|
||||
(
|
||||
channel_name,
|
||||
sender_name,
|
||||
title,
|
||||
content,
|
||||
|
||||
tokenize = unicode61,
|
||||
content = 'messages',
|
||||
content_rowid = 'scn_message_id'
|
||||
);
|
||||
|
||||
CREATE TRIGGER fts_insert AFTER INSERT ON messages BEGIN
|
||||
INSERT INTO messages_fts (rowid, channel_name, sender_name, title, content) VALUES (new.scn_message_id, new.channel_name, new.sender_name, new.title, new.content);
|
||||
END;
|
||||
|
||||
CREATE TRIGGER fts_update AFTER UPDATE ON messages BEGIN
|
||||
INSERT INTO messages_fts (messages_fts, rowid, channel_name, sender_name, title, content) VALUES ('delete', old.scn_message_id, old.channel_name, old.sender_name, old.title, old.content);
|
||||
INSERT INTO messages_fts ( rowid, channel_name, sender_name, title, content) VALUES ( new.scn_message_id, new.channel_name, new.sender_name, new.title, new.content);
|
||||
END;
|
||||
|
||||
CREATE TRIGGER fts_delete AFTER DELETE ON messages BEGIN
|
||||
INSERT INTO messages_fts (messages_fts, rowid, channel_name, sender_name, title, content) VALUES ('delete', old.scn_message_id, old.channel_name, old.sender_name, old.title, old.content);
|
||||
END;
|
||||
|
||||
|
||||
|
||||
CREATE TABLE deliveries
|
||||
(
|
||||
delivery_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
|
||||
scn_message_id INTEGER NOT NULL,
|
||||
receiver_user_id INTEGER NOT NULL,
|
||||
receiver_client_id INTEGER NOT NULL,
|
||||
|
||||
timestamp_created INTEGER NOT NULL,
|
||||
timestamp_finalized INTEGER NULL,
|
||||
|
||||
|
||||
status TEXT CHECK(status IN ('RETRY','SUCCESS','FAILED')) NOT NULL,
|
||||
retry_count INTEGER NOT NULL DEFAULT 0,
|
||||
next_delivery INTEGER NULL DEFAULT NULL,
|
||||
|
||||
fcm_message_id TEXT NULL
|
||||
) STRICT;
|
||||
CREATE INDEX "idx_deliveries_receiver" ON deliveries (scn_message_id, receiver_client_id);
|
||||
|
||||
|
||||
CREATE TABLE `meta`
|
||||
(
|
||||
meta_key TEXT NOT NULL,
|
||||
value_int INTEGER NULL,
|
||||
value_txt TEXT NULL,
|
||||
value_real REAL NULL,
|
||||
value_blob BLOB NULL,
|
||||
|
||||
PRIMARY KEY (meta_key)
|
||||
) STRICT;
|
||||
|
||||
|
||||
INSERT INTO meta (meta_key, value_int) VALUES ('schema', 3)
|
7
scnserver/db/schema/schema_sqlite.ddl
Normal file
7
scnserver/db/schema/schema_sqlite.ddl
Normal file
@@ -0,0 +1,7 @@
|
||||
CREATE TABLE sqlite_master (
|
||||
type text,
|
||||
name text,
|
||||
tbl_name text,
|
||||
rootpage integer,
|
||||
sql text
|
||||
);
|
157
scnserver/db/subscriptions.go
Normal file
157
scnserver/db/subscriptions.go
Normal file
@@ -0,0 +1,157 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"blackforestbytes.com/simplecloudnotifier/models"
|
||||
"database/sql"
|
||||
"gogs.mikescher.com/BlackForestBytes/goext/sq"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (db *Database) CreateSubscription(ctx TxContext, subscriberUID models.UserID, channel models.Channel, confirmed bool) (models.Subscription, error) {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return models.Subscription{}, err
|
||||
}
|
||||
|
||||
now := time.Now().UTC()
|
||||
|
||||
res, err := tx.Exec(ctx, "INSERT INTO subscriptions (subscriber_user_id, channel_owner_user_id, channel_name, channel_id, timestamp_created, confirmed) VALUES (:suid, :ouid, :cnam, :cid, :ts, :conf)", sq.PP{
|
||||
"suid": subscriberUID,
|
||||
"ouid": channel.OwnerUserID,
|
||||
"cnam": channel.Name,
|
||||
"cid": channel.ChannelID,
|
||||
"ts": time2DB(now),
|
||||
"conf": confirmed,
|
||||
})
|
||||
if err != nil {
|
||||
return models.Subscription{}, err
|
||||
}
|
||||
|
||||
liid, err := res.LastInsertId()
|
||||
if err != nil {
|
||||
return models.Subscription{}, err
|
||||
}
|
||||
|
||||
return models.Subscription{
|
||||
SubscriptionID: models.SubscriptionID(liid),
|
||||
SubscriberUserID: subscriberUID,
|
||||
ChannelOwnerUserID: channel.OwnerUserID,
|
||||
ChannelID: channel.ChannelID,
|
||||
ChannelName: channel.Name,
|
||||
TimestampCreated: now,
|
||||
Confirmed: confirmed,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (db *Database) ListSubscriptionsByChannel(ctx TxContext, channelID models.ChannelID) ([]models.Subscription, error) {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rows, err := tx.Query(ctx, "SELECT * FROM subscriptions WHERE channel_id = :cid", sq.PP{"cid": channelID})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data, err := models.DecodeSubscriptions(rows)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (db *Database) ListSubscriptionsByOwner(ctx TxContext, ownerUserID models.UserID) ([]models.Subscription, error) {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rows, err := tx.Query(ctx, "SELECT * FROM subscriptions WHERE channel_owner_user_id = :ouid", sq.PP{"ouid": ownerUserID})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data, err := models.DecodeSubscriptions(rows)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (db *Database) GetSubscription(ctx TxContext, subid models.SubscriptionID) (models.Subscription, error) {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return models.Subscription{}, err
|
||||
}
|
||||
|
||||
rows, err := tx.Query(ctx, "SELECT * FROM subscriptions WHERE subscription_id = :sid LIMIT 1", sq.PP{"sid": subid})
|
||||
if err != nil {
|
||||
return models.Subscription{}, err
|
||||
}
|
||||
|
||||
sub, err := models.DecodeSubscription(rows)
|
||||
if err != nil {
|
||||
return models.Subscription{}, err
|
||||
}
|
||||
|
||||
return sub, nil
|
||||
}
|
||||
|
||||
func (db *Database) GetSubscriptionBySubscriber(ctx TxContext, subscriberId models.UserID, channelId models.ChannelID) (*models.Subscription, error) {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rows, err := tx.Query(ctx, "SELECT * FROM subscriptions WHERE subscriber_user_id = :suid AND channel_id = :cid LIMIT 1", sq.PP{
|
||||
"suid": subscriberId,
|
||||
"cid": channelId,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
user, err := models.DecodeSubscription(rows)
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
func (db *Database) DeleteSubscription(ctx TxContext, subid models.SubscriptionID) error {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = tx.Exec(ctx, "DELETE FROM subscriptions WHERE subscription_id = :sid", sq.PP{"sid": subid})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *Database) UpdateSubscriptionConfirmed(ctx TxContext, subscriptionID models.SubscriptionID, confirmed bool) error {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = tx.Exec(ctx, "UPDATE subscriptions SET confirmed = :conf WHERE subscription_id = :sid", sq.PP{
|
||||
"conf": confirmed,
|
||||
"sid": subscriptionID,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
251
scnserver/db/users.go
Normal file
251
scnserver/db/users.go
Normal file
@@ -0,0 +1,251 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
scn "blackforestbytes.com/simplecloudnotifier"
|
||||
"blackforestbytes.com/simplecloudnotifier/models"
|
||||
"database/sql"
|
||||
"gogs.mikescher.com/BlackForestBytes/goext/sq"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (db *Database) CreateUser(ctx TxContext, readKey string, sendKey string, adminKey string, protoken *string, username *string) (models.User, error) {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return models.User{}, err
|
||||
}
|
||||
|
||||
now := time.Now().UTC()
|
||||
|
||||
res, err := tx.Exec(ctx, "INSERT INTO users (username, read_key, send_key, admin_key, is_pro, pro_token, timestamp_created) VALUES (:un, :rk, :sk, :ak, :pro, :tok, :ts)", sq.PP{
|
||||
"un": username,
|
||||
"rk": readKey,
|
||||
"sk": sendKey,
|
||||
"ak": adminKey,
|
||||
"pro": bool2DB(protoken != nil),
|
||||
"tok": protoken,
|
||||
"ts": time2DB(now),
|
||||
})
|
||||
if err != nil {
|
||||
return models.User{}, err
|
||||
}
|
||||
|
||||
liid, err := res.LastInsertId()
|
||||
if err != nil {
|
||||
return models.User{}, err
|
||||
}
|
||||
|
||||
return models.User{
|
||||
UserID: models.UserID(liid),
|
||||
Username: username,
|
||||
ReadKey: readKey,
|
||||
SendKey: sendKey,
|
||||
AdminKey: adminKey,
|
||||
TimestampCreated: now,
|
||||
TimestampLastRead: nil,
|
||||
TimestampLastSent: nil,
|
||||
MessagesSent: 0,
|
||||
QuotaUsed: 0,
|
||||
QuotaUsedDay: nil,
|
||||
IsPro: protoken != nil,
|
||||
ProToken: protoken,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (db *Database) ClearProTokens(ctx TxContext, protoken string) error {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = tx.Exec(ctx, "UPDATE users SET is_pro=0, pro_token=NULL WHERE pro_token = :tok", sq.PP{"tok": protoken})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *Database) GetUserByKey(ctx TxContext, key string) (*models.User, error) {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rows, err := tx.Query(ctx, "SELECT * FROM users WHERE admin_key = :key OR send_key = :key OR read_key = :key LIMIT 1", sq.PP{"key": key})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
user, err := models.DecodeUser(rows)
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
func (db *Database) GetUser(ctx TxContext, userid models.UserID) (models.User, error) {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return models.User{}, err
|
||||
}
|
||||
|
||||
rows, err := tx.Query(ctx, "SELECT * FROM users WHERE user_id = :uid LIMIT 1", sq.PP{"uid": userid})
|
||||
if err != nil {
|
||||
return models.User{}, err
|
||||
}
|
||||
|
||||
user, err := models.DecodeUser(rows)
|
||||
if err != nil {
|
||||
return models.User{}, err
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (db *Database) UpdateUserUsername(ctx TxContext, userid models.UserID, username *string) error {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = tx.Exec(ctx, "UPDATE users SET username = :nam WHERE user_id = :uid", sq.PP{
|
||||
"nam": username,
|
||||
"uid": userid,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *Database) UpdateUserProToken(ctx TxContext, userid models.UserID, protoken *string) error {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = tx.Exec(ctx, "UPDATE users SET pro_token = :tok, is_pro = :pro WHERE user_id = :uid", sq.PP{
|
||||
"tok": protoken,
|
||||
"pro": bool2DB(protoken != nil),
|
||||
"uid": userid,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *Database) IncUserMessageCounter(ctx TxContext, user models.User) error {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
quota := user.QuotaUsedToday() + 1
|
||||
|
||||
_, err = tx.Exec(ctx, "UPDATE users SET timestamp_lastsent = :ts, messages_sent = :ctr, quota_used = :qu, quota_used_day = :qd WHERE user_id = :uid", sq.PP{
|
||||
"ts": time2DB(time.Now()),
|
||||
"ctr": user.MessagesSent + 1,
|
||||
"qu": quota,
|
||||
"qd": scn.QuotaDayString(),
|
||||
"uid": user.UserID,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *Database) UpdateUserLastRead(ctx TxContext, userid models.UserID) error {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = tx.Exec(ctx, "UPDATE users SET timestamp_lastread = :ts WHERE user_id = :uid", sq.PP{
|
||||
"ts": time2DB(time.Now()),
|
||||
"uid": userid,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *Database) UpdateUserKeys(ctx TxContext, userid models.UserID, sendKey string, readKey string, adminKey string) error {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = tx.Exec(ctx, "UPDATE users SET send_key = :sk, read_key = :rk, admin_key = :ak WHERE user_id = ?", sq.PP{
|
||||
"sk": sendKey,
|
||||
"rk": readKey,
|
||||
"ak": adminKey,
|
||||
"uid": userid,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *Database) UpdateUserSendKey(ctx TxContext, userid models.UserID, newkey string) error {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = tx.Exec(ctx, "UPDATE users SET send_key = :sk WHERE user_id = :uid", sq.PP{
|
||||
"sk": newkey,
|
||||
"uid": userid,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *Database) UpdateUserReadKey(ctx TxContext, userid models.UserID, newkey string) error {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = tx.Exec(ctx, "UPDATE users SET read_key = :rk WHERE user_id = :uid", sq.PP{
|
||||
"rk": newkey,
|
||||
"uid": userid,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *Database) UpdateUserAdminKey(ctx TxContext, userid models.UserID, newkey string) error {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = tx.Exec(ctx, "UPDATE users SET admin_key = :ak WHERE user_id = :uid", sq.PP{
|
||||
"ak": newkey,
|
||||
"uid": userid,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
37
scnserver/db/utils.go
Normal file
37
scnserver/db/utils.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"gogs.mikescher.com/BlackForestBytes/goext/langext"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func bool2DB(b bool) int {
|
||||
if b {
|
||||
return 1
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
func time2DB(t time.Time) int64 {
|
||||
return t.UnixMilli()
|
||||
}
|
||||
|
||||
func time2DBOpt(t *time.Time) *int64 {
|
||||
if t == nil {
|
||||
return nil
|
||||
}
|
||||
return langext.Ptr(t.UnixMilli())
|
||||
}
|
||||
|
||||
func fmtSQLPrint(sql string) string {
|
||||
if strings.Contains(sql, ";") {
|
||||
return "(...multi...)"
|
||||
}
|
||||
|
||||
sql = strings.ReplaceAll(sql, "\r", "")
|
||||
sql = strings.ReplaceAll(sql, "\n", " ")
|
||||
|
||||
return sql
|
||||
}
|
Reference in New Issue
Block a user