POST:/users works

This commit is contained in:
2022-11-18 21:25:40 +01:00
parent 34a27d9ca4
commit 5991631bfa
44 changed files with 2131 additions and 737 deletions

15
server/db/context.go Normal file
View 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)
}

View File

@@ -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
View 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
}

View File

@@ -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);

View 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
View 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()
}