Send channel as prefix for compat clients
This commit is contained in:
parent
8826cb0312
commit
b2df0a5a02
@ -18,15 +18,6 @@
|
|||||||
|
|
||||||
- deploy
|
- 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,
|
- error logging as goroutine, gets all errors via channel,
|
||||||
(channel buffered - nonblocking send, second channel that gets a message when sender failed )
|
(channel buffered - nonblocking send, second channel that gets a message when sender failed )
|
||||||
(then all errors end up in _second_ sqlite table)
|
(then all errors end up in _second_ sqlite table)
|
||||||
@ -47,10 +38,6 @@
|
|||||||
|
|
||||||
- ios purchase verification
|
- 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
|
- move to KeyToken model
|
||||||
* [X] User can have multiple keys with different permissions
|
* [X] User can have multiple keys with different permissions
|
||||||
* [X] compat simply uses default-keys
|
* [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 ?
|
- 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?
|
Would delete all existing keys and create 3 new ones?
|
||||||
|
|
||||||
- TODO comments
|
- TODO-comments
|
||||||
|
|
||||||
#### PERSONAL
|
#### PERSONAL
|
||||||
|
|
||||||
- in my script: use `srvname` for sendername
|
- 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
|
#### UNSURE
|
||||||
|
|
||||||
- (?) default-priority for channels
|
- (?) default-priority for channels
|
||||||
|
@ -11,6 +11,7 @@ import (
|
|||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"gogs.mikescher.com/BlackForestBytes/goext/dataext"
|
"gogs.mikescher.com/BlackForestBytes/goext/dataext"
|
||||||
"gogs.mikescher.com/BlackForestBytes/goext/langext"
|
"gogs.mikescher.com/BlackForestBytes/goext/langext"
|
||||||
|
"runtime/debug"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -37,10 +38,11 @@ func Wrap(rlacc RequestLogAcceptor, fn WHandlerFunc) gin.HandlerFunc {
|
|||||||
|
|
||||||
for ctr := 1; ; ctr++ {
|
for ctr := 1; ; ctr++ {
|
||||||
|
|
||||||
wrap, panicObj := callPanicSafe(fn, g)
|
wrap, stackTrace, panicObj := callPanicSafe(fn, g)
|
||||||
if panicObj != nil {
|
if panicObj != nil {
|
||||||
log.Error().Interface("panicObj", panicObj).Msg("Panic occured (in gin handler)")
|
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() {
|
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() {
|
defer func() {
|
||||||
if rec := recover(); rec != nil {
|
if rec := recover(); rec != nil {
|
||||||
res = nil
|
res = nil
|
||||||
|
stackTrace = string(debug.Stack())
|
||||||
panicObj = rec
|
panicObj = rec
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
res = fn(g)
|
res = fn(g)
|
||||||
return res, nil
|
return res, "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func resetBody(g *gin.Context) error {
|
func resetBody(g *gin.Context) error {
|
||||||
|
@ -9,6 +9,7 @@ import (
|
|||||||
"blackforestbytes.com/simplecloudnotifier/logic"
|
"blackforestbytes.com/simplecloudnotifier/logic"
|
||||||
"blackforestbytes.com/simplecloudnotifier/models"
|
"blackforestbytes.com/simplecloudnotifier/models"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"gogs.mikescher.com/BlackForestBytes/goext/dataext"
|
"gogs.mikescher.com/BlackForestBytes/goext/dataext"
|
||||||
"gogs.mikescher.com/BlackForestBytes/goext/langext"
|
"gogs.mikescher.com/BlackForestBytes/goext/langext"
|
||||||
@ -540,7 +541,7 @@ func (h CompatHandler) Requery(g *gin.Context) ginresp.HTTPResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
compMsgs = append(compMsgs, models.CompatMessage{
|
compMsgs = append(compMsgs, models.CompatMessage{
|
||||||
Title: v.Title,
|
Title: compatizeMessageTitle(ctx, h.app, v),
|
||||||
Body: v.Content,
|
Body: v.Content,
|
||||||
Priority: v.Priority,
|
Priority: v.Priority,
|
||||||
Timestamp: v.Timestamp().Unix(),
|
Timestamp: v.Timestamp().Unix(),
|
||||||
@ -788,7 +789,7 @@ func (h CompatHandler) Expand(g *gin.Context) ginresp.HTTPResponse {
|
|||||||
Success: true,
|
Success: true,
|
||||||
Message: "ok",
|
Message: "ok",
|
||||||
Data: models.CompatMessage{
|
Data: models.CompatMessage{
|
||||||
Title: msg.Title,
|
Title: compatizeMessageTitle(ctx, h.app, msg),
|
||||||
Body: msg.Content,
|
Body: msg.Content,
|
||||||
Trimmed: langext.Ptr(false),
|
Trimmed: langext.Ptr(false),
|
||||||
Priority: msg.Priority,
|
Priority: msg.Priority,
|
||||||
@ -935,3 +936,16 @@ func (h CompatHandler) Upgrade(g *gin.Context) ginresp.HTTPResponse {
|
|||||||
IsPro: user.IsPro,
|
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)
|
||||||
|
}
|
||||||
|
@ -284,7 +284,17 @@ func (h MessageHandler) sendMessageInternal(g *gin.Context, ctx *logic.AppContex
|
|||||||
|
|
||||||
for _, client := range clients {
|
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 {
|
if err != nil {
|
||||||
_, err = h.database.CreateRetryDelivery(ctx, client, msg)
|
_, err = h.database.CreateRetryDelivery(ctx, client, msg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -266,6 +266,11 @@ func migrateUser(ctx context.Context, dbnew sq.DB, dbold sq.DB, user OldUser, ap
|
|||||||
clientid = &_clientid
|
clientid = &_clientid
|
||||||
|
|
||||||
usedFCM[*user.FcmToken] = _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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -155,3 +155,26 @@ func (db *Database) SetAck(ctx TxContext, userid models.UserID, msgid models.Mes
|
|||||||
|
|
||||||
return nil
|
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
|
||||||
|
}
|
||||||
|
@ -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 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`
|
CREATE TABLE `meta`
|
||||||
(
|
(
|
||||||
meta_key TEXT NOT NULL,
|
meta_key TEXT NOT NULL,
|
||||||
|
@ -154,7 +154,7 @@ func (j *DeliveryRetryJob) redeliver(ctx *logic.SimpleContext, delivery models.D
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
fcmDelivID, err := j.app.DeliverMessage(ctx, client, msg)
|
fcmDelivID, err := j.app.DeliverMessage(ctx, client, msg, nil)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
err = j.app.Database.Primary.SetDeliverySuccess(ctx, delivery, *fcmDelivID)
|
err = j.app.Database.Primary.SetDeliverySuccess(ctx, delivery, *fcmDelivID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -347,9 +347,9 @@ func (app *Application) NormalizeUsername(v string) string {
|
|||||||
return v
|
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 {
|
if client.FCMToken != nil {
|
||||||
fcmDelivID, err := app.Pusher.SendNotification(ctx, client, msg)
|
fcmDelivID, err := app.Pusher.SendNotification(ctx, client, msg, compatTitleOverride)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn().Str("MessageID", msg.MessageID.String()).Str("ClientID", client.ClientID.String()).Err(err).Msg("FCM Delivery failed")
|
log.Warn().Str("MessageID", msg.MessageID.String()).Str("ClientID", client.ClientID.String()).Err(err).Msg("FCM Delivery failed")
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -12,6 +12,6 @@ func NewDummy() NotificationClient {
|
|||||||
return &DummyConnector{}
|
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
|
return "%DUMMY%", nil
|
||||||
}
|
}
|
||||||
|
@ -50,7 +50,7 @@ type Notification struct {
|
|||||||
Priority int
|
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"
|
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),
|
"timestamp": strconv.FormatInt(msg.Timestamp().Unix(), 10),
|
||||||
"priority": strconv.Itoa(msg.Priority),
|
"priority": strconv.Itoa(msg.Priority),
|
||||||
"trimmed": langext.Conditional(msg.NeedsTrim(), "true", "false"),
|
"trimmed": langext.Conditional(msg.NeedsTrim(), "true", "false"),
|
||||||
"title": msg.Title,
|
"title": langext.Coalesce(compatTitleOverride, msg.Title),
|
||||||
"body": langext.Coalesce(msg.TrimmedContent(), ""),
|
"body": langext.Coalesce(msg.TrimmedContent(), ""),
|
||||||
},
|
},
|
||||||
"token": *client.FCMToken,
|
"token": *client.FCMToken,
|
||||||
|
@ -6,5 +6,5 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type NotificationClient interface {
|
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)
|
||||||
}
|
}
|
||||||
|
@ -8,8 +8,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type SinkData struct {
|
type SinkData struct {
|
||||||
Message models.Message
|
Message models.Message
|
||||||
Client models.Client
|
Client models.Client
|
||||||
|
CompatTitleOverride *string
|
||||||
}
|
}
|
||||||
|
|
||||||
type TestSink struct {
|
type TestSink struct {
|
||||||
@ -24,7 +25,7 @@ func (d *TestSink) Last() SinkData {
|
|||||||
return d.Data[len(d.Data)-1]
|
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()
|
id, err := langext.NewHexUUID()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
@ -33,8 +34,9 @@ func (d *TestSink) SendNotification(ctx context.Context, client models.Client, m
|
|||||||
key := "TestSink[" + id + "]"
|
key := "TestSink[" + id + "]"
|
||||||
|
|
||||||
d.Data = append(d.Data, SinkData{
|
d.Data = append(d.Data, SinkData{
|
||||||
Message: msg,
|
Message: msg,
|
||||||
Client: client,
|
Client: client,
|
||||||
|
CompatTitleOverride: compatTitleOverride,
|
||||||
})
|
})
|
||||||
|
|
||||||
return key, nil
|
return key, nil
|
||||||
|
@ -694,3 +694,41 @@ func TestCompatRequery(t *testing.T) {
|
|||||||
tt.AssertEqual(t, "count", 0, rq7.Count)
|
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)
|
||||||
|
|
||||||
|
}
|
||||||
|
@ -2,6 +2,7 @@ package util
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"blackforestbytes.com/simplecloudnotifier/logic"
|
"blackforestbytes.com/simplecloudnotifier/logic"
|
||||||
|
"gogs.mikescher.com/BlackForestBytes/goext/sq"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@ -64,3 +65,14 @@ func CreateCompatID(t *testing.T, ws *logic.Application, idtype string, newid st
|
|||||||
|
|
||||||
return uidold
|
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)
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user