diff --git a/scnserver/TODO.md b/scnserver/TODO.md index d8ae446..056b8ee 100644 --- a/scnserver/TODO.md +++ b/scnserver/TODO.md @@ -18,15 +18,6 @@ - deploy - - diff my currently used scnsend script vs the one in the docs here - -- (?) use str-ids (hide counts and prevents wrong-joins) -> see psycho - -> ensre that all queries that return multiple are properly ordered - -> how does it work with existing data? - -> do i care, there are only 2 active users... (are there?) - - - convert existing user-ids on compat /send endpoint - - error logging as goroutine, gets all errors via channel, (channel buffered - nonblocking send, second channel that gets a message when sender failed ) (then all errors end up in _second_ sqlite table) @@ -47,10 +38,6 @@ - ios purchase verification - - return channel as "[..] asdf" in compat methods (mark clients as compat and send compat FB to them...) - (then we can replace the old server without switching phone clients) - (still needs switching of the send-script) - - move to KeyToken model * [X] User can have multiple keys with different permissions * [X] compat simply uses default-keys @@ -62,12 +49,14 @@ - We no longer have a route to reshuffle all keys (previously in updateUser), add a /user/:uid/keys/reset ? Would delete all existing keys and create 3 new ones? -- TODO comments +- TODO-comments #### PERSONAL - in my script: use `srvname` for sendername + - switch send script everywhere (we can use the new server, but we need to send correct channels) + #### UNSURE - (?) default-priority for channels diff --git a/scnserver/api/ginresp/wrapper.go b/scnserver/api/ginresp/wrapper.go index 8d56c22..4572c7d 100644 --- a/scnserver/api/ginresp/wrapper.go +++ b/scnserver/api/ginresp/wrapper.go @@ -11,6 +11,7 @@ import ( "github.com/rs/zerolog/log" "gogs.mikescher.com/BlackForestBytes/goext/dataext" "gogs.mikescher.com/BlackForestBytes/goext/langext" + "runtime/debug" "time" ) @@ -37,10 +38,11 @@ func Wrap(rlacc RequestLogAcceptor, fn WHandlerFunc) gin.HandlerFunc { for ctr := 1; ; ctr++ { - wrap, panicObj := callPanicSafe(fn, g) + wrap, stackTrace, panicObj := callPanicSafe(fn, g) if panicObj != nil { log.Error().Interface("panicObj", panicObj).Msg("Panic occured (in gin handler)") - wrap = APIError(g, 500, apierr.PANIC, "A panic occured in the HTTP handler", errors.New(fmt.Sprintf("%+v", panicObj))) + log.Error().Msg(stackTrace) + wrap = APIError(g, 500, apierr.PANIC, "A panic occured in the HTTP handler", errors.New(fmt.Sprintf("%+v\n\n@:\n%s", panicObj, stackTrace))) } if g.Writer.Written() { @@ -138,16 +140,17 @@ func createRequestLog(g *gin.Context, t0 time.Time, ctr int, resp HTTPResponse, } } -func callPanicSafe(fn WHandlerFunc, g *gin.Context) (res HTTPResponse, panicObj any) { +func callPanicSafe(fn WHandlerFunc, g *gin.Context) (res HTTPResponse, stackTrace string, panicObj any) { defer func() { if rec := recover(); rec != nil { res = nil + stackTrace = string(debug.Stack()) panicObj = rec } }() res = fn(g) - return res, nil + return res, "", nil } func resetBody(g *gin.Context) error { diff --git a/scnserver/api/handler/compat.go b/scnserver/api/handler/compat.go index f3244ee..80e281d 100644 --- a/scnserver/api/handler/compat.go +++ b/scnserver/api/handler/compat.go @@ -9,6 +9,7 @@ import ( "blackforestbytes.com/simplecloudnotifier/logic" "blackforestbytes.com/simplecloudnotifier/models" "database/sql" + "fmt" "github.com/gin-gonic/gin" "gogs.mikescher.com/BlackForestBytes/goext/dataext" "gogs.mikescher.com/BlackForestBytes/goext/langext" @@ -540,7 +541,7 @@ func (h CompatHandler) Requery(g *gin.Context) ginresp.HTTPResponse { } compMsgs = append(compMsgs, models.CompatMessage{ - Title: v.Title, + Title: compatizeMessageTitle(ctx, h.app, v), Body: v.Content, Priority: v.Priority, Timestamp: v.Timestamp().Unix(), @@ -788,7 +789,7 @@ func (h CompatHandler) Expand(g *gin.Context) ginresp.HTTPResponse { Success: true, Message: "ok", Data: models.CompatMessage{ - Title: msg.Title, + Title: compatizeMessageTitle(ctx, h.app, msg), Body: msg.Content, Trimmed: langext.Ptr(false), Priority: msg.Priority, @@ -935,3 +936,16 @@ func (h CompatHandler) Upgrade(g *gin.Context) ginresp.HTTPResponse { IsPro: user.IsPro, })) } + +func compatizeMessageTitle(ctx *logic.AppContext, app *logic.Application, msg models.Message) string { + if msg.ChannelInternalName == "main" { + return msg.Title + } + + channel, err := app.Database.Primary.GetChannelByID(ctx, msg.ChannelID) + if err != nil { + return fmt.Sprintf("[%s] %s", "%SCN-ERR%", msg.Title) + } + + return fmt.Sprintf("[%s] %s", channel.DisplayName, msg.Title) +} diff --git a/scnserver/api/handler/message.go b/scnserver/api/handler/message.go index 65d2e73..a8aa226 100644 --- a/scnserver/api/handler/message.go +++ b/scnserver/api/handler/message.go @@ -284,7 +284,17 @@ func (h MessageHandler) sendMessageInternal(g *gin.Context, ctx *logic.AppContex for _, client := range clients { - fcmDelivID, err := h.app.DeliverMessage(ctx, client, msg) + isCompatClient, err := h.database.IsCompatClient(ctx, client.ClientID) + if err != nil { + return nil, langext.Ptr(ginresp.SendAPIError(g, 500, apierr.DATABASE_ERROR, hl.NONE, "Failed to query compat_clients", err)) + } + + var titleOverride *string = nil + if isCompatClient { + titleOverride = langext.Ptr(compatizeMessageTitle(ctx, h.app, msg)) + } + + fcmDelivID, err := h.app.DeliverMessage(ctx, client, msg, titleOverride) if err != nil { _, err = h.database.CreateRetryDelivery(ctx, client, msg) if err != nil { diff --git a/scnserver/cmd/migrate/main.go b/scnserver/cmd/migrate/main.go index 83064ad..8496e70 100644 --- a/scnserver/cmd/migrate/main.go +++ b/scnserver/cmd/migrate/main.go @@ -266,6 +266,11 @@ func migrateUser(ctx context.Context, dbnew sq.DB, dbold sq.DB, user OldUser, ap clientid = &_clientid usedFCM[*user.FcmToken] = _clientid + + _, err = dbnew.Exec(ctx, "INSERT INTO compat_clients (client_id) VALUES (:cid)", sq.PP{"cid": _clientid}) + if err != nil { + panic(err) + } } } diff --git a/scnserver/db/impl/primary/compat.go b/scnserver/db/impl/primary/compat.go index bd1f653..9357c0f 100644 --- a/scnserver/db/impl/primary/compat.go +++ b/scnserver/db/impl/primary/compat.go @@ -155,3 +155,26 @@ func (db *Database) SetAck(ctx TxContext, userid models.UserID, msgid models.Mes return nil } + +func (db *Database) IsCompatClient(ctx TxContext, clientid models.ClientID) (bool, error) { + tx, err := ctx.GetOrCreateTransaction(db) + if err != nil { + return false, err + } + + rows, err := tx.Query(ctx, "SELECT * FROM compat_clients WHERE client_id = :id LIMIT 1", sq.PP{ + "id": clientid, + }) + if err != nil { + return false, err + } + + res := rows.Next() + + err = rows.Close() + if err != nil { + return false, err + } + + return res, nil +} diff --git a/scnserver/db/impl/primary/schema/schema_3.ddl b/scnserver/db/impl/primary/schema/schema_3.ddl index bbbc394..de57bc1 100644 --- a/scnserver/db/impl/primary/schema/schema_3.ddl +++ b/scnserver/db/impl/primary/schema/schema_3.ddl @@ -214,6 +214,13 @@ 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, diff --git a/scnserver/jobs/DeliveryRetryJob.go b/scnserver/jobs/DeliveryRetryJob.go index 7e01af5..052b994 100644 --- a/scnserver/jobs/DeliveryRetryJob.go +++ b/scnserver/jobs/DeliveryRetryJob.go @@ -154,7 +154,7 @@ func (j *DeliveryRetryJob) redeliver(ctx *logic.SimpleContext, delivery models.D } } else { - fcmDelivID, err := j.app.DeliverMessage(ctx, client, msg) + fcmDelivID, err := j.app.DeliverMessage(ctx, client, msg, nil) if err == nil { err = j.app.Database.Primary.SetDeliverySuccess(ctx, delivery, *fcmDelivID) if err != nil { diff --git a/scnserver/logic/application.go b/scnserver/logic/application.go index c96b885..4b81405 100644 --- a/scnserver/logic/application.go +++ b/scnserver/logic/application.go @@ -347,9 +347,9 @@ func (app *Application) NormalizeUsername(v string) string { return v } -func (app *Application) DeliverMessage(ctx context.Context, client models.Client, msg models.Message) (*string, error) { +func (app *Application) DeliverMessage(ctx context.Context, client models.Client, msg models.Message, compatTitleOverride *string) (*string, error) { if client.FCMToken != nil { - fcmDelivID, err := app.Pusher.SendNotification(ctx, client, msg) + fcmDelivID, err := app.Pusher.SendNotification(ctx, client, msg, compatTitleOverride) if err != nil { log.Warn().Str("MessageID", msg.MessageID.String()).Str("ClientID", client.ClientID.String()).Err(err).Msg("FCM Delivery failed") return nil, err diff --git a/scnserver/push/dummy.go b/scnserver/push/dummy.go index 0d56216..c964731 100644 --- a/scnserver/push/dummy.go +++ b/scnserver/push/dummy.go @@ -12,6 +12,6 @@ func NewDummy() NotificationClient { return &DummyConnector{} } -func (d DummyConnector) SendNotification(ctx context.Context, client models.Client, msg models.Message) (string, error) { +func (d DummyConnector) SendNotification(ctx context.Context, client models.Client, msg models.Message, compatTitleOverride *string) (string, error) { return "%DUMMY%", nil } diff --git a/scnserver/push/firebase.go b/scnserver/push/firebase.go index fb43c31..9c83884 100644 --- a/scnserver/push/firebase.go +++ b/scnserver/push/firebase.go @@ -50,7 +50,7 @@ type Notification struct { Priority int } -func (fb FirebaseConnector) SendNotification(ctx context.Context, client models.Client, msg models.Message) (string, error) { +func (fb FirebaseConnector) SendNotification(ctx context.Context, client models.Client, msg models.Message, compatTitleOverride *string) (string, error) { uri := "https://fcm.googleapis.com/v1/projects/" + fb.fbProject + "/messages:send" @@ -62,7 +62,7 @@ func (fb FirebaseConnector) SendNotification(ctx context.Context, client models. "timestamp": strconv.FormatInt(msg.Timestamp().Unix(), 10), "priority": strconv.Itoa(msg.Priority), "trimmed": langext.Conditional(msg.NeedsTrim(), "true", "false"), - "title": msg.Title, + "title": langext.Coalesce(compatTitleOverride, msg.Title), "body": langext.Coalesce(msg.TrimmedContent(), ""), }, "token": *client.FCMToken, diff --git a/scnserver/push/notificationClient.go b/scnserver/push/notificationClient.go index 57617e4..51ea8ea 100644 --- a/scnserver/push/notificationClient.go +++ b/scnserver/push/notificationClient.go @@ -6,5 +6,5 @@ import ( ) type NotificationClient interface { - SendNotification(ctx context.Context, client models.Client, msg models.Message) (string, error) + SendNotification(ctx context.Context, client models.Client, msg models.Message, compatTitleOverride *string) (string, error) } diff --git a/scnserver/push/testSink.go b/scnserver/push/testSink.go index bfa5006..7499c77 100644 --- a/scnserver/push/testSink.go +++ b/scnserver/push/testSink.go @@ -8,8 +8,9 @@ import ( ) type SinkData struct { - Message models.Message - Client models.Client + Message models.Message + Client models.Client + CompatTitleOverride *string } type TestSink struct { @@ -24,7 +25,7 @@ func (d *TestSink) Last() SinkData { return d.Data[len(d.Data)-1] } -func (d *TestSink) SendNotification(ctx context.Context, client models.Client, msg models.Message) (string, error) { +func (d *TestSink) SendNotification(ctx context.Context, client models.Client, msg models.Message, compatTitleOverride *string) (string, error) { id, err := langext.NewHexUUID() if err != nil { return "", err @@ -33,8 +34,9 @@ func (d *TestSink) SendNotification(ctx context.Context, client models.Client, m key := "TestSink[" + id + "]" d.Data = append(d.Data, SinkData{ - Message: msg, - Client: client, + Message: msg, + Client: client, + CompatTitleOverride: compatTitleOverride, }) return key, nil diff --git a/scnserver/test/compat_test.go b/scnserver/test/compat_test.go index c0687a0..5f06ebd 100644 --- a/scnserver/test/compat_test.go +++ b/scnserver/test/compat_test.go @@ -694,3 +694,41 @@ func TestCompatRequery(t *testing.T) { tt.AssertEqual(t, "count", 0, rq7.Count) } + +func TestCompatTitlePatch(t *testing.T) { + ws, baseUrl, stop := tt.StartSimpleWebserver(t) + defer stop() + + pusher := ws.Pusher.(*push.TestSink) + + 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) + sendtok := r0["send_key"].(string) + + type clientlist struct { + Clients []gin.H `json:"clients"` + } + + clist1 := tt.RequestAuthGet[clientlist](t, admintok, baseUrl, fmt.Sprintf("/api/v2/users/%s/clients", url.QueryEscape(uid))) + + tt.SetCompatClient(t, ws, clist1.Clients[0]["client_id"].(string)) + + _ = tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{ + "key": sendtok, + "user_id": uid, + "title": "HelloWorld_001", + "channel": "TestChan", + }) + + tt.AssertEqual(t, "messageCount", 1, len(pusher.Data)) + tt.AssertStrRepEqual(t, "msg.title", "HelloWorld_001", pusher.Last().Message.Title) + tt.AssertStrRepEqual(t, "msg.ovrTitle", "[TestChan] HelloWorld_001", pusher.Last().CompatTitleOverride) + +} diff --git a/scnserver/test/util/internals.go b/scnserver/test/util/internals.go index a51c560..359ef90 100644 --- a/scnserver/test/util/internals.go +++ b/scnserver/test/util/internals.go @@ -2,6 +2,7 @@ package util import ( "blackforestbytes.com/simplecloudnotifier/logic" + "gogs.mikescher.com/BlackForestBytes/goext/sq" "testing" "time" ) @@ -64,3 +65,14 @@ func CreateCompatID(t *testing.T, ws *logic.Application, idtype string, newid st return uidold } + +func SetCompatClient(t *testing.T, ws *logic.Application, cid string) { + ctx := ws.NewSimpleTransactionContext(5 * time.Second) + defer ctx.Cancel() + + _, err := ws.Database.Primary.DB().Exec(ctx, "INSERT INTO compat_clients (client_id) VALUES (:cid)", sq.PP{"cid": cid}) + TestFailIfErr(t, err) + + err = ctx.CommitTransaction() + TestFailIfErr(t, err) +}