diff --git a/scnserver/.gitignore b/scnserver/.gitignore index f9286da..18cb456 100644 --- a/scnserver/.gitignore +++ b/scnserver/.gitignore @@ -8,6 +8,10 @@ DOCKER_GIT_INFO scn_export.dat scn_export.json +identifier.sqlite + +.idea/dataSources.xml + ############## diff --git a/scnserver/api/handler/compat.go b/scnserver/api/handler/compat.go index 04382f8..108aa4e 100644 --- a/scnserver/api/handler/compat.go +++ b/scnserver/api/handler/compat.go @@ -71,7 +71,7 @@ func (h MessageHandler) SendMessageCompat(g *gin.Context) ginresp.HTTPResponse { var f combined var q combined - ctx, errResp := h.app.StartRequest(g, nil, &q, nil, &f) + ctx, errResp := h.app.StartRequest(g, nil, &q, nil, &f, logic.RequestOptions{IgnoreWrongContentType: true}) if errResp != nil { return *errResp } @@ -159,7 +159,7 @@ func (h CompatHandler) Register(g *gin.Context) ginresp.HTTPResponse { var datq query var datb query - ctx, errResp := h.app.StartRequest(g, nil, &datq, nil, &datb) + ctx, errResp := h.app.StartRequest(g, nil, &datq, nil, &datb, logic.RequestOptions{IgnoreWrongContentType: true}) if errResp != nil { return *errResp } @@ -278,7 +278,7 @@ func (h CompatHandler) Info(g *gin.Context) ginresp.HTTPResponse { var datq query var datb query - ctx, errResp := h.app.StartRequest(g, nil, &datq, nil, &datb) + ctx, errResp := h.app.StartRequest(g, nil, &datq, nil, &datb, logic.RequestOptions{IgnoreWrongContentType: true}) if errResp != nil { return *errResp } @@ -373,7 +373,7 @@ func (h CompatHandler) Ack(g *gin.Context) ginresp.HTTPResponse { var datq query var datb query - ctx, errResp := h.app.StartRequest(g, nil, &datq, nil, &datb) + ctx, errResp := h.app.StartRequest(g, nil, &datq, nil, &datb, logic.RequestOptions{IgnoreWrongContentType: true}) if errResp != nil { return *errResp } @@ -478,7 +478,7 @@ func (h CompatHandler) Requery(g *gin.Context) ginresp.HTTPResponse { var datq query var datb query - ctx, errResp := h.app.StartRequest(g, nil, &datq, nil, &datb) + ctx, errResp := h.app.StartRequest(g, nil, &datq, nil, &datb, logic.RequestOptions{IgnoreWrongContentType: true}) if errResp != nil { return *errResp } @@ -595,7 +595,7 @@ func (h CompatHandler) Update(g *gin.Context) ginresp.HTTPResponse { var datq query var datb query - ctx, errResp := h.app.StartRequest(g, nil, &datq, nil, &datb) + ctx, errResp := h.app.StartRequest(g, nil, &datq, nil, &datb, logic.RequestOptions{IgnoreWrongContentType: true}) if errResp != nil { return *errResp } @@ -722,7 +722,7 @@ func (h CompatHandler) Expand(g *gin.Context) ginresp.HTTPResponse { var datq query var datb query - ctx, errResp := h.app.StartRequest(g, nil, &datq, nil, &datb) + ctx, errResp := h.app.StartRequest(g, nil, &datq, nil, &datb, logic.RequestOptions{IgnoreWrongContentType: true}) if errResp != nil { return *errResp } @@ -838,7 +838,7 @@ func (h CompatHandler) Upgrade(g *gin.Context) ginresp.HTTPResponse { var datq query var datb query - ctx, errResp := h.app.StartRequest(g, nil, &datq, nil, &datb) + ctx, errResp := h.app.StartRequest(g, nil, &datq, nil, &datb, logic.RequestOptions{IgnoreWrongContentType: true}) if errResp != nil { return *errResp } diff --git a/scnserver/api/handler/message.go b/scnserver/api/handler/message.go index c22eb33..07f4aa4 100644 --- a/scnserver/api/handler/message.go +++ b/scnserver/api/handler/message.go @@ -85,7 +85,7 @@ func (h MessageHandler) SendMessage(g *gin.Context) ginresp.HTTPResponse { var b combined var q combined var f combined - ctx, errResp := h.app.StartRequest(g, nil, &q, &b, &f) + ctx, errResp := h.app.StartRequest(g, nil, &q, &b, &f, logic.RequestOptions{IgnoreWrongContentType: true}) if errResp != nil { return *errResp } diff --git a/scnserver/db/impl/primary/channels.go b/scnserver/db/impl/primary/channels.go index 39ecdce..38f519a 100644 --- a/scnserver/db/impl/primary/channels.go +++ b/scnserver/db/impl/primary/channels.go @@ -206,8 +206,7 @@ func (db *Database) IncChannelMessageCounter(ctx TxContext, channel models.Chann 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, + _, err = tx.Exec(ctx, "UPDATE channels SET messages_sent = messages_sent+1, timestamp_lastsent = :ts WHERE channel_id = :cid", sq.PP{ "cid": time2DB(time.Now()), "ts": channel.ChannelID, }) diff --git a/scnserver/db/impl/primary/keytokens.go b/scnserver/db/impl/primary/keytokens.go index ce19db2..aad021d 100644 --- a/scnserver/db/impl/primary/keytokens.go +++ b/scnserver/db/impl/primary/keytokens.go @@ -187,7 +187,7 @@ func (db *Database) IncKeyTokenMessageCounter(ctx TxContext, keyTokenid models.K return err } - _, err = tx.Exec(ctx, "UPDATE keytokens SET messages_sent = messages_sent + 1, timestamp_lastused = :ts WHERE keytoken_id = :tid", sq.PP{ + _, err = tx.Exec(ctx, "UPDATE keytokens SET messages_sent = messages_sent+1, timestamp_lastused = :ts WHERE keytoken_id = :tid", sq.PP{ "ts": time2DB(time.Now()), "tid": keyTokenid, }) diff --git a/scnserver/db/impl/primary/users.go b/scnserver/db/impl/primary/users.go index 32a5e2f..81a32c6 100644 --- a/scnserver/db/impl/primary/users.go +++ b/scnserver/db/impl/primary/users.go @@ -110,9 +110,8 @@ func (db *Database) IncUserMessageCounter(ctx TxContext, user models.User) error 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{ + _, err = tx.Exec(ctx, "UPDATE users SET timestamp_lastsent = :ts, messages_sent = messages_sent+1, 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, diff --git a/scnserver/logic/application.go b/scnserver/logic/application.go index 113786c..8427f3e 100644 --- a/scnserver/logic/application.go +++ b/scnserver/logic/application.go @@ -225,7 +225,13 @@ func (app *Application) Migrate() error { return app.Database.Migrate(ctx) } -func (app *Application) StartRequest(g *gin.Context, uri any, query any, body any, form any) (*AppContext, *ginresp.HTTPResponse) { +type RequestOptions struct { + IgnoreWrongContentType bool +} + +func (app *Application) StartRequest(g *gin.Context, uri any, query any, body any, form any, opts ...RequestOptions) (*AppContext, *ginresp.HTTPResponse) { + + ignoreWrongContentType := langext.ArrAny(opts, func(o RequestOptions) bool { return o.IgnoreWrongContentType }) if uri != nil { if err := g.ShouldBindUri(uri); err != nil { @@ -245,7 +251,9 @@ func (app *Application) StartRequest(g *gin.Context, uri any, query any, body an return nil, langext.Ptr(ginresp.APIError(g, 400, apierr.BINDFAIL_BODY_PARAM, "Failed to read body", err)) } } else { - return nil, langext.Ptr(ginresp.APIError(g, 400, apierr.BINDFAIL_BODY_PARAM, "missing JSON body", nil)) + if !ignoreWrongContentType { + return nil, langext.Ptr(ginresp.APIError(g, 400, apierr.BINDFAIL_BODY_PARAM, "missing JSON body", nil)) + } } } @@ -255,7 +263,9 @@ func (app *Application) StartRequest(g *gin.Context, uri any, query any, body an return nil, langext.Ptr(ginresp.APIError(g, 400, apierr.BINDFAIL_BODY_PARAM, "Failed to read multipart-form", err)) } } else { - return nil, langext.Ptr(ginresp.APIError(g, 400, apierr.BINDFAIL_BODY_PARAM, "missing form body", nil)) + if !ignoreWrongContentType { + return nil, langext.Ptr(ginresp.APIError(g, 400, apierr.BINDFAIL_BODY_PARAM, "missing form body", nil)) + } } } diff --git a/scnserver/test/channel_test.go b/scnserver/test/channel_test.go index e8c3dd6..163ded2 100644 --- a/scnserver/test/channel_test.go +++ b/scnserver/test/channel_test.go @@ -1090,3 +1090,120 @@ func TestListChannelMessagesOfRevokedConfirmation(t *testing.T) { tt.RequestAuthGetShouldFail(t, data1.AdminKey, baseUrl, fmt.Sprintf("/api/v2/users/%s/channels/%s/messages", data1.UID, chan1.ChannelId), 401, apierr.USER_AUTH_FAILED) } + +func TestChannelMessageCounter(t *testing.T) { //TODO this fails! + _, baseUrl, stop := tt.StartSimpleWebserver(t) + defer stop() + + r0 := tt.RequestPost[gin.H](t, baseUrl, "/api/v2/users", gin.H{ + "agent_model": "DUMMY_PHONE", + "agent_version": "4X", + "client_type": "ANDROID", + "fcm_token": "DUMMY_FCM", + }) + + uid := r0["user_id"].(string) + admintok := r0["admin_key"].(string) + + type chanobj struct { + ChannelId string `json:"channel_id"` + DescriptionName string `json:"description_name"` + DisplayName string `json:"display_name"` + InternalName string `json:"internal_name"` + MessagesSent int `json:"messages_sent"` + OwnerUserId string `json:"owner_user_id"` + SubscribeKey string `json:"subscribe_key"` + Subscription struct { + ChannelId string `json:"channel_id"` + ChannelInternalName string `json:"channel_internal_name"` + ChannelOwnerUserId string `json:"channel_owner_user_id"` + Confirmed bool `json:"confirmed"` + SubscriberUserId string `json:"subscriber_user_id"` + SubscriptionId string `json:"subscription_id"` + TimestampCreated string `json:"timestamp_created"` + } `json:"subscription"` + TimestampCreated string `json:"timestamp_created"` + TimestampLastsent string `json:"timestamp_lastsent"` + } + + type chanlist struct { + Channels []chanobj `json:"channels"` + } + + tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{ + "key": admintok, + "user_id": uid, + "title": tt.ShortLipsum(1001, 1), + }) + + chan0 := tt.RequestAuthGet[chanlist](t, admintok, baseUrl, fmt.Sprintf("/api/v2/users/%s/channels", uid)).Channels[0] + + chan1 := tt.RequestAuthPost[chanobj](t, admintok, baseUrl, fmt.Sprintf("/api/v2/users/%s/channels", uid), gin.H{ + "name": "Chan1", + }) + + chan2 := tt.RequestAuthPost[chanobj](t, admintok, baseUrl, fmt.Sprintf("/api/v2/users/%s/channels", uid), gin.H{ + "name": "Chan2", + }) + + assertCounter := func(c0 int, c1 int, c2 int) { + r1 := tt.RequestAuthGet[gin.H](t, admintok, baseUrl, "/api/v2/users/"+uid+"/channels/"+chan0.ChannelId) + tt.AssertStrRepEqual(t, "c0.messages_sent", c0, r1["messages_sent"]) + + r2 := tt.RequestAuthGet[gin.H](t, admintok, baseUrl, "/api/v2/users/"+uid+"/channels/"+chan1.ChannelId) + tt.AssertStrRepEqual(t, "c1.messages_sent", c1, r2["messages_sent"]) + + r3 := tt.RequestAuthGet[gin.H](t, admintok, baseUrl, "/api/v2/users/"+uid+"/channels/"+chan2.ChannelId) + tt.AssertStrRepEqual(t, "c2.messages_sent", c2, r3["messages_sent"]) + } + + assertCounter(1, 0, 0) + + tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{ + "key": admintok, + "user_id": uid, + "title": tt.ShortLipsum(1002, 1), + }) + + assertCounter(2, 0, 0) + + tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{ + "key": admintok, + "user_id": uid, + "channel": "Chan1", + "title": tt.ShortLipsum(1003, 1), + }) + tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{ + "key": admintok, + "user_id": uid, + "channel": "Chan2", + "title": tt.ShortLipsum(1004, 1), + }) + tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{ + "key": admintok, + "user_id": uid, + "channel": "Chan2", + "title": tt.ShortLipsum(1005, 1), + }) + + assertCounter(5, 1, 2) + assertCounter(5, 1, 2) + + tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{ + "key": admintok, + "user_id": uid, + "channel": "Chan2", + "title": tt.ShortLipsum(1004, 1), + }) + + assertCounter(5, 1, 3) + + tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{ + "key": admintok, + "user_id": uid, + "title": tt.ShortLipsum(1002, 1), + }) + + assertCounter(6, 1, 3) + +} diff --git a/scnserver/test/keytoken_test.go b/scnserver/test/keytoken_test.go index 4a2a972..b03a280 100644 --- a/scnserver/test/keytoken_test.go +++ b/scnserver/test/keytoken_test.go @@ -5,6 +5,7 @@ import ( tt "blackforestbytes.com/simplecloudnotifier/test/util" "fmt" "github.com/gin-gonic/gin" + "gogs.mikescher.com/BlackForestBytes/goext/langext" "testing" ) @@ -491,3 +492,117 @@ func TestTokenKeysPermissions(t *testing.T) { }, 401, apierr.USER_AUTH_FAILED) // no send perm } + +func TestTokenKeysMessageCounter(t *testing.T) { + _, baseUrl, stop := tt.StartSimpleWebserver(t) + defer stop() + + r0 := tt.RequestPost[gin.H](t, baseUrl, "/api/v2/users", gin.H{ + "agent_model": "DUMMY_PHONE", + "agent_version": "4X", + "client_type": "ANDROID", + "fcm_token": "DUMMY_FCM", + }) + + 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"` + Token string `json:"token"` // only in create + } + type keylist struct { + Keys []keyobj `json:"keys"` + } + + uid := r0["user_id"].(string) + admintok := r0["admin_key"].(string) + sendtok := r0["send_key"].(string) + + klist := tt.RequestAuthGet[keylist](t, admintok, baseUrl, fmt.Sprintf("/api/v2/users/%s/keys", uid)) + tt.AssertEqual(t, "len(keys)", 3, len(klist.Keys)) + + admintokid := langext.ArrFirstOrNil(klist.Keys, func(v keyobj) bool { return v.Name == "AdminKey (default)" }).KeytokenId + sendtokid := langext.ArrFirstOrNil(klist.Keys, func(v keyobj) bool { return v.Name == "SendKey (default)" }).KeytokenId + readtokid := langext.ArrFirstOrNil(klist.Keys, func(v keyobj) bool { return v.Name == "ReadKey (default)" }).KeytokenId + + assertCounter := func(c0 int, c1 int, c2 int) { + r1 := tt.RequestAuthGet[gin.H](t, admintok, baseUrl, "/api/v2/users/"+uid+"/keys/"+admintokid) + tt.AssertStrRepEqual(t, "c0.messages_sent", c0, r1["messages_sent"]) + + r2 := tt.RequestAuthGet[gin.H](t, admintok, baseUrl, "/api/v2/users/"+uid+"/keys/"+sendtokid) + tt.AssertStrRepEqual(t, "c1.messages_sent", c1, r2["messages_sent"]) + + r3 := tt.RequestAuthGet[gin.H](t, admintok, baseUrl, "/api/v2/users/"+uid+"/keys/"+readtokid) + tt.AssertStrRepEqual(t, "c2.messages_sent", c2, r3["messages_sent"]) + } + + assertCounter(0, 0, 0) + + tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{ + "key": admintok, + "user_id": uid, + "title": tt.ShortLipsum(1001, 1), + }) + + assertCounter(1, 0, 0) + + tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{ + "key": admintok, + "user_id": uid, + "title": tt.ShortLipsum(1002, 1), + }) + + assertCounter(2, 0, 0) + + tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{ + "key": sendtok, + "user_id": uid, + "title": tt.ShortLipsum(1002, 1), + }) + + assertCounter(2, 1, 0) + + tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{ + "key": sendtok, + "user_id": uid, + "channel": "Chan1", + "title": tt.ShortLipsum(1003, 1), + }) + tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{ + "key": sendtok, + "user_id": uid, + "channel": "Chan2", + "title": tt.ShortLipsum(1004, 1), + }) + tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{ + "key": sendtok, + "user_id": uid, + "channel": "Chan2", + "title": tt.ShortLipsum(1005, 1), + }) + + assertCounter(2, 4, 0) + assertCounter(2, 4, 0) + + tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{ + "key": admintok, + "user_id": uid, + "channel": "Chan2", + "title": tt.ShortLipsum(1004, 1), + }) + + assertCounter(3, 4, 0) + + tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{ + "key": admintok, + "user_id": uid, + "title": tt.ShortLipsum(1002, 1), + }) + + assertCounter(4, 4, 0) + +} diff --git a/scnserver/test/send_test.go b/scnserver/test/send_test.go index 682f0fb..7d1f3bf 100644 --- a/scnserver/test/send_test.go +++ b/scnserver/test/send_test.go @@ -1790,7 +1790,3 @@ func TestSendWithPermissionSendKey(t *testing.T) { func TestSendDeliveryRetry(t *testing.T) { t.SkipNow() //TODO } - -//TODO check message_counter + last_sent in channel - -//TODO check message_counter + last_sent in user diff --git a/scnserver/test/user_test.go b/scnserver/test/user_test.go index 49eeddd..67fe515 100644 --- a/scnserver/test/user_test.go +++ b/scnserver/test/user_test.go @@ -370,3 +370,61 @@ func TestReuseProToken(t *testing.T) { } } + +func TestUserMessageCounter(t *testing.T) { + _, baseUrl, stop := tt.StartSimpleWebserver(t) + defer stop() + + r0 := tt.RequestPost[gin.H](t, baseUrl, "/api/v2/users", gin.H{ + "agent_model": "DUMMY_PHONE", + "agent_version": "4X", + "client_type": "ANDROID", + "fcm_token": "DUMMY_FCM", + }) + + uid := r0["user_id"].(string) + admintok := r0["admin_key"].(string) + + assertCounter := func(c int) { + r1 := tt.RequestAuthGet[gin.H](t, admintok, baseUrl, "/api/v2/users/"+uid) + tt.AssertStrRepEqual(t, "messages_sent", c, r1["messages_sent"]) + tt.AssertStrRepEqual(t, "quota_used", c, r1["quota_used"]) + } + + assertCounter(0) + + tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{ + "key": admintok, + "user_id": uid, + "title": tt.ShortLipsum(1001, 1), + }) + + assertCounter(1) + assertCounter(1) + + tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{ + "key": admintok, + "user_id": uid, + "title": tt.ShortLipsum(1002, 1), + }) + + assertCounter(2) + + tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{ + "key": admintok, + "user_id": uid, + "title": tt.ShortLipsum(1003, 1), + }) + tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{ + "key": admintok, + "user_id": uid, + "title": tt.ShortLipsum(1004, 1), + }) + tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{ + "key": admintok, + "user_id": uid, + "title": tt.ShortLipsum(1005, 1), + }) + + assertCounter(5) +} diff --git a/scnserver/test/util/factory.go b/scnserver/test/util/factory.go index 104cda3..fa5a7da 100644 --- a/scnserver/test/util/factory.go +++ b/scnserver/test/util/factory.go @@ -554,6 +554,9 @@ func Lipsum(seed int64, paracount int) string { return loremipsum.NewWithSeed(seed).Paragraphs(paracount) } +func ShortLipsum(seed int64, wcount int) string { + return loremipsum.NewWithSeed(seed).Words(wcount) +} func ShortLipsum0(wcount int) string { return loremipsum.NewWithSeed(0).Words(wcount) }