diff --git a/scnserver/cmd/scnserver/main.go b/scnserver/cmd/scnserver/main.go index beeb1c8..545b7f9 100644 --- a/scnserver/cmd/scnserver/main.go +++ b/scnserver/cmd/scnserver/main.go @@ -37,7 +37,7 @@ func main() { AllowCors: &conf.Cors, GinDebug: &conf.GinDebug, BufferBody: langext.PTrue, - Timeout: langext.Ptr(time.Duration(int64(conf.RequestTimeout) * int64(conf.RequestMaxRetry))), + Timeout: langext.Ptr(time.Duration(int64(conf.RequestTimeout) * int64(conf.RequestMaxRetry+1))), BuildRequestBindError: logic.BuildGinRequestError, }) diff --git a/scnserver/go.mod b/scnserver/go.mod index 293d812..4850cb6 100644 --- a/scnserver/go.mod +++ b/scnserver/go.mod @@ -5,7 +5,7 @@ go 1.23.0 toolchain go1.24.2 require ( - git.blackforestbytes.com/BlackForestBytes/goext v0.0.572 + git.blackforestbytes.com/BlackForestBytes/goext v0.0.575 github.com/gin-gonic/gin v1.10.0 github.com/glebarez/go-sqlite v1.22.0 github.com/go-playground/validator/v10 v10.26.0 @@ -48,13 +48,13 @@ require ( github.com/xdg-go/stringprep v1.0.4 // indirect github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect go.mongodb.org/mongo-driver v1.17.3 // indirect - golang.org/x/arch v0.16.0 // indirect - golang.org/x/crypto v0.37.0 // indirect - golang.org/x/net v0.39.0 // indirect - golang.org/x/sync v0.13.0 // indirect - golang.org/x/sys v0.32.0 // indirect - golang.org/x/term v0.31.0 // indirect - golang.org/x/text v0.24.0 // indirect + golang.org/x/arch v0.17.0 // indirect + golang.org/x/crypto v0.38.0 // indirect + golang.org/x/net v0.40.0 // indirect + golang.org/x/sync v0.14.0 // indirect + golang.org/x/sys v0.33.0 // indirect + golang.org/x/term v0.32.0 // indirect + golang.org/x/text v0.25.0 // indirect google.golang.org/protobuf v1.36.6 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect modernc.org/libc v1.37.6 // indirect diff --git a/scnserver/go.sum b/scnserver/go.sum index 1bf5e8f..2ff940f 100644 --- a/scnserver/go.sum +++ b/scnserver/go.sum @@ -1,7 +1,7 @@ filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= -git.blackforestbytes.com/BlackForestBytes/goext v0.0.572 h1:NALJ4KKkrRZcNJNsmGrUsjFdOclHSA/KyB6f94QV43k= -git.blackforestbytes.com/BlackForestBytes/goext v0.0.572/go.mod h1:C4mXq6MwC919jRHjN5IM++qGy6wmvzJZyz30nf285MU= +git.blackforestbytes.com/BlackForestBytes/goext v0.0.575 h1:scgvSaNZQ+C0pMbfPbsxF/hE3+rgLotHe+6Lbl5+mJU= +git.blackforestbytes.com/BlackForestBytes/goext v0.0.575/go.mod h1:Rj+bq1jLkgvXYe2sthg5UtXHf22nFvmTLeo+54fbYq8= github.com/bytedance/sonic v1.13.2 h1:8/H1FempDZqC4VqjptGo14QQlJx8VdZJegxs6wwfqpQ= github.com/bytedance/sonic v1.13.2/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4= github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= @@ -115,23 +115,23 @@ github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfS github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.mongodb.org/mongo-driver v1.17.3 h1:TQyXhnsWfWtgAhMtOgtYHMTkZIfBTpMTsMnd9ZBeHxQ= go.mongodb.org/mongo-driver v1.17.3/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ= -golang.org/x/arch v0.16.0 h1:foMtLTdyOmIniqWCHjY6+JxuC54XP1fDwx4N0ASyW+U= -golang.org/x/arch v0.16.0/go.mod h1:JmwW7aLIoRUKgaTzhkiEFxvcEiQGyOg9BMonBJUS7EE= +golang.org/x/arch v0.17.0 h1:4O3dfLzd+lQewptAHqjewQZQDyEdejz3VwgeYwkZneU= +golang.org/x/arch v0.17.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= -golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= +golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= +golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= -golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= +golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= +golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= -golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= +golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -140,18 +140,18 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= -golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o= -golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw= +golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg= +golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= -golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= +golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= +golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= diff --git a/scnserver/logic/application.go b/scnserver/logic/application.go index 548481a..71cc319 100644 --- a/scnserver/logic/application.go +++ b/scnserver/logic/application.go @@ -10,6 +10,8 @@ import ( "context" "errors" "fmt" + "git.blackforestbytes.com/BlackForestBytes/goext/dataext" + "git.blackforestbytes.com/BlackForestBytes/goext/exerr" "git.blackforestbytes.com/BlackForestBytes/goext/ginext" "git.blackforestbytes.com/BlackForestBytes/goext/syncext" "github.com/rs/zerolog/log" @@ -23,26 +25,28 @@ import ( ) type Application struct { - Config scn.Config - Gin *ginext.GinWrapper - Database *DBPool - Pusher push.NotificationClient - AndroidPublisher google.AndroidPublisherClient - Jobs []Job - stopChan chan bool - Port string - IsRunning *syncext.AtomicBool - RequestLogQueue chan models.RequestLog - MainDatabaseLock golock.RWMutex + Config scn.Config + Gin *ginext.GinWrapper + Database *DBPool + Pusher push.NotificationClient + AndroidPublisher google.AndroidPublisherClient + Jobs []Job + stopChan chan bool + Port string + IsRunning *syncext.AtomicBool + RequestLogQueue chan models.RequestLog + MainDatabaseLock golock.RWMutex + keyTokenLastUsedDCI *dataext.SyncMap[models.KeyTokenID, *dataext.DelayedCombiningInvoker] } func NewApp(db *DBPool) *Application { return &Application{ - Database: db, - stopChan: make(chan bool), - IsRunning: syncext.NewAtomicBool(false), - RequestLogQueue: make(chan models.RequestLog, 8192), - MainDatabaseLock: golock.NewCASMutex(), + Database: db, + stopChan: make(chan bool), + IsRunning: syncext.NewAtomicBool(false), + RequestLogQueue: make(chan models.RequestLog, 8192), + MainDatabaseLock: golock.NewCASMutex(), + keyTokenLastUsedDCI: dataext.NewSyncMap[models.KeyTokenID, *dataext.DelayedCombiningInvoker](), } } @@ -117,6 +121,7 @@ func (app *Application) Run() { } else { log.Info().Msg("Manually stopped HTTP-Server") } + } // ================== STOP JOBS ================== @@ -125,6 +130,25 @@ func (app *Application) Run() { job.Stop() } + // ================== STOP Keytoken DCI ================== + + func() { + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + + for i, dci := range app.keyTokenLastUsedDCI.GetAllValues() { + running := dci.ExecuteNow() + if running { + err := dci.WaitForCompletion(ctx) + if err != nil { + log.Err(err).Msg("Failed to wait for keytoken dci job") + } else { + log.Info().Msg(fmt.Sprintf("Succesfully waited for finishing of KeyToken-DCI job [%d]", i)) + } + } + } + }() + // ================== STOP DB ================== { @@ -223,11 +247,7 @@ func (app *Application) getPermissions(ctx db.TxContext, hdr string) (models.Per } if tok != nil { - - err = app.Database.Primary.UpdateKeyTokenLastUsed(ctx, tok.KeyTokenID) - if err != nil { - return models.PermissionSet{}, err - } + go app.updateKeyTokenLastUsed(tok.KeyTokenID) return models.PermissionSet{Token: tok}, nil } @@ -235,6 +255,37 @@ func (app *Application) getPermissions(ctx db.TxContext, hdr string) (models.Per return models.NewEmptyPermissions(), nil } +func (app *Application) updateKeyTokenLastUsed(keyTokenID models.KeyTokenID) { + app.keyTokenLastUsedDCI.GetAndSetIfNotContainsFunc(keyTokenID, func() *dataext.DelayedCombiningInvoker { + return dataext.NewDelayedCombiningInvoker(func() { + islock := app.MainDatabaseLock.TryLockWithTimeout(10 * time.Second) + if !islock { + exerr.New(exerr.TypeInternal, "Failed to lock {MainDatabaseLock} [rw] for {updateKeyTokenLastUsed}").Id("KeyTokenID", keyTokenID).Print() + return + } + defer app.MainDatabaseLock.Unlock() + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + _, _ = simplectx.Run(ctx, func(ctx db.TxContext) (any, error) { + err := app.Database.Primary.UpdateKeyTokenLastUsed(ctx, keyTokenID) + if err != nil { + exerr.Wrap(err, "Failed to update keytoken last used").Id("KeyTokenID", keyTokenID).Print() + } + return nil, nil + }) + }, 5*time.Second, 30*time.Second) + }).Request() + + if app.keyTokenLastUsedDCI.Count() > 1024 { + rmCount := app.keyTokenLastUsedDCI.DeleteIf(func(key models.KeyTokenID, val *dataext.DelayedCombiningInvoker) bool { + return !val.HasPendingRequests() + }) + log.Debug().Msg(fmt.Sprintf("Cleaned up %d old keytoken-DCI jobs", rmCount)) + } +} + func (app *Application) GetOrCreateChannel(ctx *AppContext, userid models.UserID, displayChanName string, intChanName string) (models.Channel, error) { existingChan, err := app.Database.Primary.GetChannelByName(ctx, userid, intChanName) if err != nil { diff --git a/scnserver/logic/request.go b/scnserver/logic/request.go index b428cd5..db4d517 100644 --- a/scnserver/logic/request.go +++ b/scnserver/logic/request.go @@ -55,7 +55,6 @@ func (app *Application) DoRequest(gectx *ginext.AppContext, g *gin.Context, lock defer app.MainDatabaseLock.RUnlock() } else if lockmode == models.TLockReadWrite { - islock := app.MainDatabaseLock.TryLockWithTimeout(dl.Sub(time.Now())) if !islock { return ginresp.APIError(g, 500, apierr.INTERNAL_EXCEPTION, "Failed to lock {MainDatabaseLock} [rw]", nil) diff --git a/scnserver/models/enums_gen.go b/scnserver/models/enums_gen.go index 649c1f7..501da2c 100644 --- a/scnserver/models/enums_gen.go +++ b/scnserver/models/enums_gen.go @@ -5,7 +5,7 @@ package models import "git.blackforestbytes.com/BlackForestBytes/goext/langext" import "git.blackforestbytes.com/BlackForestBytes/goext/enums" -const ChecksumEnumGenerator = "3cf637627b9baaf975ce4757ce1e1f596bee3874e4969d05e1973b33c930a1fb" // GoExtVersion: 0.0.572 +const ChecksumEnumGenerator = "1e8100b30bf6c946a1dfdc273b41efcaa91f33eab2bda12ce5dfa853741ac90b" // GoExtVersion: 0.0.575 // ================================ ClientType ================================ // diff --git a/scnserver/models/ids_gen.go b/scnserver/models/ids_gen.go index 1ed73ab..4e876ec 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 = "3cf637627b9baaf975ce4757ce1e1f596bee3874e4969d05e1973b33c930a1fb" // GoExtVersion: 0.0.572 +const ChecksumCharsetIDGenerator = "1e8100b30bf6c946a1dfdc273b41efcaa91f33eab2bda12ce5dfa853741ac90b" // GoExtVersion: 0.0.575 const idlen = 24 diff --git a/scnserver/swagger/swagger.json b/scnserver/swagger/swagger.json index a836676..9b5633f 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" } ], @@ -2812,37 +2860,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" }, { @@ -2855,37 +2927,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" } ], @@ -2934,72 +3030,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" } ], @@ -3514,26 +3658,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" } } }, @@ -3562,7 +3726,7 @@ "type": "integer" }, "scn_msg_id": { - "type": "integer" + "type": "string" }, "success": { "type": "boolean" diff --git a/scnserver/swagger/swagger.yaml b/scnserver/swagger/swagger.yaml index 7ed4140..9c64061 100644 --- a/scnserver/swagger/swagger.yaml +++ b/scnserver/swagger/swagger.yaml @@ -340,19 +340,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: @@ -372,7 +389,7 @@ definitions: quota_max: type: integer scn_msg_id: - type: integer + type: string success: type: boolean suppress_send: @@ -852,52 +869,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": @@ -2762,52 +2817,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": @@ -2840,47 +2933,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/keytoken_test.go b/scnserver/test/keytoken_test.go index 614e656..2c51de2 100644 --- a/scnserver/test/keytoken_test.go +++ b/scnserver/test/keytoken_test.go @@ -6,7 +6,9 @@ import ( "fmt" "git.blackforestbytes.com/BlackForestBytes/goext/langext" "github.com/gin-gonic/gin" + "strings" "testing" + "time" ) func TestTokenKeys(t *testing.T) { @@ -722,3 +724,50 @@ func TestTokenKeysGetCurrent(t *testing.T) { } } + +func TestKeyTokenLastUsed(t *testing.T) { + ws, baseUrl, stop := tt.StartSimpleWebserver(t) + defer stop() + + data := tt.InitSingleData(t, ws) + + type keyobj struct { + AllChannels bool `json:"all_channels"` + Channels []string `json:"channels"` + KeytokenId string `json:"keytoken_id"` + MessagesSent int `json:"messages_sent"` + Name string `json:"name"` + OwnerUserId string `json:"owner_user_id"` + Permissions string `json:"permissions"` + TimestampLastUsed *string `json:"timestamp_lastused"` + } + + rkey1 := tt.RequestAuthGet[keyobj](t, data.ReadKey, baseUrl, fmt.Sprintf("/api/v2/users/%s/keys/current", data.UID)) + readKeyID := rkey1.KeytokenId + + time.Sleep(6 * time.Second) // wait for first DCI + + rkey2 := tt.RequestAuthGet[keyobj](t, data.AdminKey, baseUrl, fmt.Sprintf("/api/v2/users/%s/keys/%s", data.UID, readKeyID)) + + tt.AssertNotNil(t, "timestamp_lastused", rkey2.TimestampLastUsed) + + rkey3 := tt.RequestAuthGet[keyobj](t, data.AdminKey, baseUrl, fmt.Sprintf("/api/v2/users/%s/keys/%s", data.UID, readKeyID)) + + time.Sleep(6 * time.Second) // wait for first DCI + + tt.AssertEqual(t, "timestamp_lastused", *rkey2.TimestampLastUsed, *rkey3.TimestampLastUsed) // should be unchanged, we did nothing with the readkey + + tt.RequestAuthGetRaw(t, data.ReadKey, baseUrl, fmt.Sprintf("/api/v2/users/%s", data.UID)) + + rkey4 := tt.RequestAuthGet[keyobj](t, data.AdminKey, baseUrl, fmt.Sprintf("/api/v2/users/%s/keys/%s", data.UID, readKeyID)) + + tt.AssertEqual(t, "timestamp_lastused", *rkey2.TimestampLastUsed, *rkey4.TimestampLastUsed) // still the same - the DCI is pending + + time.Sleep(6 * time.Second) // wait for second DCI + + rkey5 := tt.RequestAuthGet[keyobj](t, data.AdminKey, baseUrl, fmt.Sprintf("/api/v2/users/%s/keys/%s", data.UID, readKeyID)) + + tt.AssertNotEqual(t, "timestamp_lastused", *rkey2.TimestampLastUsed, *rkey5.TimestampLastUsed) // did + tt.AssertEqual(t, "timestamp_lastused", -1, strings.Compare(*rkey2.TimestampLastUsed, *rkey5.TimestampLastUsed)) + +} diff --git a/scnserver/test/util/webserver.go b/scnserver/test/util/webserver.go index b99501b..6889068 100644 --- a/scnserver/test/util/webserver.go +++ b/scnserver/test/util/webserver.go @@ -93,7 +93,7 @@ func StartSimpleWebserver(t *testing.T) (*logic.Application, string, func()) { AllowCors: &scn.Conf.Cors, GinDebug: &scn.Conf.GinDebug, BufferBody: langext.PTrue, - Timeout: langext.Ptr(time.Duration(int64(scn.Conf.RequestTimeout) * int64(scn.Conf.RequestMaxRetry))), + Timeout: langext.Ptr(time.Duration(int64(scn.Conf.RequestTimeout) * int64(scn.Conf.RequestMaxRetry+1))), BuildRequestBindError: logic.BuildGinRequestError, }) @@ -221,7 +221,7 @@ func CreateTestConfig(t *testing.T, dbfile1 string, dbfile2 string, dbfile3 stri conf.DBMain.ConnMaxIdleTime = 1 * time.Second conf.DBLogs.ConnMaxIdleTime = 1 * time.Second conf.DBRequests.ConnMaxIdleTime = 1 * time.Second - conf.RequestMaxRetry = 32 + conf.RequestMaxRetry = 0 // 32 // normal server does retries - but should not _have_ to, so in out test we enfore no retrying... conf.RequestRetrySleep = 100 * time.Millisecond conf.ReturnRawErrors = true conf.DummyFirebase = true