POST:/users works
This commit is contained in:
15
server/db/context.go
Normal file
15
server/db/context.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"time"
|
||||
)
|
||||
|
||||
type TxContext interface {
|
||||
Deadline() (deadline time.Time, ok bool)
|
||||
Done() <-chan struct{}
|
||||
Err() error
|
||||
Value(key any) any
|
||||
|
||||
GetOrCreateTransaction(db *Database) (*sql.Tx, error)
|
||||
}
|
||||
@@ -8,43 +8,60 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
"time"
|
||||
)
|
||||
|
||||
//go:embed schema_1.0.sql
|
||||
//go:embed schema_1.0.ddl
|
||||
var schema_1_0 string
|
||||
|
||||
//go:embed schema_2.0.sql
|
||||
//go:embed schema_2.0.ddl
|
||||
var schema_2_0 string
|
||||
|
||||
func NewDatabase(ctx context.Context, conf scn.Config) (*sql.DB, error) {
|
||||
//go:embed schema_3.0.ddl
|
||||
var schema_3_0 string
|
||||
|
||||
type Database struct {
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
func NewDatabase(conf scn.Config) (*Database, error) {
|
||||
db, err := sql.Open("sqlite3", conf.DBFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
schema, err := getSchemaFromDB(ctx, db)
|
||||
return &Database{db}, nil
|
||||
}
|
||||
|
||||
func (db *Database) Migrate(ctx context.Context) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 24*time.Second)
|
||||
defer cancel()
|
||||
|
||||
schema, err := db.ReadSchema(ctx)
|
||||
if schema == 0 {
|
||||
|
||||
_, err = db.ExecContext(ctx, schema_1_0)
|
||||
_, err = db.db.ExecContext(ctx, schema_3_0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
|
||||
return db, nil
|
||||
return nil
|
||||
|
||||
} else if schema == 1 {
|
||||
return nil, errors.New("cannot autom. upgrade schema 1")
|
||||
return errors.New("cannot autom. upgrade schema 1")
|
||||
} else if schema == 2 {
|
||||
return db, nil
|
||||
return errors.New("cannot autom. upgrade schema 2") //TODO
|
||||
} else if schema == 3 {
|
||||
return nil // current
|
||||
} else {
|
||||
return nil, errors.New(fmt.Sprintf("Unknown DB schema: %d", schema))
|
||||
return errors.New(fmt.Sprintf("Unknown DB schema: %d", schema))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func getSchemaFromDB(ctx context.Context, db *sql.DB) (int, error) {
|
||||
func (db *Database) ReadSchema(ctx context.Context) (int, error) {
|
||||
|
||||
r1, err := db.QueryContext(ctx, "SELECT name FROM sqlite_master WHERE type='table' AND name='meta'")
|
||||
r1, err := db.db.QueryContext(ctx, "SELECT name FROM sqlite_master WHERE type='table' AND name='meta'")
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
@@ -53,7 +70,7 @@ func getSchemaFromDB(ctx context.Context, db *sql.DB) (int, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
r2, err := db.QueryContext(ctx, "SELECT value_int FROM meta WHERE key='schema'")
|
||||
r2, err := db.db.QueryContext(ctx, "SELECT value_int FROM meta WHERE meta_key='schema'")
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
@@ -69,3 +86,11 @@ func getSchemaFromDB(ctx context.Context, db *sql.DB) (int, error) {
|
||||
|
||||
return schema, nil
|
||||
}
|
||||
|
||||
func (db *Database) Ping() error {
|
||||
return db.db.Ping()
|
||||
}
|
||||
|
||||
func (db *Database) BeginTx(ctx context.Context) (*sql.Tx, error) {
|
||||
return db.db.BeginTx(ctx, nil)
|
||||
}
|
||||
|
||||
112
server/db/methods.go
Normal file
112
server/db/methods.go
Normal file
@@ -0,0 +1,112 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"blackforestbytes.com/simplecloudnotifier/api/models"
|
||||
"gogs.mikescher.com/BlackForestBytes/goext/langext"
|
||||
"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.ExecContext(ctx, "INSERT INTO users (username, read_key, send_key, admin_key, is_pro, pro_token, timestamp_created) VALUES (?, ?, ?, ?, ?, ?, ?)",
|
||||
username,
|
||||
readKey,
|
||||
sendKey,
|
||||
adminKey,
|
||||
bool2DB(protoken != nil),
|
||||
protoken,
|
||||
time2DB(now))
|
||||
if err != nil {
|
||||
return models.User{}, err
|
||||
}
|
||||
|
||||
liid, err := res.LastInsertId()
|
||||
if err != nil {
|
||||
return models.User{}, err
|
||||
}
|
||||
|
||||
return models.User{
|
||||
UserID: liid,
|
||||
Username: username,
|
||||
ReadKey: readKey,
|
||||
SendKey: sendKey,
|
||||
AdminKey: adminKey,
|
||||
TimestampCreated: now,
|
||||
TimestampLastRead: nil,
|
||||
TimestampLastSent: nil,
|
||||
MessagesSent: 0,
|
||||
QuotaToday: 0,
|
||||
QuotaDay: nil,
|
||||
IsPro: protoken != nil,
|
||||
ProToken: protoken,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (db *Database) CreateClient(ctx TxContext, userid int64, 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.ExecContext(ctx, "INSERT INTO clients (user_id, type, fcm_token, timestamp_created, agent_model, agent_version) VALUES (?, ?, ?, ?, ?, ?)",
|
||||
userid,
|
||||
string(ctype),
|
||||
fcmToken,
|
||||
time2DB(now),
|
||||
agentModel,
|
||||
agentVersion)
|
||||
if err != nil {
|
||||
return models.Client{}, err
|
||||
}
|
||||
|
||||
liid, err := res.LastInsertId()
|
||||
if err != nil {
|
||||
return models.Client{}, err
|
||||
}
|
||||
|
||||
return models.Client{
|
||||
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.ExecContext(ctx, "DELETE FROM clients WHERE fcm_token = ?", fcmtoken)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *Database) ClearProTokens(ctx TxContext, protoken string) error {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = tx.ExecContext(ctx, "UPDATE users SET is_pro=0, pro_token=NULL WHERE pro_token = ?", protoken)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -8,9 +8,9 @@ CREATE TABLE users
|
||||
send_key TEXT NOT NULL,
|
||||
admin_key TEXT NOT NULL,
|
||||
|
||||
timestamp_created TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
timestamp_lastread TEXT NULL DEFAULT NULL,
|
||||
timestamp_lastsent TEXT NULL DEFAULT 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',
|
||||
|
||||
@@ -18,24 +18,23 @@ CREATE TABLE users
|
||||
quota_day TEXT NULL DEFAULT NULL,
|
||||
|
||||
is_pro INTEGER CHECK(is_pro IN (0, 1)) NOT NULL DEFAULT 0,
|
||||
pro_token TEXT NULL DEFAULT NULL,
|
||||
|
||||
PRIMARY KEY (user_id)
|
||||
pro_token TEXT NULL DEFAULT NULL
|
||||
);
|
||||
CREATE UNIQUE INDEX "idx_users_protoken" ON users (pro_token);
|
||||
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,
|
||||
client_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
|
||||
user_id INTEGER NOT NULL,
|
||||
user_id INTEGER NOT NULL,
|
||||
type TEXT CHECK(type IN ('ANDROID', 'IOS')) NOT NULL,
|
||||
fcm_token TEXT NULL,
|
||||
|
||||
type TEXT NOT NULL,
|
||||
timestamp_created INTEGER NOT NULL,
|
||||
|
||||
fcm_token TEXT NULL,
|
||||
|
||||
PRIMARY KEY (client_id)
|
||||
agent_model TEXT NOT NULL,
|
||||
agent_version TEXT NOT NULL
|
||||
);
|
||||
CREATE INDEX "idx_clients_userid" ON clients (user_id);
|
||||
CREATE UNIQUE INDEX "idx_clients_fcmtoken" ON clients (fcm_token);
|
||||
@@ -54,11 +53,9 @@ CREATE TABLE channels
|
||||
|
||||
messages_sent INTEGER NOT NULL DEFAULT '0',
|
||||
|
||||
timestamp_created TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
timestamp_lastread TEXT NULL DEFAULT NULL,
|
||||
timestamp_lastsent TEXT NULL DEFAULT NULL,
|
||||
|
||||
PRIMARY KEY (channel_id)
|
||||
timestamp_created INTEGER NOT NULL,
|
||||
timestamp_lastread INTEGER NULL DEFAULT NULL,
|
||||
timestamp_lastsent INTEGER NULL DEFAULT NULL
|
||||
);
|
||||
CREATE UNIQUE INDEX "idx_channels_identity" ON channels (owner_user_id, name);
|
||||
|
||||
@@ -68,9 +65,7 @@ CREATE TABLE subscriptions
|
||||
|
||||
subscriber_user_id INTEGER NOT NULL,
|
||||
channel_owner_user_id INTEGER NOT NULL,
|
||||
channel_name TEXT NOT NULL,
|
||||
|
||||
PRIMARY KEY (subscription_id)
|
||||
channel_name TEXT NOT NULL
|
||||
);
|
||||
CREATE UNIQUE INDEX "idx_subscriptions_ref" ON subscriptions (subscriber_user_id, channel_owner_user_id, channel_name);
|
||||
|
||||
@@ -83,15 +78,13 @@ CREATE TABLE messages
|
||||
|
||||
channel_id INTEGER NOT NULL,
|
||||
|
||||
timestamp_real TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
timestamp_client 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,
|
||||
|
||||
PRIMARY KEY (scn_message_id)
|
||||
usr_message_id TEXT NULL
|
||||
);
|
||||
CREATE INDEX "idx_messages_channel" ON messages (sender_user_id, channel_name);
|
||||
CREATE INDEX "idx_messages_idempotency" ON messages (sender_user_id, usr_message_id);
|
||||
@@ -105,16 +98,15 @@ CREATE TABLE deliveries
|
||||
receiver_user_id INTEGER NOT NULL,
|
||||
receiver_client_id INTEGER NOT NULL,
|
||||
|
||||
timestamp_created TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
timestamp_finalized TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
timestamp_created INTEGER NOT NULL,
|
||||
timestamp_finalized INTEGER NOT 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,
|
||||
|
||||
PRIMARY KEY (delivery_id)
|
||||
fcm_message_id TEXT NULL
|
||||
);
|
||||
CREATE INDEX "idx_deliveries_receiver" ON deliveries (scn_message_id, receiver_client_id);
|
||||
|
||||
7
server/db/schema_sqlite.ddl
Normal file
7
server/db/schema_sqlite.ddl
Normal file
@@ -0,0 +1,7 @@
|
||||
CREATE TABLE sqlite_master (
|
||||
type text,
|
||||
name text,
|
||||
tbl_name text,
|
||||
rootpage integer,
|
||||
sql text
|
||||
);
|
||||
15
server/db/utils.go
Normal file
15
server/db/utils.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package db
|
||||
|
||||
import "time"
|
||||
|
||||
func bool2DB(b bool) int {
|
||||
if b {
|
||||
return 1
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
func time2DB(t time.Time) int64 {
|
||||
return t.UnixMilli()
|
||||
}
|
||||
Reference in New Issue
Block a user