diff --git a/flutter/TODO.md b/flutter/TODO.md index c74ee12..cea51e1 100644 --- a/flutter/TODO.md +++ b/flutter/TODO.md @@ -31,4 +31,5 @@ - [ ] Single struct for model/db/json - [ ] use sq.Query | sq.Update | sq.InsertAndQuery | .... - [ ] sq.DBOptions - enable CommentTrimmer and DefaultConverter - - [ ] run unit-tests... \ No newline at end of file + - [ ] run unit-tests... + - [ ] Copy db.Migrate code \ No newline at end of file diff --git a/flutter/lib/models/client.dart b/flutter/lib/models/client.dart index cc14851..895d90c 100644 --- a/flutter/lib/models/client.dart +++ b/flutter/lib/models/client.dart @@ -6,6 +6,7 @@ class Client { final String timestampCreated; final String agentModel; final String agentVersion; + final String? descriptionName; const Client({ required this.clientID, @@ -15,6 +16,7 @@ class Client { required this.timestampCreated, required this.agentModel, required this.agentVersion, + required this.descriptionName, }); factory Client.fromJson(Map json) { @@ -26,6 +28,7 @@ class Client { timestampCreated: json['timestamp_created'] as String, agentModel: json['agent_model'] as String, agentVersion: json['agent_version'] as String, + descriptionName: json['description_name'] as String?, ); } diff --git a/scnserver/.idea/sqldialects.xml b/scnserver/.idea/sqldialects.xml index cb98251..f8df685 100644 --- a/scnserver/.idea/sqldialects.xml +++ b/scnserver/.idea/sqldialects.xml @@ -5,7 +5,7 @@ - + \ No newline at end of file diff --git a/scnserver/api/handler/apiClient.go b/scnserver/api/handler/apiClient.go index 506df06..72c299f 100644 --- a/scnserver/api/handler/apiClient.go +++ b/scnserver/api/handler/apiClient.go @@ -119,10 +119,11 @@ func (h APIHandler) AddClient(g *gin.Context) ginresp.HTTPResponse { UserID models.UserID `uri:"uid" binding:"entityid"` } type body struct { - FCMToken string `json:"fcm_token" binding:"required"` - AgentModel string `json:"agent_model" binding:"required"` - AgentVersion string `json:"agent_version" binding:"required"` - ClientType models.ClientType `json:"client_type" binding:"required"` + FCMToken string `json:"fcm_token" binding:"required"` + AgentModel string `json:"agent_model" binding:"required"` + AgentVersion string `json:"agent_version" binding:"required"` + DescriptionName *string `json:"description_name"` + ClientType models.ClientType `json:"client_type" binding:"required"` } var u uri @@ -147,7 +148,7 @@ func (h APIHandler) AddClient(g *gin.Context) ginresp.HTTPResponse { return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to delete existing clients in db", err) } - client, err := h.database.CreateClient(ctx, u.UserID, clientType, b.FCMToken, b.AgentModel, b.AgentVersion) + client, err := h.database.CreateClient(ctx, u.UserID, clientType, b.FCMToken, b.AgentModel, b.AgentVersion, b.DescriptionName) if err != nil { return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to create client in db", err) } @@ -230,9 +231,10 @@ func (h APIHandler) UpdateClient(g *gin.Context) ginresp.HTTPResponse { ClientID models.ClientID `uri:"cid" binding:"entityid"` } type body struct { - FCMToken *string `json:"fcm_token"` - AgentModel *string `json:"agent_model"` - AgentVersion *string `json:"agent_version"` + FCMToken *string `json:"fcm_token"` + AgentModel *string `json:"agent_model"` + AgentVersion *string `json:"agent_version"` + DescriptionName *string `json:"description_name"` } var u uri @@ -282,6 +284,20 @@ func (h APIHandler) UpdateClient(g *gin.Context) ginresp.HTTPResponse { } } + if b.DescriptionName != nil { + if *b.DescriptionName == "" { + err = h.database.UpdateClientDescriptionName(ctx, u.ClientID, nil) + if err != nil { + return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to update client", err) + } + } else { + err = h.database.UpdateClientDescriptionName(ctx, u.ClientID, langext.Ptr(*b.DescriptionName)) + if err != nil { + return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to update client", err) + } + } + } + client, err = h.database.GetClient(ctx, u.UserID, u.ClientID) if err != nil { return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to query (updated) client", err) diff --git a/scnserver/api/handler/apiUser.go b/scnserver/api/handler/apiUser.go index 39c1703..ef12927 100644 --- a/scnserver/api/handler/apiUser.go +++ b/scnserver/api/handler/apiUser.go @@ -28,13 +28,14 @@ import ( // @Router /api/v2/users [POST] func (h APIHandler) CreateUser(g *gin.Context) ginresp.HTTPResponse { type body struct { - FCMToken string `json:"fcm_token"` - ProToken *string `json:"pro_token"` - Username *string `json:"username"` - AgentModel string `json:"agent_model"` - AgentVersion string `json:"agent_version"` - ClientType models.ClientType `json:"client_type"` - NoClient bool `json:"no_client"` + FCMToken string `json:"fcm_token"` + ProToken *string `json:"pro_token"` + Username *string `json:"username"` + AgentModel string `json:"agent_model"` + AgentVersion string `json:"agent_version"` + DescriptionName *string `json:"description_name"` + ClientType models.ClientType `json:"client_type"` + NoClient bool `json:"no_client"` } var b body @@ -123,7 +124,7 @@ func (h APIHandler) CreateUser(g *gin.Context) ginresp.HTTPResponse { return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to delete existing clients in db", err) } - client, err := h.database.CreateClient(ctx, userobj.UserID, clientType, b.FCMToken, b.AgentModel, b.AgentVersion) + client, err := h.database.CreateClient(ctx, userobj.UserID, clientType, b.FCMToken, b.AgentModel, b.AgentVersion, b.DescriptionName) if err != nil { return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to create client in db", err) } diff --git a/scnserver/api/handler/compat.go b/scnserver/api/handler/compat.go index fa5a007..180465d 100644 --- a/scnserver/api/handler/compat.go +++ b/scnserver/api/handler/compat.go @@ -206,7 +206,7 @@ func (h CompatHandler) Register(g *gin.Context) ginresp.HTTPResponse { return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to create admin-key in db", err) } - _, err = h.database.CreateClient(ctx, user.UserID, models.ClientTypeAndroid, *data.FCMToken, "compat", "compat") + _, err = h.database.CreateClient(ctx, user.UserID, models.ClientTypeAndroid, *data.FCMToken, "compat", "compat", nil) if err != nil { return ginresp.CompatAPIError(0, "Failed to create client in db") } @@ -661,7 +661,7 @@ func (h CompatHandler) Update(g *gin.Context) ginresp.HTTPResponse { } - _, err = h.database.CreateClient(ctx, user.UserID, models.ClientTypeAndroid, *data.FCMToken, "compat", "compat") + _, err = h.database.CreateClient(ctx, user.UserID, models.ClientTypeAndroid, *data.FCMToken, "compat", "compat", nil) if err != nil { return ginresp.CompatAPIError(0, "Failed to create client") } diff --git a/scnserver/cmd/dbhash/main.go b/scnserver/cmd/dbhash/main.go index 1e6748a..ca64535 100644 --- a/scnserver/cmd/dbhash/main.go +++ b/scnserver/cmd/dbhash/main.go @@ -5,55 +5,45 @@ import ( "context" "fmt" "github.com/mattn/go-sqlite3" + "gogs.mikescher.com/BlackForestBytes/goext/exerr" "gogs.mikescher.com/BlackForestBytes/goext/sq" "time" ) func main() { + exerr.Init(exerr.ErrorPackageConfigInit{}) + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() sqlite3.Version() // ensure slite3 loaded - { - h0, err := sq.HashSqliteSchema(ctx, schema.PrimarySchema[1].SQL) + + fmt.Println() + + for i := 2; i <= schema.PrimarySchemaVersion; i++ { + h0, err := sq.HashMattnSqliteSchema(ctx, schema.PrimarySchema[i].SQL) if err != nil { h0 = "ERR" } - fmt.Printf("PrimarySchema1 := %s\n", h0) + fmt.Printf("PrimarySchema%d := %s\n", i, h0) } - { - h0, err := sq.HashSqliteSchema(ctx, schema.PrimarySchema[2].SQL) + + for i := 1; i <= schema.RequestsSchemaVersion; i++ { + h0, err := sq.HashMattnSqliteSchema(ctx, schema.RequestsSchema[i].SQL) if err != nil { h0 = "ERR" } - fmt.Printf("PrimarySchema2 := %s\n", h0) + fmt.Printf("RequestsSchema%d := %s\n", i, h0) } - { - h0, err := sq.HashSqliteSchema(ctx, schema.PrimarySchema[3].SQL) + + for i := 1; i <= schema.LogsSchemaVersion; i++ { + h0, err := sq.HashMattnSqliteSchema(ctx, schema.LogsSchema[i].SQL) if err != nil { h0 = "ERR" } - fmt.Printf("PrimarySchema3 := %s\n", h0) - } - { - h0, err := sq.HashSqliteSchema(ctx, schema.PrimarySchema[4].SQL) - if err != nil { - h0 = "ERR" - } - fmt.Printf("PrimarySchema4 := %s\n", h0) - } - { - h0, err := sq.HashSqliteSchema(ctx, schema.RequestsSchema[1].SQL) - if err != nil { - h0 = "ERR" - } - fmt.Printf("RequestsSchema1 := %s\n", h0) - } - { - h0, err := sq.HashSqliteSchema(ctx, schema.LogsSchema[1].SQL) - if err != nil { - h0 = "ERR" - } - fmt.Printf("LogsSchema1 := %s\n", h0) + fmt.Printf("LogsSchema%d := %s\n", i, h0) } + + fmt.Println() + } diff --git a/scnserver/db/impl/primary/clients.go b/scnserver/db/impl/primary/clients.go index 4c56f2b..922f815 100644 --- a/scnserver/db/impl/primary/clients.go +++ b/scnserver/db/impl/primary/clients.go @@ -7,7 +7,7 @@ import ( "time" ) -func (db *Database) CreateClient(ctx db.TxContext, userid models.UserID, ctype models.ClientType, fcmToken string, agentModel string, agentVersion string) (models.Client, error) { +func (db *Database) CreateClient(ctx db.TxContext, userid models.UserID, ctype models.ClientType, fcmToken string, agentModel string, agentVersion string, descriptionName *string) (models.Client, error) { tx, err := ctx.GetOrCreateTransaction(db) if err != nil { return models.Client{}, err @@ -21,6 +21,7 @@ func (db *Database) CreateClient(ctx db.TxContext, userid models.UserID, ctype m TimestampCreated: time2DB(time.Now()), AgentModel: agentModel, AgentVersion: agentVersion, + DescriptionName: descriptionName, } _, err = sq.InsertSingle(ctx, tx, "clients", entity) @@ -164,3 +165,20 @@ func (db *Database) UpdateClientAgentVersion(ctx db.TxContext, clientid models.C return nil } + +func (db *Database) UpdateClientDescriptionName(ctx db.TxContext, clientid models.ClientID, descriptionName *string) error { + tx, err := ctx.GetOrCreateTransaction(db) + if err != nil { + return err + } + + _, err = tx.Exec(ctx, "UPDATE clients SET description_name = :vvv WHERE client_id = :cid", sq.PP{ + "vvv": descriptionName, + "cid": clientid, + }) + if err != nil { + return err + } + + return nil +} diff --git a/scnserver/db/impl/primary/database.go b/scnserver/db/impl/primary/database.go index 237a35d..52ad6e9 100644 --- a/scnserver/db/impl/primary/database.go +++ b/scnserver/db/impl/primary/database.go @@ -160,7 +160,7 @@ func (db *Database) Migrate(outerctx context.Context) error { return err } - log.Info().Int("currschema", currschema).Msg("Upgrade schema from 3 -> 4 succesfuly") + log.Info().Int("currschema", currschema).Msg("Upgrade schema from 3 -> 4 succesfully") ppReInit = true } @@ -185,6 +185,51 @@ func (db *Database) Migrate(outerctx context.Context) error { } else { log.Debug().Str("schemHash", schemHashDB).Msg("Verified Schema consistency (primary db)") } + + log.Info().Int("currschema", currschema).Msg("Upgrade schema from 4 -> 5") + + _, err = tx.Exec(tctx, schema.PrimaryMigration_4_5, sq.PP{}) + if err != nil { + return err + } + + currschema = 5 + + err = db.WriteMetaInt(tctx, "schema", int64(currschema)) + if err != nil { + return err + } + + err = db.WriteMetaString(tctx, "schema_hash", schema.PrimarySchema[currschema].Hash) + if err != nil { + return err + } + + log.Info().Int("currschema", currschema).Msg("Upgrade schema from 4 -> 5 succesfully") + + ppReInit = true + } + + if currschema == 5 { + + schemaHashMeta, err := db.ReadMetaString(tctx, "schema_hash") + if err != nil { + return err + } + + schemHashDB, err := sq.HashSqliteDatabase(tctx, tx) + if err != nil { + return err + } + + if schemHashDB != langext.Coalesce(schemaHashMeta, "") || langext.Coalesce(schemaHashMeta, "") != schema.PrimarySchema[currschema].Hash { + log.Debug().Str("schemHashDB", schemHashDB).Msg("Schema (primary db)") + log.Debug().Str("schemaHashMeta", langext.Coalesce(schemaHashMeta, "")).Msg("Schema (primary db)") + log.Debug().Str("schemaHashAsset", schema.PrimarySchema[currschema].Hash).Msg("Schema (primary db)") + return errors.New("database schema does not match (primary db)") + } else { + log.Debug().Str("schemHash", schemHashDB).Msg("Verified Schema consistency (primary db)") + } } if currschema != schema.PrimarySchemaVersion { diff --git a/scnserver/db/schema/assets.go b/scnserver/db/schema/assets.go index 5f16036..ceef776 100644 --- a/scnserver/db/schema/assets.go +++ b/scnserver/db/schema/assets.go @@ -19,9 +19,15 @@ var primarySchema3 string //go:embed primary_4.ddl var primarySchema4 string +//go:embed primary_5.ddl +var primarySchema5 string + //go:embed primary_migration_3_4.ddl var PrimaryMigration_3_4 string +//go:embed primary_migration_4_5.ddl +var PrimaryMigration_4_5 string + //go:embed requests_1.ddl var requestsSchema1 string @@ -34,9 +40,10 @@ var PrimarySchema = map[int]Def{ 2: {primarySchema2, "07ed1449114416ed043084a30e0722a5f97bf172161338d2f7106a8dfd387d0a"}, 3: {primarySchema3, "65c2125ad0e12d02490cf2275f0067ef3c62a8522edf9a35ee8aa3f3c09b12e8"}, 4: {primarySchema4, "cb022156ab0e7aea39dd0c985428c43cae7d60e41ca8e9e5a84c774b3019d2ca"}, + 5: {primarySchema5, "04bd0d4a81540f69f10c8f8cd656a1fdf852d4ef7a2ab2918ca6369b5423b1b6"}, } -var PrimarySchemaVersion = 4 +var PrimarySchemaVersion = 5 var RequestsSchema = map[int]Def{ 0: {"", ""}, diff --git a/scnserver/db/schema/primary_5.ddl b/scnserver/db/schema/primary_5.ddl new file mode 100644 index 0000000..bed6725 --- /dev/null +++ b/scnserver/db/schema/primary_5.ddl @@ -0,0 +1,234 @@ +CREATE TABLE users +( + user_id TEXT NOT NULL, + + username 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', + + 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, + + PRIMARY KEY (user_id) +) STRICT; +CREATE UNIQUE INDEX "idx_users_protoken" ON users (pro_token) WHERE pro_token IS NOT NULL; + + +CREATE TABLE keytokens +( + keytoken_id TEXT NOT NULL, + + timestamp_created INTEGER NOT NULL, + timestamp_lastused INTEGER NULL DEFAULT NULL, + + name TEXT NOT NULL, + + owner_user_id TEXT NOT NULL, + + all_channels INTEGER CHECK(all_channels IN (0, 1)) NOT NULL, + channels TEXT NOT NULL, + token TEXT NOT NULL, + permissions TEXT NOT NULL, + + messages_sent INTEGER NOT NULL DEFAULT '0', + + PRIMARY KEY (keytoken_id) +) STRICT; +CREATE UNIQUE INDEX "idx_keytokens_token" ON keytokens (token); + + +CREATE TABLE clients +( + client_id TEXT NOT NULL, + + user_id TEXT NOT NULL, + type TEXT CHECK(type IN ('ANDROID', 'IOS')) NOT NULL, + fcm_token TEXT NOT NULL, + description_name TEXT NULL, + + timestamp_created INTEGER NOT NULL, + + agent_model TEXT NOT NULL, + agent_version TEXT NOT NULL, + + PRIMARY KEY (client_id) +) 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 TEXT NOT NULL, + + owner_user_id TEXT NOT NULL, + + internal_name TEXT NOT NULL, + display_name TEXT NOT NULL, + description_name TEXT NULL, + + subscribe_key TEXT NOT NULL, + + timestamp_created INTEGER NOT NULL, + timestamp_lastsent INTEGER NULL DEFAULT NULL, + + messages_sent INTEGER NOT NULL DEFAULT '0', + + PRIMARY KEY (channel_id) +) STRICT; +CREATE UNIQUE INDEX "idx_channels_identity" ON channels (owner_user_id, internal_name); + +CREATE TABLE subscriptions +( + subscription_id TEXT NOT NULL, + + subscriber_user_id TEXT NOT NULL, + channel_owner_user_id TEXT NOT NULL, + channel_internal_name TEXT NOT NULL, + channel_id TEXT NOT NULL, + + timestamp_created INTEGER NOT NULL, + + confirmed INTEGER CHECK(confirmed IN (0, 1)) NOT NULL, + + PRIMARY KEY (subscription_id) +) STRICT; +CREATE UNIQUE INDEX "idx_subscriptions_ref" ON subscriptions (subscriber_user_id, channel_owner_user_id, channel_internal_name); +CREATE INDEX "idx_subscriptions_chan" ON subscriptions (channel_id); +CREATE INDEX "idx_subscriptions_subuser" ON subscriptions (subscriber_user_id); +CREATE INDEX "idx_subscriptions_ownuser" ON subscriptions (channel_owner_user_id); +CREATE INDEX "idx_subscriptions_tsc" ON subscriptions (timestamp_created); +CREATE INDEX "idx_subscriptions_conf" ON subscriptions (confirmed); + + +CREATE TABLE messages +( + message_id TEXT NOT NULL, + sender_user_id TEXT NOT NULL, + channel_internal_name TEXT NOT NULL, + channel_id TEXT 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, + + used_key_id TEXT NOT NULL, + + deleted INTEGER CHECK(deleted IN (0, 1)) NOT NULL DEFAULT '0', + + PRIMARY KEY (message_id) +) STRICT; +CREATE INDEX "idx_messages_channel" ON messages (channel_internal_name COLLATE BINARY); +CREATE INDEX "idx_messages_channel_nc" ON messages (channel_internal_name COLLATE NOCASE); +CREATE UNIQUE INDEX "idx_messages_idempotency" ON messages (sender_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_usedkey" ON messages (sender_user_id, used_key_id); +CREATE INDEX "idx_messages_deleted" ON messages (deleted); + + +CREATE VIRTUAL TABLE messages_fts USING fts5 +( + channel_internal_name, + sender_name, + title, + content, + + tokenize = unicode61, + content = 'messages', + content_rowid = 'rowid' +); + +CREATE TRIGGER fts_insert AFTER INSERT ON messages BEGIN + INSERT INTO messages_fts (rowid, channel_internal_name, sender_name, title, content) VALUES (new.rowid, new.channel_internal_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_internal_name, sender_name, title, content) VALUES ('delete', old.rowid, old.channel_internal_name, old.sender_name, old.title, old.content); + INSERT INTO messages_fts ( rowid, channel_internal_name, sender_name, title, content) VALUES ( new.rowid, new.channel_internal_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_internal_name, sender_name, title, content) VALUES ('delete', old.rowid, old.channel_internal_name, old.sender_name, old.title, old.content); +END; + + +CREATE TABLE deliveries +( + delivery_id TEXT NOT NULL, + + message_id TEXT NOT NULL, + receiver_user_id TEXT NOT NULL, + receiver_client_id TEXT 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 TEXT NULL DEFAULT NULL, + + fcm_message_id TEXT NULL, + + PRIMARY KEY (delivery_id) +) STRICT; +CREATE INDEX "idx_deliveries_receiver" ON deliveries (message_id, receiver_client_id); + + +CREATE TABLE compat_ids +( + old INTEGER NOT NULL, + new TEXT NOT NULL, + type TEXT NOT NULL +) STRICT; +CREATE UNIQUE INDEX "idx_compatids_new" ON compat_ids (new); +CREATE UNIQUE INDEX "idx_compatids_old" ON compat_ids (old, type); + + +CREATE TABLE compat_acks +( + user_id TEXT NOT NULL, + message_id TEXT NOT NULL +) STRICT; +CREATE INDEX "idx_compatacks_userid" ON compat_acks (user_id); +CREATE UNIQUE INDEX "idx_compatacks_messageid" ON compat_acks (message_id); +CREATE UNIQUE INDEX "idx_compatacks_userid_messageid" ON compat_acks (user_id, message_id); + + +CREATE TABLE compat_clients +( + client_id TEXT NOT NULL +) STRICT; +CREATE UNIQUE INDEX "idx_compatclient_clientid" ON compat_clients (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) \ No newline at end of file diff --git a/scnserver/db/schema/primary_migration_4_5.ddl b/scnserver/db/schema/primary_migration_4_5.ddl new file mode 100644 index 0000000..658d440 --- /dev/null +++ b/scnserver/db/schema/primary_migration_4_5.ddl @@ -0,0 +1,6 @@ + +ALTER TABLE clients ADD COLUMN "description_name" TEXT NULL; + + + + diff --git a/scnserver/models/client.go b/scnserver/models/client.go index 61d2dfa..ba0f4a6 100644 --- a/scnserver/models/client.go +++ b/scnserver/models/client.go @@ -26,6 +26,7 @@ type Client struct { TimestampCreated time.Time AgentModel string AgentVersion string + DescriptionName *string } func (c Client) JSON() ClientJSON { @@ -37,6 +38,7 @@ func (c Client) JSON() ClientJSON { TimestampCreated: c.TimestampCreated.Format(time.RFC3339Nano), AgentModel: c.AgentModel, AgentVersion: c.AgentVersion, + DescriptionName: c.DescriptionName, } } @@ -48,6 +50,7 @@ type ClientJSON struct { TimestampCreated string `json:"timestamp_created"` AgentModel string `json:"agent_model"` AgentVersion string `json:"agent_version"` + DescriptionName *string `json:"description_name"` } type ClientDB struct { @@ -58,6 +61,7 @@ type ClientDB struct { TimestampCreated int64 `db:"timestamp_created"` AgentModel string `db:"agent_model"` AgentVersion string `db:"agent_version"` + DescriptionName *string `db:"description_name"` } func (c ClientDB) Model() Client { @@ -69,6 +73,7 @@ func (c ClientDB) Model() Client { TimestampCreated: timeFromMilli(c.TimestampCreated), AgentModel: c.AgentModel, AgentVersion: c.AgentVersion, + DescriptionName: c.DescriptionName, } } diff --git a/scnserver/models/enums_gen.go b/scnserver/models/enums_gen.go index 755fada..6048a26 100644 --- a/scnserver/models/enums_gen.go +++ b/scnserver/models/enums_gen.go @@ -5,7 +5,7 @@ package models import "gogs.mikescher.com/BlackForestBytes/goext/langext" import "gogs.mikescher.com/BlackForestBytes/goext/enums" -const ChecksumEnumGenerator = "20ad4720103f17781ed6aaa102b503abb182ac08c077b42d4f56557ef38f28eb" // GoExtVersion: 0.0.463 +const ChecksumEnumGenerator = "814434d6d179eec7fb90b380db859daf6051b5d767d83a20633f9ef78d66e857" // GoExtVersion: 0.0.463 // ================================ ClientType ================================ // diff --git a/scnserver/models/ids_gen.go b/scnserver/models/ids_gen.go index 119d58a..775b9d7 100644 --- a/scnserver/models/ids_gen.go +++ b/scnserver/models/ids_gen.go @@ -15,7 +15,7 @@ import "reflect" import "regexp" import "strings" -const ChecksumCharsetIDGenerator = "20ad4720103f17781ed6aaa102b503abb182ac08c077b42d4f56557ef38f28eb" // GoExtVersion: 0.0.463 +const ChecksumCharsetIDGenerator = "814434d6d179eec7fb90b380db859daf6051b5d767d83a20633f9ef78d66e857" // GoExtVersion: 0.0.463 const idlen = 24 diff --git a/scnserver/swagger/swagger.json b/scnserver/swagger/swagger.json index 8347912..62684b6 100644 --- a/scnserver/swagger/swagger.json +++ b/scnserver/swagger/swagger.json @@ -19,37 +19,61 @@ "parameters": [ { "type": "string", + "example": "test", + "name": "channel", + "in": "query" + }, + { + "type": "string", + "example": "This is a message", "name": "content", "in": "query" }, { "type": "string", + "example": "P3TNH8mvv14fm", + "name": "key", + "in": "query" + }, + { + "type": "string", + "example": "db8b0e6a-a08c-4646", "name": "msg_id", "in": "query" }, { + "enum": [ + 0, + 1, + 2 + ], "type": "integer", + "example": 1, "name": "priority", "in": "query" }, + { + "type": "string", + "example": "example-server", + "name": "sender_name", + "in": "query" + }, { "type": "number", + "example": 1669824037, "name": "timestamp", "in": "query" }, { "type": "string", + "example": "Hello World", "name": "title", "in": "query" }, - { - "type": "integer", - "name": "user_id", - "in": "query" - }, { "type": "string", - "name": "user_key", + "example": "7725", + "name": "user_id", "in": "query" }, { @@ -62,37 +86,61 @@ }, { "type": "string", + "example": "test", + "name": "channel", + "in": "formData" + }, + { + "type": "string", + "example": "This is a message", "name": "content", "in": "formData" }, { "type": "string", + "example": "P3TNH8mvv14fm", + "name": "key", + "in": "formData" + }, + { + "type": "string", + "example": "db8b0e6a-a08c-4646", "name": "msg_id", "in": "formData" }, { + "enum": [ + 0, + 1, + 2 + ], "type": "integer", + "example": 1, "name": "priority", "in": "formData" }, + { + "type": "string", + "example": "example-server", + "name": "sender_name", + "in": "formData" + }, { "type": "number", + "example": 1669824037, "name": "timestamp", "in": "formData" }, { "type": "string", + "example": "Hello World", "name": "title", "in": "formData" }, - { - "type": "integer", - "name": "user_id", - "in": "formData" - }, { "type": "string", - "name": "user_key", + "example": "7725", + "name": "user_id", "in": "formData" } ], @@ -2509,37 +2557,61 @@ "parameters": [ { "type": "string", + "example": "test", + "name": "channel", + "in": "query" + }, + { + "type": "string", + "example": "This is a message", "name": "content", "in": "query" }, { "type": "string", + "example": "P3TNH8mvv14fm", + "name": "key", + "in": "query" + }, + { + "type": "string", + "example": "db8b0e6a-a08c-4646", "name": "msg_id", "in": "query" }, { + "enum": [ + 0, + 1, + 2 + ], "type": "integer", + "example": 1, "name": "priority", "in": "query" }, + { + "type": "string", + "example": "example-server", + "name": "sender_name", + "in": "query" + }, { "type": "number", + "example": 1669824037, "name": "timestamp", "in": "query" }, { "type": "string", + "example": "Hello World", "name": "title", "in": "query" }, - { - "type": "integer", - "name": "user_id", - "in": "query" - }, { "type": "string", - "name": "user_key", + "example": "7725", + "name": "user_id", "in": "query" }, { @@ -2552,37 +2624,61 @@ }, { "type": "string", + "example": "test", + "name": "channel", + "in": "formData" + }, + { + "type": "string", + "example": "This is a message", "name": "content", "in": "formData" }, { "type": "string", + "example": "P3TNH8mvv14fm", + "name": "key", + "in": "formData" + }, + { + "type": "string", + "example": "db8b0e6a-a08c-4646", "name": "msg_id", "in": "formData" }, { + "enum": [ + 0, + 1, + 2 + ], "type": "integer", + "example": 1, "name": "priority", "in": "formData" }, + { + "type": "string", + "example": "example-server", + "name": "sender_name", + "in": "formData" + }, { "type": "number", + "example": 1669824037, "name": "timestamp", "in": "formData" }, { "type": "string", + "example": "Hello World", "name": "title", "in": "formData" }, - { - "type": "integer", - "name": "user_id", - "in": "formData" - }, { "type": "string", - "name": "user_key", + "example": "7725", + "name": "user_id", "in": "formData" } ], @@ -2631,72 +2727,120 @@ "parameters": [ { "type": "string", + "example": "test", + "name": "channel", + "in": "query" + }, + { + "type": "string", + "example": "This is a message", "name": "content", "in": "query" }, { "type": "string", + "example": "P3TNH8mvv14fm", + "name": "key", + "in": "query" + }, + { + "type": "string", + "example": "db8b0e6a-a08c-4646", "name": "msg_id", "in": "query" }, { + "enum": [ + 0, + 1, + 2 + ], "type": "integer", + "example": 1, "name": "priority", "in": "query" }, + { + "type": "string", + "example": "example-server", + "name": "sender_name", + "in": "query" + }, { "type": "number", + "example": 1669824037, "name": "timestamp", "in": "query" }, { "type": "string", + "example": "Hello World", "name": "title", "in": "query" }, { - "type": "integer", + "type": "string", + "example": "7725", "name": "user_id", "in": "query" }, { "type": "string", - "name": "user_key", - "in": "query" + "example": "test", + "name": "channel", + "in": "formData" }, { "type": "string", + "example": "This is a message", "name": "content", "in": "formData" }, { "type": "string", + "example": "P3TNH8mvv14fm", + "name": "key", + "in": "formData" + }, + { + "type": "string", + "example": "db8b0e6a-a08c-4646", "name": "msg_id", "in": "formData" }, { + "enum": [ + 0, + 1, + 2 + ], "type": "integer", + "example": 1, "name": "priority", "in": "formData" }, + { + "type": "string", + "example": "example-server", + "name": "sender_name", + "in": "formData" + }, { "type": "number", + "example": 1669824037, "name": "timestamp", "in": "formData" }, { "type": "string", + "example": "Hello World", "name": "title", "in": "formData" }, - { - "type": "integer", - "name": "user_id", - "in": "formData" - }, { "type": "string", - "name": "user_key", + "example": "7725", + "name": "user_id", "in": "formData" } ], @@ -2895,6 +3039,9 @@ "client_type": { "$ref": "#/definitions/models.ClientType" }, + "description_name": { + "type": "string" + }, "fcm_token": { "type": "string" } @@ -2940,6 +3087,9 @@ "client_type": { "$ref": "#/definitions/models.ClientType" }, + "description_name": { + "type": "string" + }, "fcm_token": { "type": "string" }, @@ -3187,26 +3337,46 @@ "handler.SendMessage.combined": { "type": "object", "properties": { + "channel": { + "type": "string", + "example": "test" + }, "content": { - "type": "string" + "type": "string", + "example": "This is a message" + }, + "key": { + "type": "string", + "example": "P3TNH8mvv14fm" }, "msg_id": { - "type": "string" + "type": "string", + "example": "db8b0e6a-a08c-4646" }, "priority": { - "type": "integer" + "type": "integer", + "enum": [ + 0, + 1, + 2 + ], + "example": 1 + }, + "sender_name": { + "type": "string", + "example": "example-server" }, "timestamp": { - "type": "number" + "type": "number", + "example": 1669824037 }, "title": { - "type": "string" + "type": "string", + "example": "Hello World" }, "user_id": { - "type": "integer" - }, - "user_key": { - "type": "string" + "type": "string", + "example": "7725" } } }, @@ -3235,7 +3405,7 @@ "type": "integer" }, "scn_msg_id": { - "type": "integer" + "type": "string" }, "success": { "type": "boolean" @@ -3471,6 +3641,9 @@ "client_id": { "type": "string" }, + "description_name": { + "type": "string" + }, "fcm_token": { "type": "string" }, diff --git a/scnserver/swagger/swagger.yaml b/scnserver/swagger/swagger.yaml index 987e65f..37d7ba3 100644 --- a/scnserver/swagger/swagger.yaml +++ b/scnserver/swagger/swagger.yaml @@ -129,6 +129,8 @@ definitions: type: string client_type: $ref: '#/definitions/models.ClientType' + description_name: + type: string fcm_token: type: string required: @@ -163,6 +165,8 @@ definitions: type: string client_type: $ref: '#/definitions/models.ClientType' + description_name: + type: string fcm_token: type: string no_client: @@ -323,19 +327,36 @@ definitions: type: object handler.SendMessage.combined: properties: + channel: + example: test + type: string content: + example: This is a message + type: string + key: + example: P3TNH8mvv14fm type: string msg_id: + example: db8b0e6a-a08c-4646 type: string priority: + enum: + - 0 + - 1 + - 2 + example: 1 type: integer + sender_name: + example: example-server + type: string timestamp: + example: 1669824037 type: number title: + example: Hello World type: string user_id: - type: integer - user_key: + example: "7725" type: string type: object handler.SendMessage.response: @@ -355,7 +376,7 @@ definitions: quota_max: type: integer scn_msg_id: - type: integer + type: string success: type: boolean suppress_send: @@ -508,6 +529,8 @@ definitions: type: string client_id: type: string + description_name: + type: string fcm_token: type: string timestamp_created: @@ -717,52 +740,90 @@ paths: description: All parameter can be set via query-parameter or the json body. Only UserID, UserKey and Title are required parameters: - - in: query + - example: test + in: query + name: channel + type: string + - example: This is a message + in: query name: content type: string - - in: query + - example: P3TNH8mvv14fm + in: query + name: key + type: string + - example: db8b0e6a-a08c-4646 + in: query name: msg_id type: string - - in: query + - enum: + - 0 + - 1 + - 2 + example: 1 + in: query name: priority type: integer - - in: query + - example: example-server + in: query + name: sender_name + type: string + - example: 1669824037 + in: query name: timestamp type: number - - in: query + - example: Hello World + in: query name: title type: string - - in: query + - example: "7725" + in: query name: user_id - type: integer - - in: query - name: user_key type: string - description: ' ' in: body name: post_body schema: $ref: '#/definitions/handler.SendMessage.combined' - - in: formData + - example: test + in: formData + name: channel + type: string + - example: This is a message + in: formData name: content type: string - - in: formData + - example: P3TNH8mvv14fm + in: formData + name: key + type: string + - example: db8b0e6a-a08c-4646 + in: formData name: msg_id type: string - - in: formData + - enum: + - 0 + - 1 + - 2 + example: 1 + in: formData name: priority type: integer - - in: formData + - example: example-server + in: formData + name: sender_name + type: string + - example: 1669824037 + in: formData name: timestamp type: number - - in: formData + - example: Hello World + in: formData name: title type: string - - in: formData + - example: "7725" + in: formData name: user_id - type: integer - - in: formData - name: user_key type: string responses: "200": @@ -2420,52 +2481,90 @@ paths: description: All parameter can be set via query-parameter or the json body. Only UserID, UserKey and Title are required parameters: - - in: query + - example: test + in: query + name: channel + type: string + - example: This is a message + in: query name: content type: string - - in: query + - example: P3TNH8mvv14fm + in: query + name: key + type: string + - example: db8b0e6a-a08c-4646 + in: query name: msg_id type: string - - in: query + - enum: + - 0 + - 1 + - 2 + example: 1 + in: query name: priority type: integer - - in: query + - example: example-server + in: query + name: sender_name + type: string + - example: 1669824037 + in: query name: timestamp type: number - - in: query + - example: Hello World + in: query name: title type: string - - in: query + - example: "7725" + in: query name: user_id - type: integer - - in: query - name: user_key type: string - description: ' ' in: body name: post_body schema: $ref: '#/definitions/handler.SendMessage.combined' - - in: formData + - example: test + in: formData + name: channel + type: string + - example: This is a message + in: formData name: content type: string - - in: formData + - example: P3TNH8mvv14fm + in: formData + name: key + type: string + - example: db8b0e6a-a08c-4646 + in: formData name: msg_id type: string - - in: formData + - enum: + - 0 + - 1 + - 2 + example: 1 + in: formData name: priority type: integer - - in: formData + - example: example-server + in: formData + name: sender_name + type: string + - example: 1669824037 + in: formData name: timestamp type: number - - in: formData + - example: Hello World + in: formData name: title type: string - - in: formData + - example: "7725" + in: formData name: user_id - type: integer - - in: formData - name: user_key type: string responses: "200": @@ -2498,47 +2597,85 @@ paths: description: All parameter can be set via query-parameter or form-data body. Only UserID, UserKey and Title are required parameters: - - in: query + - example: test + in: query + name: channel + type: string + - example: This is a message + in: query name: content type: string - - in: query + - example: P3TNH8mvv14fm + in: query + name: key + type: string + - example: db8b0e6a-a08c-4646 + in: query name: msg_id type: string - - in: query + - enum: + - 0 + - 1 + - 2 + example: 1 + in: query name: priority type: integer - - in: query + - example: example-server + in: query + name: sender_name + type: string + - example: 1669824037 + in: query name: timestamp type: number - - in: query + - example: Hello World + in: query name: title type: string - - in: query + - example: "7725" + in: query name: user_id - type: integer - - in: query - name: user_key type: string - - in: formData + - example: test + in: formData + name: channel + type: string + - example: This is a message + in: formData name: content type: string - - in: formData + - example: P3TNH8mvv14fm + in: formData + name: key + type: string + - example: db8b0e6a-a08c-4646 + in: formData name: msg_id type: string - - in: formData + - enum: + - 0 + - 1 + - 2 + example: 1 + in: formData name: priority type: integer - - in: formData + - example: example-server + in: formData + name: sender_name + type: string + - example: 1669824037 + in: formData name: timestamp type: number - - in: formData + - example: Hello World + in: formData name: title type: string - - in: formData + - example: "7725" + in: formData name: user_id - type: integer - - in: formData - name: user_key type: string responses: "200": diff --git a/scnserver/test/database_test.go b/scnserver/test/database_test.go index 882bf07..fde75d4 100644 --- a/scnserver/test/database_test.go +++ b/scnserver/test/database_test.go @@ -357,10 +357,108 @@ func TestPrimaryDB_Migrate_from_3(t *testing.T) { tt.TestFailIfErr(t, err) } + //================================================ { err = db1.Migrate(ctx) tt.TestFailIfErr(t, err) } + //================================================ + + { + tctx := simplectx.CreateSimpleContext(ctx, nil) + + schema2, err := db1.ReadSchema(tctx) + tt.TestFailIfErr(t, err) + tt.AssertEqual(t, "schema2", schema.PrimarySchemaVersion, schema2) + + err = tctx.CommitTransaction() + tt.TestFailIfErr(t, err) + } + + { + tctx := simplectx.CreateSimpleContext(ctx, nil) + schemHashDB, err := sq.HashSqliteDatabase(tctx, db1.DB()) + tt.TestFailIfErr(t, err) + tt.AssertEqual(t, "schemHashDB", schema.PrimarySchema[schema.PrimarySchemaVersion].Hash, schemHashDB) + err = tctx.CommitTransaction() + tt.TestFailIfErr(t, err) + } + + err = db1.Stop(ctx) + tt.TestFailIfErr(t, err) + } +} + +func TestPrimaryDB_Migrate_from_4(t *testing.T) { + dbf1, dbf2, dbf3, conf, stop := tt.StartSimpleTestspace(t) + defer stop() + + ctx := context.Background() + + tt.AssertAny(dbf1) + tt.AssertAny(dbf2) + tt.AssertAny(dbf3) + tt.AssertAny(conf) + + { + url := fmt.Sprintf("file:%s", dbf1) + + xdb, err := sqlx.Open("sqlite3", url) + tt.TestFailIfErr(t, err) + + qqdb := sq.NewDB(xdb, sq.DBOptions{}) + + schemavers := 4 + + dbschema := schema.PrimarySchema[schemavers] + + _, err = qqdb.Exec(ctx, dbschema.SQL, sq.PP{}) + tt.TestFailIfErr(t, err) + + _, err = qqdb.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": "schema", + "val": schemavers, + }) + + _, err = qqdb.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": "schema_hash", + "val": dbschema.Hash, + }) + + { + tctx := simplectx.CreateSimpleContext(ctx, nil) + schemHashDB, err := sq.HashSqliteDatabase(tctx, qqdb) + tt.TestFailIfErr(t, err) + tt.AssertEqual(t, "schemHashDB", dbschema.Hash, schemHashDB) + err = tctx.CommitTransaction() + tt.TestFailIfErr(t, err) + } + + err = qqdb.Exit() + tt.TestFailIfErr(t, err) + } + + { + db1, err := primary.NewPrimaryDatabase(conf) + tt.TestFailIfErr(t, err) + + { + tctx := simplectx.CreateSimpleContext(ctx, nil) + + schema1, err := db1.ReadSchema(tctx) + tt.TestFailIfErr(t, err) + tt.AssertEqual(t, "schema1", 4, schema1) + + err = tctx.CommitTransaction() + tt.TestFailIfErr(t, err) + } + + //================================================ + { + err = db1.Migrate(ctx) + tt.TestFailIfErr(t, err) + } + //================================================ { tctx := simplectx.CreateSimpleContext(ctx, nil)