23 Commits

Author SHA1 Message Date
1bb37eec30 TODO's 2023-06-18 14:28:58 +02:00
59511b2345 Fix bug in migration script 2023-06-18 14:16:37 +02:00
5b7bc02c61 Fix validation in web form 2023-06-18 13:25:00 +02:00
b329f537e7 Fix message_sent html 2023-06-18 13:19:51 +02:00
5879e81759 Enable RequestLog on dev/stag/prod 2023-06-18 13:11:48 +02:00
f4e88bef77 Fix NPE in compat-ack 2023-06-18 13:09:36 +02:00
b3ec45309c Insert exclam on compat clients if message uses old channel syntax 2023-06-18 11:59:26 +02:00
2fbc892898 various fixes in scn_send script 2023-06-18 04:45:28 +02:00
c46190c3fc Support x-www-form-urlencoded form-data 2023-06-18 03:46:01 +02:00
860e540de1 Better CreateKey API (make all_channels and channels optional) 2023-06-18 02:54:41 +02:00
8cde286cac Fix failing tests 2023-06-18 02:36:44 +02:00
90830fe384 Fix empty string in channels field in GetKeys route 2023-06-18 02:34:04 +02:00
686f89f75d change URL to simplecloudnotifier.de 2023-06-18 02:22:29 +02:00
4210af5680 Properly implement compat unack_count 2023-06-18 02:09:05 +02:00
aefc368cfd Send compat-msgid to compat clients (BF old android client) 2023-06-18 01:55:58 +02:00
67218d8045 Added a few logs 2023-06-18 01:36:34 +02:00
c05deb3a41 allow \n in private-key envs 2023-06-18 01:29:13 +02:00
43d0107fb5 Update goext (fix bool env parsing) 2023-06-18 01:18:33 +02:00
ece7612f9d more migration fixes 2023-06-18 00:49:29 +02:00
a9809d90cb fix exception in js-send (logic.js) 2023-06-18 00:25:10 +02:00
bbc9a79996 Fix bug in migration script 2023-06-18 00:24:53 +02:00
b71f1885ec Fix wrongly named env variables 2023-06-17 23:40:46 +02:00
885aad2047 Update migrationscript 2023-06-17 23:23:54 +02:00
32 changed files with 792 additions and 155 deletions

View File

@@ -8,6 +8,12 @@ DOCKER_GIT_INFO
scn_export.dat scn_export.dat
scn_export.json scn_export.json
scn_export_*.dat
scn_export_*.json
simple_cloud_notifier-202306172202.sql
simple_cloud_notifier-*.sql
identifier.sqlite identifier.sqlite
.idea/dataSources.xml .idea/dataSources.xml

View File

@@ -5,7 +5,7 @@ PORT=9090
NAMESPACE=$(shell git rev-parse --abbrev-ref HEAD) NAMESPACE=$(shell git rev-parse --abbrev-ref HEAD)
HASH=$(shell git rev-parse HEAD) HASH=$(shell git rev-parse HEAD)
.PHONY: test swagger pygmentize docker migrate dgi pygmentize lint .PHONY: test swagger pygmentize docker migrate dgi pygmentize lint docker
build: swagger pygmentize fmt build: swagger pygmentize fmt
mkdir -p _build mkdir -p _build
@@ -29,7 +29,7 @@ dgi:
echo -n "COMMITTIME=" >> DOCKER_GIT_INFO ; git log -1 --format=%cd --date=iso >> DOCKER_GIT_INFO echo -n "COMMITTIME=" >> DOCKER_GIT_INFO ; git log -1 --format=%cd --date=iso >> DOCKER_GIT_INFO
echo -n "REMOTE=" >> DOCKER_GIT_INFO ; git config --get remote.origin.url >> DOCKER_GIT_INFO echo -n "REMOTE=" >> DOCKER_GIT_INFO ; git config --get remote.origin.url >> DOCKER_GIT_INFO
build-docker: dgi docker: dgi
docker build \ docker build \
-t "$(DOCKER_NAME):$(HASH)" \ -t "$(DOCKER_NAME):$(HASH)" \
-t "$(DOCKER_NAME):$(NAMESPACE)-latest" \ -t "$(DOCKER_NAME):$(NAMESPACE)-latest" \

View File

@@ -6,15 +6,9 @@
#### BEFORE RELEASE #### BEFORE RELEASE
- migrate old data
- in my script: use `srvname` for sendername
- switch send script everywhere (we can use the new server, but we need to send correct channels)
- app-store link in HTML - app-store link in HTML
- deploy - backups (no longer container in my db_backup, perhaps extend it to sqlite?)
- ios purchase verification - ios purchase verification
@@ -30,6 +24,8 @@
- (?) add querylog (similar to requestlog/errorlog) - only for main-db - (?) add querylog (similar to requestlog/errorlog) - only for main-db
- (?) specify 'type' of message (debug, info, warn, error, fatal) -> distinct from priority
#### LATER #### LATER
- do i need bool2db()? it seems to work for keytokens without them? - do i need bool2db()? it seems to work for keytokens without them?

View File

@@ -68,6 +68,12 @@ func Wrap(rlacc RequestLogAcceptor, fn WHandlerFunc) gin.HandlerFunc {
if scn.Conf.ReqLogEnabled { if scn.Conf.ReqLogEnabled {
rlacc.InsertRequestLog(createRequestLog(g, t0, ctr, wrap, nil)) rlacc.InsertRequestLog(createRequestLog(g, t0, ctr, wrap, nil))
} }
statuscode := wrap.Statuscode()
if statuscode/100 != 2 {
log.Warn().Str("url", g.Request.Method+"::"+g.Request.URL.String()).Msg(fmt.Sprintf("Request failed with statuscode %d", statuscode))
}
wrap.Write(g) wrap.Write(g)
} }

View File

@@ -45,12 +45,12 @@ func (h APIHandler) ListUserKeys(g *gin.Context) ginresp.HTTPResponse {
return *permResp return *permResp
} }
clients, err := h.database.ListKeyTokens(ctx, u.UserID) toks, err := h.database.ListKeyTokens(ctx, u.UserID)
if err != nil { if err != nil {
return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to query keys", err) return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to query keys", err)
} }
res := langext.ArrMap(clients, func(v models.KeyToken) models.KeyTokenJSON { return v.JSON() }) res := langext.ArrMap(toks, func(v models.KeyToken) models.KeyTokenJSON { return v.JSON() })
return ctx.FinishSuccess(ginresp.JSON(http.StatusOK, response{Keys: res})) return ctx.FinishSuccess(ginresp.JSON(http.StatusOK, response{Keys: res}))
} }
@@ -221,9 +221,9 @@ func (h APIHandler) CreateUserKey(g *gin.Context) ginresp.HTTPResponse {
} }
type body struct { type body struct {
Name string `json:"name" binding:"required"` Name string `json:"name" binding:"required"`
AllChannels *bool `json:"all_channels" binding:"required"` Permissions string `json:"permissions" binding:"required"`
Channels *[]models.ChannelID `json:"channels" binding:"required"` AllChannels *bool `json:"all_channels"`
Permissions *string `json:"permissions" binding:"required"` Channels *[]models.ChannelID `json:"channels"`
} }
var u uri var u uri
@@ -234,7 +234,18 @@ func (h APIHandler) CreateUserKey(g *gin.Context) ginresp.HTTPResponse {
} }
defer ctx.Cancel() defer ctx.Cancel()
for _, c := range *b.Channels { channels := langext.Coalesce(b.Channels, make([]models.ChannelID, 0))
var allChan bool
if b.AllChannels == nil && b.Channels != nil {
allChan = false
} else if b.AllChannels == nil && b.Channels == nil {
allChan = true
} else {
allChan = *b.AllChannels
}
for _, c := range channels {
if err := c.Valid(); err != nil { if err := c.Valid(); err != nil {
return ginresp.APIError(g, 400, apierr.INVALID_BODY_PARAM, "Invalid ChannelID", err) return ginresp.APIError(g, 400, apierr.INVALID_BODY_PARAM, "Invalid ChannelID", err)
} }
@@ -246,9 +257,9 @@ func (h APIHandler) CreateUserKey(g *gin.Context) ginresp.HTTPResponse {
token := h.app.GenerateRandomAuthKey() token := h.app.GenerateRandomAuthKey()
perms := models.ParseTokenPermissionList(*b.Permissions) perms := models.ParseTokenPermissionList(b.Permissions)
keytok, err := h.database.CreateKeyToken(ctx, b.Name, *ctx.GetPermissionUserID(), *b.AllChannels, *b.Channels, perms, token) keytok, err := h.database.CreateKeyToken(ctx, b.Name, *ctx.GetPermissionUserID(), allChan, channels, perms, token)
if err != nil { if err != nil {
return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to create keytoken in db", err) return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to create keytoken in db", err)
} }

View File

@@ -5,7 +5,9 @@ import (
"blackforestbytes.com/simplecloudnotifier/api/ginresp" "blackforestbytes.com/simplecloudnotifier/api/ginresp"
"blackforestbytes.com/simplecloudnotifier/models" "blackforestbytes.com/simplecloudnotifier/models"
"database/sql" "database/sql"
"fmt"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/rs/zerolog/log"
"gogs.mikescher.com/BlackForestBytes/goext/langext" "gogs.mikescher.com/BlackForestBytes/goext/langext"
"net/http" "net/http"
) )
@@ -113,6 +115,8 @@ func (h APIHandler) CreateUser(g *gin.Context) ginresp.HTTPResponse {
return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to create read-key in db", err) return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to create read-key in db", err)
} }
log.Info().Msg(fmt.Sprintf("Sucessfully created new user %s (client: %v)", userobj.UserID, b.NoClient))
if b.NoClient { if b.NoClient {
return ctx.FinishSuccess(ginresp.JSON(http.StatusOK, userobj.JSONWithClients(make([]models.Client, 0), adminKey, sendKey, readKey))) return ctx.FinishSuccess(ginresp.JSON(http.StatusOK, userobj.JSONWithClients(make([]models.Client, 0), adminKey, sendKey, readKey)))
} else { } else {

View File

@@ -258,7 +258,7 @@ func (h CompatHandler) Info(g *gin.Context) ginresp.HTTPResponse {
QuotaMax int `json:"quota_max"` QuotaMax int `json:"quota_max"`
IsPro int `json:"is_pro"` IsPro int `json:"is_pro"`
FCMSet bool `json:"fcm_token_set"` FCMSet bool `json:"fcm_token_set"`
UnackCount int `json:"unack_count"` UnackCount int64 `json:"unack_count"`
} }
var datq query var datq query
@@ -310,6 +310,16 @@ func (h CompatHandler) Info(g *gin.Context) ginresp.HTTPResponse {
return ginresp.CompatAPIError(0, "Failed to query clients") return ginresp.CompatAPIError(0, "Failed to query clients")
} }
filter := models.MessageFilter{
Owner: langext.Ptr([]models.UserID{user.UserID}),
CompatAcknowledged: langext.Ptr(false),
}
unackCount, err := h.database.CountMessages(ctx, filter)
if err != nil {
return ginresp.CompatAPIError(0, "Failed to query user")
}
return ctx.FinishSuccess(ginresp.JSON(http.StatusOK, response{ return ctx.FinishSuccess(ginresp.JSON(http.StatusOK, response{
Success: true, Success: true,
Message: "ok", Message: "ok",
@@ -319,7 +329,7 @@ func (h CompatHandler) Info(g *gin.Context) ginresp.HTTPResponse {
QuotaMax: user.QuotaPerDay(), QuotaMax: user.QuotaPerDay(),
IsPro: langext.Conditional(user.IsPro, 1, 0), IsPro: langext.Conditional(user.IsPro, 1, 0),
FCMSet: len(clients) > 0, FCMSet: len(clients) > 0,
UnackCount: 0, UnackCount: unackCount,
})) }))
} }
@@ -381,7 +391,7 @@ func (h CompatHandler) Ack(g *gin.Context) ginresp.HTTPResponse {
return ginresp.SendAPIError(g, 500, apierr.DATABASE_ERROR, hl.NONE, "Failed to query userid<old>", err) return ginresp.SendAPIError(g, 500, apierr.DATABASE_ERROR, hl.NONE, "Failed to query userid<old>", err)
} }
if useridCompNew == nil { if useridCompNew == nil {
return ginresp.SendAPIError(g, 400, apierr.USER_NOT_FOUND, hl.USER_ID, "User not found (compat)", nil) return ginresp.SendAPIError(g, 400, apierr.USER_NOT_FOUND, hl.USER_ID, fmt.Sprintf("User %d not found (compat)", *data.UserID), nil)
} }
user, err := h.database.GetUser(ctx, models.UserID(*useridCompNew)) user, err := h.database.GetUser(ctx, models.UserID(*useridCompNew))
@@ -407,8 +417,8 @@ func (h CompatHandler) Ack(g *gin.Context) ginresp.HTTPResponse {
if err != nil { if err != nil {
return ginresp.SendAPIError(g, 500, apierr.DATABASE_ERROR, hl.NONE, "Failed to query messageid<old>", err) return ginresp.SendAPIError(g, 500, apierr.DATABASE_ERROR, hl.NONE, "Failed to query messageid<old>", err)
} }
if useridCompNew == nil { if messageIdComp == nil {
return ginresp.SendAPIError(g, 400, apierr.MESSAGE_NOT_FOUND, hl.USER_ID, "Message not found (compat)", nil) return ginresp.SendAPIError(g, 400, apierr.MESSAGE_NOT_FOUND, hl.NONE, fmt.Sprintf("Message %d not found (compat)", *data.MessageID), nil)
} }
ackBefore, err := h.database.GetAck(ctx, models.MessageID(*messageIdComp)) ackBefore, err := h.database.GetAck(ctx, models.MessageID(*messageIdComp))
@@ -524,7 +534,7 @@ func (h CompatHandler) Requery(g *gin.Context) ginresp.HTTPResponse {
} }
compMsgs = append(compMsgs, models.CompatMessage{ compMsgs = append(compMsgs, models.CompatMessage{
Title: compatizeMessageTitle(ctx, h.app, v), Title: h.app.CompatizeMessageTitle(ctx, v),
Body: v.Content, Body: v.Content,
Priority: v.Priority, Priority: v.Priority,
Timestamp: v.Timestamp().Unix(), Timestamp: v.Timestamp().Unix(),
@@ -772,7 +782,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: compatizeMessageTitle(ctx, h.app, msg), Title: h.app.CompatizeMessageTitle(ctx, msg),
Body: msg.Content, Body: msg.Content,
Trimmed: langext.Ptr(false), Trimmed: langext.Ptr(false),
Priority: msg.Priority, Priority: msg.Priority,
@@ -919,16 +929,3 @@ 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)
}

View File

@@ -10,6 +10,7 @@ import (
"database/sql" "database/sql"
"fmt" "fmt"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"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"
"gogs.mikescher.com/BlackForestBytes/goext/mathext" "gogs.mikescher.com/BlackForestBytes/goext/mathext"
@@ -238,7 +239,7 @@ func (h MessageHandler) sendMessageInternal(g *gin.Context, ctx *logic.AppContex
return nil, langext.Ptr(ginresp.SendAPIError(g, 500, apierr.DATABASE_ERROR, hl.NONE, "Failed to create message in db", err)) return nil, langext.Ptr(ginresp.SendAPIError(g, 500, apierr.DATABASE_ERROR, hl.NONE, "Failed to create message in db", err))
} }
cid, err := h.database.CreateCompatID(ctx, "messageid", msg.MessageID.String()) compatMsgID, err := h.database.CreateCompatID(ctx, "messageid", msg.MessageID.String())
if err != nil { if err != nil {
return nil, langext.Ptr(ginresp.SendAPIError(g, 500, apierr.DATABASE_ERROR, hl.NONE, "Failed to create compat-id", err)) return nil, langext.Ptr(ginresp.SendAPIError(g, 500, apierr.DATABASE_ERROR, hl.NONE, "Failed to create compat-id", err))
} }
@@ -263,6 +264,8 @@ func (h MessageHandler) sendMessageInternal(g *gin.Context, ctx *logic.AppContex
return nil, langext.Ptr(ginresp.SendAPIError(g, 500, apierr.DATABASE_ERROR, hl.NONE, "Failed to inc token msg-counter", err)) return nil, langext.Ptr(ginresp.SendAPIError(g, 500, apierr.DATABASE_ERROR, hl.NONE, "Failed to inc token msg-counter", err))
} }
log.Info().Msg(fmt.Sprintf("Sending new notification %s for user %s", msg.MessageID, UserID))
for _, sub := range subscriptions { for _, sub := range subscriptions {
clients, err := h.database.ListClients(ctx, sub.SubscriberUserID) clients, err := h.database.ListClients(ctx, sub.SubscriberUserID)
if err != nil { if err != nil {
@@ -281,11 +284,13 @@ func (h MessageHandler) sendMessageInternal(g *gin.Context, ctx *logic.AppContex
} }
var titleOverride *string = nil var titleOverride *string = nil
var msgidOverride *string = nil
if isCompatClient { if isCompatClient {
titleOverride = langext.Ptr(compatizeMessageTitle(ctx, h.app, msg)) titleOverride = langext.Ptr(h.app.CompatizeMessageTitle(ctx, msg))
msgidOverride = langext.Ptr(fmt.Sprintf("%d", compatMsgID))
} }
fcmDelivID, err := h.app.DeliverMessage(ctx, client, msg, titleOverride) fcmDelivID, err := h.app.DeliverMessage(ctx, client, msg, titleOverride, msgidOverride)
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 {
@@ -305,6 +310,6 @@ func (h MessageHandler) sendMessageInternal(g *gin.Context, ctx *logic.AppContex
User: user, User: user,
Message: msg, Message: msg,
MessageIsOld: false, MessageIsOld: false,
CompatMessageID: cid, CompatMessageID: compatMsgID,
}, nil }, nil
} }

View File

@@ -40,7 +40,7 @@ func NewRouter(app *logic.Application) *Router {
// @title SimpleCloudNotifier API // @title SimpleCloudNotifier API
// @version 2.0 // @version 2.0
// @description API for SCN // @description API for SCN
// @host scn.blackforestbytes.com // @host simplecloudnotifier.de
// //
// @tag.name External // @tag.name External
// @tag.name API-v1 // @tag.name API-v1

View File

@@ -13,6 +13,7 @@ import (
"gogs.mikescher.com/BlackForestBytes/goext/langext" "gogs.mikescher.com/BlackForestBytes/goext/langext"
"gogs.mikescher.com/BlackForestBytes/goext/rext" "gogs.mikescher.com/BlackForestBytes/goext/rext"
"gogs.mikescher.com/BlackForestBytes/goext/sq" "gogs.mikescher.com/BlackForestBytes/goext/sq"
"gogs.mikescher.com/BlackForestBytes/goext/termext"
"os" "os"
"regexp" "regexp"
"strings" "strings"
@@ -89,9 +90,10 @@ func main() {
panic(err) panic(err)
} }
scanner := bufio.NewScanner(os.Stdin)
connstr := os.Getenv("SQL_CONN_STR") connstr := os.Getenv("SQL_CONN_STR")
if connstr == "" { if connstr == "" {
scanner := bufio.NewScanner(os.Stdin)
fmt.Print("Enter DB URL [127.0.0.1:3306]: ") fmt.Print("Enter DB URL [127.0.0.1:3306]: ")
scanner.Scan() scanner.Scan()
@@ -103,21 +105,33 @@ func main() {
fmt.Print("Enter DB Username [root]: ") fmt.Print("Enter DB Username [root]: ")
scanner.Scan() scanner.Scan()
username := scanner.Text() username := scanner.Text()
if host == "" { if username == "" {
host = "root" username = "root"
} }
fmt.Print("Enter DB Password []: ") fmt.Print("Enter DB Password []: ")
scanner.Scan() scanner.Scan()
pass := scanner.Text() pass := scanner.Text()
if host == "" { if pass == "" {
host = "" pass = ""
} }
connstr = fmt.Sprintf("%s:%s@tcp(%s)", username, pass, host) connstr = fmt.Sprintf("%s:%s@tcp(%s)", username, pass, host)
} }
_dbold, err := sqlx.Open("mysql", connstr+"/simple_cloud_notifier?parseTime=true") olddbname := os.Getenv("SQL_CONN_DBNAME")
if olddbname == "" {
fmt.Print("Enter DB Name: ")
scanner.Scan()
olddbname = scanner.Text()
if olddbname == "" {
olddbname = "scn_final"
}
}
_dbold, err := sqlx.Open("mysql", connstr+"/"+olddbname+"?parseTime=true")
if err != nil { if err != nil {
panic(err) panic(err)
} }
@@ -292,8 +306,8 @@ func migrateUser(ctx context.Context, dbnew sq.DB, dbold sq.DB, user OldUser, ap
_, err = dbnew.Exec(ctx, "INSERT INTO subscriptions (subscription_id, subscriber_user_id, channel_owner_user_id, channel_internal_name, channel_id, timestamp_created, confirmed) VALUES (:sid, :suid, :ouid, :cnam, :cid, :ts, :conf)", sq.PP{ _, err = dbnew.Exec(ctx, "INSERT INTO subscriptions (subscription_id, subscriber_user_id, channel_owner_user_id, channel_internal_name, channel_id, timestamp_created, confirmed) VALUES (:sid, :suid, :ouid, :cnam, :cid, :ts, :conf)", sq.PP{
"sid": models.NewSubscriptionID(), "sid": models.NewSubscriptionID(),
"suid": user.UserId, "suid": userid,
"ouid": user.UserId, "ouid": userid,
"cnam": "main", "cnam": "main",
"cid": mainChannelID, "cid": mainChannelID,
"ts": user.TimestampCreated.UnixMilli(), "ts": user.TimestampCreated.UnixMilli(),
@@ -340,7 +354,7 @@ func migrateUser(ctx context.Context, dbnew sq.DB, dbold sq.DB, user OldUser, ap
dispName := dummyApp.NormalizeChannelDisplayName(chanNameTitle) dispName := dummyApp.NormalizeChannelDisplayName(chanNameTitle)
intName := dummyApp.NormalizeChannelInternalName(chanNameTitle) intName := dummyApp.NormalizeChannelInternalName(chanNameTitle)
if v, ok := channelMap[intName]; ok { if v, ok := channelMap[strings.ToLower(intName)]; ok {
channelID = v channelID = v
channelInternalName = intName channelInternalName = intName
} else { } else {
@@ -363,8 +377,8 @@ func migrateUser(ctx context.Context, dbnew sq.DB, dbold sq.DB, user OldUser, ap
_, err = dbnew.Exec(ctx, "INSERT INTO subscriptions (subscription_id, subscriber_user_id, channel_owner_user_id, channel_internal_name, channel_id, timestamp_created, confirmed) VALUES (:sid, :suid, :ouid, :cnam, :cid, :ts, :conf)", sq.PP{ _, err = dbnew.Exec(ctx, "INSERT INTO subscriptions (subscription_id, subscriber_user_id, channel_owner_user_id, channel_internal_name, channel_id, timestamp_created, confirmed) VALUES (:sid, :suid, :ouid, :cnam, :cid, :ts, :conf)", sq.PP{
"sid": models.NewSubscriptionID(), "sid": models.NewSubscriptionID(),
"suid": user.UserId, "suid": userid,
"ouid": user.UserId, "ouid": userid,
"cnam": intName, "cnam": intName,
"cid": channelID, "cid": channelID,
"ts": oldmessage.TimestampReal.UnixMilli(), "ts": oldmessage.TimestampReal.UnixMilli(),
@@ -374,7 +388,7 @@ func migrateUser(ctx context.Context, dbnew sq.DB, dbold sq.DB, user OldUser, ap
panic(err) panic(err)
} }
channelMap[intName] = channelID channelMap[strings.ToLower(intName)] = channelID
fmt.Printf("Auto Created Channel [%s]: %s\n", dispName, channelID) fmt.Printf("Auto Created Channel [%s]: %s\n", dispName, channelID)
@@ -383,6 +397,9 @@ func migrateUser(ctx context.Context, dbnew sq.DB, dbold sq.DB, user OldUser, ap
} }
sendername := determineSenderName(user, oldmessage, title, oldmessage.Content, channelInternalName) sendername := determineSenderName(user, oldmessage, title, oldmessage.Content, channelInternalName)
if sendername != nil && *sendername == "" {
panic("sendername")
}
if lastTitle == title && channelID == lastChannel && if lastTitle == title && channelID == lastChannel &&
langext.PtrEquals(lastContent, oldmessage.Content) && langext.PtrEquals(lastContent, oldmessage.Content) &&
@@ -394,7 +411,7 @@ func migrateUser(ctx context.Context, dbnew sq.DB, dbold sq.DB, user OldUser, ap
lastSendername = sendername lastSendername = sendername
lastTimestamp = oldmessage.TimestampReal lastTimestamp = oldmessage.TimestampReal
fmt.Printf("Skip message [%d] \"%s\" (fast-duplicate)\n", oldmessage.ScnMessageId, oldmessage.Title) //fmt.Printf("Skip message [%d] \"%s\" (fast-duplicate)\n", oldmessage.ScnMessageId, oldmessage.Title)
continue continue
} }
@@ -413,7 +430,7 @@ func migrateUser(ctx context.Context, dbnew sq.DB, dbold sq.DB, user OldUser, ap
lastSendername = sendername lastSendername = sendername
lastTimestamp = oldmessage.TimestampReal lastTimestamp = oldmessage.TimestampReal
fmt.Printf("Skip message [%d] \"%s\" (locally deleted in app)\n", oldmessage.ScnMessageId, oldmessage.Title) //fmt.Printf("Skip message [%d] \"%s\" (locally deleted in app)\n", oldmessage.ScnMessageId, oldmessage.Title)
continue continue
} }
} }
@@ -421,7 +438,7 @@ func migrateUser(ctx context.Context, dbnew sq.DB, dbold sq.DB, user OldUser, ap
pp := sq.PP{ pp := sq.PP{
"mid": messageid, "mid": messageid,
"suid": userid, "suid": userid,
"ouid": user.UserId, "ouid": userid,
"cnam": channelInternalName, "cnam": channelInternalName,
"cid": channelID, "cid": channelID,
"tsr": oldmessage.TimestampReal.UnixMilli(), "tsr": oldmessage.TimestampReal.UnixMilli(),
@@ -456,7 +473,7 @@ func migrateUser(ctx context.Context, dbnew sq.DB, dbold sq.DB, user OldUser, ap
_, err = dbnew.Exec(ctx, "INSERT INTO deliveries (delivery_id, message_id, receiver_user_id, receiver_client_id, timestamp_created, timestamp_finalized, status, fcm_message_id, next_delivery) VALUES (:did, :mid, :ruid, :rcid, :tsc, :tsf, :stat, :fcm, :next)", sq.PP{ _, err = dbnew.Exec(ctx, "INSERT INTO deliveries (delivery_id, message_id, receiver_user_id, receiver_client_id, timestamp_created, timestamp_finalized, status, fcm_message_id, next_delivery) VALUES (:did, :mid, :ruid, :rcid, :tsc, :tsf, :stat, :fcm, :next)", sq.PP{
"did": models.NewDeliveryID(), "did": models.NewDeliveryID(),
"mid": messageid, "mid": messageid,
"ruid": user.UserId, "ruid": userid,
"rcid": *clientid, "rcid": *clientid,
"tsc": oldmessage.TimestampReal.UnixMilli(), "tsc": oldmessage.TimestampReal.UnixMilli(),
"tsf": oldmessage.TimestampReal.UnixMilli(), "tsf": oldmessage.TimestampReal.UnixMilli(),
@@ -483,7 +500,7 @@ func migrateUser(ctx context.Context, dbnew sq.DB, dbold sq.DB, user OldUser, ap
_, err = dbnew.Exec(ctx, "INSERT INTO deliveries (delivery_id, message_id, receiver_user_id, receiver_client_id, timestamp_created, timestamp_finalized, status, fcm_message_id, next_delivery) VALUES (:did, :mid, :ruid, :rcid, :tsc, :tsf, :stat, :fcm, :next)", sq.PP{ _, err = dbnew.Exec(ctx, "INSERT INTO deliveries (delivery_id, message_id, receiver_user_id, receiver_client_id, timestamp_created, timestamp_finalized, status, fcm_message_id, next_delivery) VALUES (:did, :mid, :ruid, :rcid, :tsc, :tsf, :stat, :fcm, :next)", sq.PP{
"did": models.NewDeliveryID(), "did": models.NewDeliveryID(),
"mid": messageid, "mid": messageid,
"ruid": user.UserId, "ruid": userid,
"rcid": *clientid, "rcid": *clientid,
"tsc": oldmessage.TimestampReal.UnixMilli(), "tsc": oldmessage.TimestampReal.UnixMilli(),
"tsf": oldmessage.TimestampReal.UnixMilli(), "tsf": oldmessage.TimestampReal.UnixMilli(),
@@ -509,6 +526,92 @@ func migrateUser(ctx context.Context, dbnew sq.DB, dbold sq.DB, user OldUser, ap
lastTimestamp = oldmessage.TimestampReal lastTimestamp = oldmessage.TimestampReal
} }
{
c, err := dbnew.Query(ctx, "SELECT COUNT (*) FROM messages WHERE sender_user_id = :uid", sq.PP{
"uid": userid,
})
if err != nil {
panic(err)
}
if !c.Next() {
panic(false)
}
count := 0
err = c.Scan(&count)
if err != nil {
panic(err)
}
err = c.Close()
if err != nil {
panic(err)
}
if count > 0 {
_, err = dbnew.Exec(ctx, "UPDATE users SET messages_sent = :c, timestamp_lastread = :ts0, timestamp_lastsent = :ts1 WHERE user_id = :uid", sq.PP{
"uid": userid,
"c": count,
"ts0": lastTimestamp.UnixMilli(),
"ts1": lastTimestamp.UnixMilli(),
})
if err != nil {
panic(err)
}
} else {
_, err = dbnew.Exec(ctx, "UPDATE users SET messages_sent = :c, timestamp_lastread = :ts0 WHERE user_id = :uid", sq.PP{
"uid": userid,
"c": count,
"ts0": user.TimestampCreated.UnixMilli(),
})
if err != nil {
panic(err)
}
}
_, err = dbnew.Exec(ctx, "UPDATE keytokens SET messages_sent = :c WHERE owner_user_id = :uid", sq.PP{
"uid": userid,
"c": count,
})
if err != nil {
panic(err)
}
}
for _, cid := range channelMap {
c, err := dbnew.Query(ctx, "SELECT COUNT (*) FROM messages WHERE sender_user_id = :uid AND channel_id = :cid", sq.PP{
"cid": cid,
"uid": userid,
})
if err != nil {
panic(err)
}
if !c.Next() {
panic(false)
}
count := 0
err = c.Scan(&count)
if err != nil {
panic(err)
}
err = c.Close()
if err != nil {
panic(err)
}
_, err = dbnew.Exec(ctx, "UPDATE channels SET messages_sent = :c WHERE channel_id = :cid", sq.PP{
"cid": cid,
"c": count,
})
if err != nil {
panic(err)
}
}
} }
func determineSenderName(user OldUser, oldmessage OldMessage, title string, content *string, channame string) *string { func determineSenderName(user OldUser, oldmessage OldMessage, title string, content *string, channame string) *string {
@@ -516,6 +619,8 @@ func determineSenderName(user OldUser, oldmessage OldMessage, title string, cont
return nil return nil
} }
channame = strings.ToLower(channame)
if channame == "t-ctrl" { if channame == "t-ctrl" {
return langext.Ptr("sbox") return langext.Ptr("sbox")
} }
@@ -542,6 +647,42 @@ func determineSenderName(user OldUser, oldmessage OldMessage, title string, cont
if strings.Contains(title, "error on niflheim-3") { if strings.Contains(title, "error on niflheim-3") {
return langext.Ptr("niflheim-3") return langext.Ptr("niflheim-3")
} }
if strings.Contains(title, "error on statussrv") {
return langext.Ptr("statussrv")
}
if strings.Contains(title, "error on plan-web-prod") {
return langext.Ptr("plan-web-prod")
}
if strings.Contains(title, "error on inoshop") {
return langext.Ptr("inoshop")
}
if strings.Contains(title, "error on firestopcloud") {
return langext.Ptr("firestopcloud")
}
if strings.Contains(title, "error on plantafelstaging") {
return langext.Ptr("plantafelstaging")
}
if strings.Contains(title, "error on plantafeldev") {
return langext.Ptr("plantafeldev")
}
if strings.Contains(title, "error on balu-prod") {
return langext.Ptr("lbxprod")
}
if strings.Contains(title, "error on dyno-prod") {
return langext.Ptr("dyno-prod")
}
if strings.Contains(title, "error on dyno-dev") {
return langext.Ptr("dyno-dev")
}
if strings.Contains(title, "error on wkk") {
return langext.Ptr("wkk")
}
if strings.Contains(title, "error on lbxdev") {
return langext.Ptr("lbxdev")
}
if strings.Contains(title, "error on lbxprod") {
return langext.Ptr("lbxprod")
}
if strings.Contains(*content, "on mscom") { if strings.Contains(*content, "on mscom") {
return langext.Ptr("mscom") return langext.Ptr("mscom")
@@ -664,7 +805,7 @@ func determineSenderName(user OldUser, oldmessage OldMessage, title string, cont
return langext.Ptr("bfb") return langext.Ptr("bfb")
} }
if strings.Contains(title, "balu-db") { if strings.Contains(title, "balu-db") {
return langext.Ptr("lbprod") return langext.Ptr("lbxprod")
} }
} }
@@ -762,6 +903,31 @@ func determineSenderName(user OldUser, oldmessage OldMessage, title string, cont
if strings.Contains(*content, "plantafelstaging.de") { if strings.Contains(*content, "plantafelstaging.de") {
return langext.Ptr("plantafeldev") return langext.Ptr("plantafeldev")
} }
if strings.Contains(title, "Update cert_mscom") {
return langext.Ptr("mscom")
}
if strings.Contains(title, "Update cert_bfb") {
return langext.Ptr("bfb")
}
if strings.Contains(title, "Update staging.app.reuse.me") {
return langext.Ptr("wkk")
}
if strings.Contains(title, "Update develop.app.reuse.me") {
return langext.Ptr("wkk")
}
if strings.Contains(title, "Update inoshop_staging_bfb") {
return langext.Ptr("inoshop")
}
if strings.Contains(title, "Update cert_portfoliomanager_main") {
return langext.Ptr("bfb-testserver")
}
if strings.Contains(title, "Update cert_pfm2") {
return langext.Ptr("bfb-testserver")
}
if strings.Contains(title, "Update cert_kaz_main") {
return langext.Ptr("bfb-testserver")
}
} }
if channame == "space-warning" { if channame == "space-warning" {
@@ -777,6 +943,9 @@ func determineSenderName(user OldUser, oldmessage OldMessage, title string, cont
if title == "statussrv" { if title == "statussrv" {
return langext.Ptr("statussrv") return langext.Ptr("statussrv")
} }
if title == "virmach01" {
return langext.Ptr("statussrv")
}
} }
if channame == "srv-backup" { if channame == "srv-backup" {
@@ -856,6 +1025,28 @@ func determineSenderName(user OldUser, oldmessage OldMessage, title string, cont
if strings.Contains(title, "Reboot lbxprod") { if strings.Contains(title, "Reboot lbxprod") {
return langext.Ptr("lbxprod") return langext.Ptr("lbxprod")
} }
if strings.Contains(title, "Reboot plantafeldev") {
return langext.Ptr("plantafeldev")
}
if strings.Contains(title, "Reboot plantafelstaging") {
return langext.Ptr("plantafelstaging")
}
if strings.Contains(title, "Reboot statussrv") {
return langext.Ptr("statussrv")
}
if strings.Contains(title, "Reboot heydyno-prod-01") {
return langext.Ptr("dyno-prod")
}
if strings.Contains(title, "Reboot heydyno-dev-01") {
return langext.Ptr("dyno-dev")
}
if strings.Contains(title, "Reboot lbxdev") {
return langext.Ptr("lbxdev")
}
if strings.Contains(langext.Coalesce(content, ""), "Server: 'firestopcloud'") {
return langext.Ptr("firestopcloud")
}
} }
if channame == "yt-tvc" { if channame == "yt-tvc" {
@@ -870,6 +1061,124 @@ func determineSenderName(user OldUser, oldmessage OldMessage, title string, cont
return langext.Ptr("mscom") return langext.Ptr("mscom")
} }
if channame == "backup-rr" {
if strings.Contains(title, "bfb/server-plantafelstaging") {
return langext.Ptr("plantafelstaging")
}
if strings.Contains(title, "bfb/server-plantafeldev") {
return langext.Ptr("plantafeldev")
}
if strings.Contains(title, "bfb/server-wkk") {
return langext.Ptr("wkk")
}
if strings.Contains(title, "bfb/holz100") {
return langext.Ptr("bfb")
}
if strings.Contains(title, "bfb/clockify") {
return langext.Ptr("bfb")
}
if strings.Contains(title, "bfb/gdapi") {
return langext.Ptr("bfb")
}
if strings.Contains(title, "mike/databases-mscom") {
return langext.Ptr("mscom")
}
if strings.Contains(title, "bfb/databases-bfb") {
return langext.Ptr("bfb")
}
if strings.Contains(title, "bfb/server-dynoprod") {
return langext.Ptr("dyno-prod")
}
if strings.Contains(title, "bfb/server-dynodev") {
return langext.Ptr("dyno-dev")
}
if strings.Contains(title, "bfb/server-baluprod") {
return langext.Ptr("lbxprod")
}
if strings.Contains(title, "bfb/server-baludev") {
return langext.Ptr("lbxdev")
}
if strings.Contains(title, "bfb/plantafeldigital") {
return langext.Ptr("plan-web-prod")
}
if strings.Contains(title, "mike/server-statussrv") {
return langext.Ptr("statussrv")
}
if strings.Contains(title, "mike/thunderbird") {
return langext.Ptr("niflheim-3")
}
if strings.Contains(title, "mike/seedbox") {
return langext.Ptr("sbox")
}
if strings.Contains(title, "bfb/balu") {
return langext.Ptr("lbxprod")
}
if strings.Contains(title, "mike/ext-git-graph") {
return langext.Ptr("mscom")
}
if strings.Contains(title, "bfb/server-bfbtest") {
return langext.Ptr("bfb-testserver")
}
if strings.Contains(title, "mike/server-mscom") {
return langext.Ptr("mscom")
}
if strings.Contains(title, "mike/database-statussrv") {
return langext.Ptr("statussrv")
}
if strings.Contains(title, "mike/server-mscom") {
return langext.Ptr("mscom")
}
if strings.Contains(title, "mike/server-inoshop") {
return langext.Ptr("inoshop")
}
if strings.Contains(title, "mike/server-bfb") {
return langext.Ptr("bfb")
}
if strings.Contains(title, "bfb/discord") {
return langext.Ptr("nifleim-3")
}
if strings.Contains(title, "bfb/server-bfbtest") {
return langext.Ptr("bfb-testserver")
}
if strings.Contains(title, "mike/ext-git-graph") {
return langext.Ptr("mscom")
}
if strings.Contains(title, "bfb/server-inoshop") {
return langext.Ptr("inoshop")
}
if strings.Contains(title, "bfb/server-bfb") {
return langext.Ptr("bfb")
}
if strings.Contains(title, "bfb/server-plantafelprod") {
return langext.Ptr("plan-web-prod")
}
if strings.Contains(title, "bfb/server-inoshop") {
return langext.Ptr("inoshop")
}
if strings.Contains(title, "mike/niflheim-3") {
return langext.Ptr("niflheim-3")
}
if strings.Contains(title, "mike/pihole") {
return langext.Ptr("pihole")
}
if strings.Contains(title, "bfb/server-firestopcloud") {
return langext.Ptr("firestopcloud")
}
if strings.Contains(title, "bfb/server-agentzero") {
return langext.Ptr("agentzero")
}
}
if channame == "backup" {
if strings.Contains(title, "mike/ext-git-graph") {
return langext.Ptr("mscom")
}
}
if channame == "spezi-alert" {
return langext.Ptr("mscom")
}
if title == "NCC Upload failed" || title == "NCC Upload successful" { if title == "NCC Upload failed" || title == "NCC Upload successful" {
return langext.Ptr("mscom") return langext.Ptr("mscom")
} }
@@ -878,16 +1187,29 @@ func determineSenderName(user OldUser, oldmessage OldMessage, title string, cont
return langext.Ptr("mscom") return langext.Ptr("mscom")
} }
if strings.Contains(title, "BFBackup VC migrate") {
return langext.Ptr("bfbackup")
}
if strings.Contains(title, "bfbackup job") { if strings.Contains(title, "bfbackup job") {
return langext.Ptr("bfbackup") return langext.Ptr("bfbackup")
} }
if strings.Contains(title, "bfbackup finished") {
return langext.Ptr("bfbackup")
}
if strings.Contains(title, "Repo migration of /volume1") { if strings.Contains(title, "Repo migration of /volume1") {
return langext.Ptr("bfbackup") return langext.Ptr("bfbackup")
} }
if channame == "docker-watch" {
return nil // okay
}
//fmt.Printf("Failed to determine sender of [%d] '%s' '%s'\n", oldmessage.ScnMessageId, oldmessage.Title, langext.Coalesce(oldmessage.Content, "<NULL>")) //fmt.Printf("Failed to determine sender of [%d] '%s' '%s'\n", oldmessage.ScnMessageId, oldmessage.Title, langext.Coalesce(oldmessage.Content, "<NULL>"))
fmt.Printf("Failed to determine sender of [%d] '%s'\n", oldmessage.ScnMessageId, oldmessage.Title)
fmt.Printf("%s", termext.Red(fmt.Sprintf("Failed to determine sender of [%d] '%s' -- '%s' -- '%s'\n", oldmessage.ScnMessageId, channame, title, langext.Coalesce(oldmessage.Content, "<NULL>"))))
return nil return nil
} }

View File

@@ -268,6 +268,7 @@ var configDev = func() Config {
GooglePackageName: confEnv("SCN_GOOG_PACKAGENAME"), GooglePackageName: confEnv("SCN_GOOG_PACKAGENAME"),
GoogleProProductID: confEnv("SCN_GOOG_PROPRODUCTID"), GoogleProProductID: confEnv("SCN_GOOG_PROPRODUCTID"),
Cors: true, Cors: true,
ReqLogEnabled: true,
ReqLogMaxBodySize: 2048, ReqLogMaxBodySize: 2048,
ReqLogHistoryMaxCount: 1638, ReqLogHistoryMaxCount: 1638,
ReqLogHistoryMaxDuration: timeext.FromDays(60), ReqLogHistoryMaxDuration: timeext.FromDays(60),
@@ -339,6 +340,7 @@ var configStag = func() Config {
GooglePackageName: confEnv("SCN_GOOG_PACKAGENAME"), GooglePackageName: confEnv("SCN_GOOG_PACKAGENAME"),
GoogleProProductID: confEnv("SCN_GOOG_PROPRODUCTID"), GoogleProProductID: confEnv("SCN_GOOG_PROPRODUCTID"),
Cors: true, Cors: true,
ReqLogEnabled: true,
ReqLogMaxBodySize: 2048, ReqLogMaxBodySize: 2048,
ReqLogHistoryMaxCount: 1638, ReqLogHistoryMaxCount: 1638,
ReqLogHistoryMaxDuration: timeext.FromDays(60), ReqLogHistoryMaxDuration: timeext.FromDays(60),
@@ -398,18 +400,19 @@ var configProd = func() Config {
ReturnRawErrors: false, ReturnRawErrors: false,
DummyFirebase: false, DummyFirebase: false,
FirebaseTokenURI: "https://oauth2.googleapis.com/token", FirebaseTokenURI: "https://oauth2.googleapis.com/token",
FirebaseProjectID: confEnv("SCN_SCN_FB_PROJECTID"), FirebaseProjectID: confEnv("SCN_FB_PROJECTID"),
FirebasePrivKeyID: confEnv("SCN_SCN_FB_PRIVATEKEYID"), FirebasePrivKeyID: confEnv("SCN_FB_PRIVATEKEYID"),
FirebaseClientMail: confEnv("SCN_SCN_FB_CLIENTEMAIL"), FirebaseClientMail: confEnv("SCN_FB_CLIENTEMAIL"),
FirebasePrivateKey: confEnv("SCN_SCN_FB_PRIVATEKEY"), FirebasePrivateKey: confEnv("SCN_FB_PRIVATEKEY"),
DummyGoogleAPI: false, DummyGoogleAPI: false,
GoogleAPITokenURI: "https://oauth2.googleapis.com/token", GoogleAPITokenURI: "https://oauth2.googleapis.com/token",
GoogleAPIPrivKeyID: confEnv("SCN_SCN_GOOG_PRIVATEKEYID"), GoogleAPIPrivKeyID: confEnv("SCN_GOOG_PRIVATEKEYID"),
GoogleAPIClientMail: confEnv("SCN_SCN_GOOG_CLIENTEMAIL"), GoogleAPIClientMail: confEnv("SCN_GOOG_CLIENTEMAIL"),
GoogleAPIPrivateKey: confEnv("SCN_SCN_GOOG_PRIVATEKEY"), GoogleAPIPrivateKey: confEnv("SCN_GOOG_PRIVATEKEY"),
GooglePackageName: confEnv("SCN_SCN_GOOG_PACKAGENAME"), GooglePackageName: confEnv("SCN_GOOG_PACKAGENAME"),
GoogleProProductID: confEnv("SCN_SCN_GOOG_PROPRODUCTID"), GoogleProProductID: confEnv("SCN_GOOG_PROPRODUCTID"),
Cors: true, Cors: true,
ReqLogEnabled: true,
ReqLogMaxBodySize: 2048, ReqLogMaxBodySize: 2048,
ReqLogHistoryMaxCount: 1638, ReqLogHistoryMaxCount: 1638,
ReqLogHistoryMaxDuration: timeext.FromDays(60), ReqLogHistoryMaxDuration: timeext.FromDays(60),
@@ -449,7 +452,7 @@ func confEnv(key string) string {
} }
func init() { func init() {
ns := os.Getenv("SCN_NAMESPACE") ns := os.Getenv("CONF_NS")
cfg, ok := GetConfig(ns) cfg, ok := GetConfig(ns)
if !ok { if !ok {

View File

@@ -4,6 +4,7 @@ import (
ct "blackforestbytes.com/simplecloudnotifier/db/cursortoken" ct "blackforestbytes.com/simplecloudnotifier/db/cursortoken"
"blackforestbytes.com/simplecloudnotifier/models" "blackforestbytes.com/simplecloudnotifier/models"
"database/sql" "database/sql"
"errors"
"gogs.mikescher.com/BlackForestBytes/goext/sq" "gogs.mikescher.com/BlackForestBytes/goext/sq"
"time" "time"
) )
@@ -149,3 +150,31 @@ func (db *Database) ListMessages(ctx TxContext, filter models.MessageFilter, pag
return data[0:*pageSize], outToken, nil return data[0:*pageSize], outToken, nil
} }
} }
func (db *Database) CountMessages(ctx TxContext, filter models.MessageFilter) (int64, error) {
tx, err := ctx.GetOrCreateTransaction(db)
if err != nil {
return 0, err
}
filterCond, filterJoin, prepParams, err := filter.SQL()
sqlQuery := "SELECT " + "COUNT(*)" + " FROM messages " + filterJoin + " WHERE ( " + filterCond + " ) "
rows, err := tx.Query(ctx, sqlQuery, prepParams)
if err != nil {
return 0, err
}
if !rows.Next() {
return 0, errors.New("COUNT query returned no results")
}
var countRes int64
err = rows.Scan(&countRes)
if err != nil {
return 0, err
}
return countRes, nil
}

View File

@@ -9,7 +9,7 @@ require (
github.com/jmoiron/sqlx v1.3.5 github.com/jmoiron/sqlx v1.3.5
github.com/mattn/go-sqlite3 v1.14.17 github.com/mattn/go-sqlite3 v1.14.17
github.com/rs/zerolog v1.29.1 github.com/rs/zerolog v1.29.1
gogs.mikescher.com/BlackForestBytes/goext v0.0.156 gogs.mikescher.com/BlackForestBytes/goext v0.0.163
gopkg.in/loremipsum.v1 v1.1.2 gopkg.in/loremipsum.v1 v1.1.2
) )
@@ -33,10 +33,11 @@ require (
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.11 // indirect github.com/ugorji/go/codec v1.2.11 // indirect
golang.org/x/arch v0.3.0 // indirect golang.org/x/arch v0.3.0 // indirect
golang.org/x/crypto v0.9.0 // indirect golang.org/x/crypto v0.10.0 // indirect
golang.org/x/net v0.10.0 // indirect golang.org/x/net v0.10.0 // indirect
golang.org/x/sys v0.8.0 // indirect golang.org/x/sys v0.9.0 // indirect
golang.org/x/text v0.9.0 // indirect golang.org/x/term v0.9.0 // indirect
golang.org/x/text v0.10.0 // indirect
google.golang.org/protobuf v1.30.0 // indirect google.golang.org/protobuf v1.30.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )

View File

@@ -81,15 +81,13 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
gogs.mikescher.com/BlackForestBytes/goext v0.0.155 h1:eFcWmq9OXk4yCw8mjuyz8+JWiUEqjeSGoWOHwO000co= gogs.mikescher.com/BlackForestBytes/goext v0.0.163 h1:GYC34wLOdBM/CgAov0AyznfHGd09Km106Ijmp8cZmp4=
gogs.mikescher.com/BlackForestBytes/goext v0.0.155/go.mod h1:bIcO9mw2CD03pmJcBaE5YCF6etG5u73OVkUa8Alx3D0= gogs.mikescher.com/BlackForestBytes/goext v0.0.163/go.mod h1:Tood+vqmPqS/meYRnUcGz837wqHkP8BykVpY1h8TWoI=
gogs.mikescher.com/BlackForestBytes/goext v0.0.156 h1:mRRyAkLQCGVbDfG1t+7Y3zTf1S4TNajENiRpX1KTGw8=
gogs.mikescher.com/BlackForestBytes/goext v0.0.156/go.mod h1:bIcO9mw2CD03pmJcBaE5YCF6etG5u73OVkUa8Alx3D0=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM=
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -97,10 +95,12 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= golang.org/x/term v0.9.0 h1:GRRCnKYhdQrD8kfRAdQ6Zcw1P0OcELxGLKJvtjVMZ28=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo=
golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58=
golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=

View File

@@ -9,6 +9,7 @@ import (
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"io" "io"
"net/http" "net/http"
"strings"
"time" "time"
) )
@@ -23,7 +24,9 @@ type AndroidPublisher struct {
func NewAndroidPublisherAPI(conf scn.Config) (AndroidPublisherClient, error) { func NewAndroidPublisherAPI(conf scn.Config) (AndroidPublisherClient, error) {
googauth, err := NewAuth(conf.GoogleAPITokenURI, conf.GoogleAPIPrivKeyID, conf.GoogleAPIClientMail, conf.GoogleAPIPrivateKey) pkey := strings.ReplaceAll(conf.GoogleAPIPrivateKey, "\\n", "\n")
googauth, err := NewAuth(conf.GoogleAPITokenURI, conf.GoogleAPIPrivKeyID, conf.GoogleAPIClientMail, pkey)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@@ -6,6 +6,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"gogs.mikescher.com/BlackForestBytes/goext/langext"
"gogs.mikescher.com/BlackForestBytes/goext/syncext" "gogs.mikescher.com/BlackForestBytes/goext/syncext"
"time" "time"
) )
@@ -156,7 +157,29 @@ func (j *DeliveryRetryJob) redeliver(ctx *logic.SimpleContext, delivery models.D
} }
} else { } else {
fcmDelivID, err := j.app.DeliverMessage(ctx, client, msg, nil) isCompatClient, err := j.app.Database.Primary.IsCompatClient(ctx, client.ClientID)
if err != nil {
log.Err(err).Str("MessageID", delivery.MessageID.String()).Str("ClientID", client.ClientID.String()).Msg("Failed to get <IsCompatClient>")
ctx.RollbackTransaction()
return
}
var titleOverride *string = nil
var msgidOverride *string = nil
if isCompatClient {
messageIdComp, err := j.app.Database.Primary.ConvertToCompatIDOrCreate(ctx, msg.MessageID.String(), "messageid")
if err != nil {
log.Err(err).Str("MessageID", delivery.MessageID.String()).Str("ClientID", client.ClientID.String()).Msg("Failed to query/create messageid")
ctx.RollbackTransaction()
return
}
titleOverride = langext.Ptr(j.app.CompatizeMessageTitle(ctx, msg))
msgidOverride = langext.Ptr(fmt.Sprintf("%d", messageIdComp))
}
fcmDelivID, err := j.app.DeliverMessage(ctx, client, msg, titleOverride, msgidOverride)
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 {

View File

@@ -13,6 +13,15 @@ import (
"time" "time"
) )
type TxContext interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key any) any
GetOrCreateTransaction(db db.DatabaseImpl) (sq.Tx, error)
}
type AppContext struct { type AppContext struct {
app *Application app *Application
inner context.Context inner context.Context

View File

@@ -9,6 +9,7 @@ import (
"blackforestbytes.com/simplecloudnotifier/push" "blackforestbytes.com/simplecloudnotifier/push"
"context" "context"
"errors" "errors"
"fmt"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding" "github.com/gin-gonic/gin/binding"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
@@ -28,6 +29,7 @@ import (
var rexWhitespaceStart = rext.W(regexp.MustCompile("^\\s+")) var rexWhitespaceStart = rext.W(regexp.MustCompile("^\\s+"))
var rexWhitespaceEnd = rext.W(regexp.MustCompile("\\s+$")) var rexWhitespaceEnd = rext.W(regexp.MustCompile("\\s+$"))
var rexNormalizeUsername = rext.W(regexp.MustCompile("[^[:alnum:]\\-_ ]")) var rexNormalizeUsername = rext.W(regexp.MustCompile("[^[:alnum:]\\-_ ]"))
var rexCompatTitleChannel = rext.W(regexp.MustCompile("^\\[(?P<channel>[A-Za-z\\-0-9_ ]+)] (?P<title>(.|\\r|\\n)+)$"))
type Application struct { type Application struct {
Config scn.Config Config scn.Config
@@ -262,6 +264,10 @@ func (app *Application) StartRequest(g *gin.Context, uri any, query any, body an
if err := g.ShouldBindWith(form, binding.Form); err != nil { if err := g.ShouldBindWith(form, binding.Form); err != nil {
return nil, langext.Ptr(ginresp.APIError(g, 400, apierr.BINDFAIL_BODY_PARAM, "Failed to read multipart-form", err)) return nil, langext.Ptr(ginresp.APIError(g, 400, apierr.BINDFAIL_BODY_PARAM, "Failed to read multipart-form", err))
} }
} else if g.ContentType() == "application/x-www-form-urlencoded" {
if err := g.ShouldBindWith(form, binding.Form); err != nil {
return nil, langext.Ptr(ginresp.APIError(g, 400, apierr.BINDFAIL_BODY_PARAM, "Failed to read urlencoded-form", err))
}
} else { } else {
if !ignoreWrongContentType { if !ignoreWrongContentType {
return nil, langext.Ptr(ginresp.APIError(g, 400, apierr.BINDFAIL_BODY_PARAM, "missing form body", nil)) return nil, langext.Ptr(ginresp.APIError(g, 400, apierr.BINDFAIL_BODY_PARAM, "missing form body", nil))
@@ -357,8 +363,8 @@ func (app *Application) NormalizeUsername(v string) string {
return strings.TrimSpace(v) return strings.TrimSpace(v)
} }
func (app *Application) DeliverMessage(ctx context.Context, client models.Client, msg models.Message, compatTitleOverride *string) (string, error) { func (app *Application) DeliverMessage(ctx context.Context, client models.Client, msg models.Message, compatTitleOverride *string, compatMsgIDOverride *string) (string, error) {
fcmDelivID, err := app.Pusher.SendNotification(ctx, client, msg, compatTitleOverride) fcmDelivID, err := app.Pusher.SendNotification(ctx, client, msg, compatTitleOverride, compatMsgIDOverride)
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 "", err return "", err
@@ -372,3 +378,20 @@ func (app *Application) InsertRequestLog(data models.RequestLog) {
log.Error().Msg("failed to insert request-log (queue full)") log.Error().Msg("failed to insert request-log (queue full)")
} }
} }
func (app *Application) CompatizeMessageTitle(ctx TxContext, msg models.Message) string {
if msg.ChannelInternalName == "main" {
if rexCompatTitleChannel.IsMatch(msg.Title) {
return "!" + msg.Title // channel in title ?!
}
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)
}

View File

@@ -129,6 +129,12 @@ type KeyTokenDB struct {
} }
func (k KeyTokenDB) Model() KeyToken { func (k KeyTokenDB) Model() KeyToken {
channels := make([]ChannelID, 0)
if strings.TrimSpace(k.Channels) != "" {
channels = langext.ArrMap(strings.Split(k.Channels, ";"), func(v string) ChannelID { return ChannelID(v) })
}
return KeyToken{ return KeyToken{
KeyTokenID: k.KeyTokenID, KeyTokenID: k.KeyTokenID,
Name: k.Name, Name: k.Name,
@@ -136,7 +142,7 @@ func (k KeyTokenDB) Model() KeyToken {
TimestampLastUsed: timeOptFromMilli(k.TimestampLastUsed), TimestampLastUsed: timeOptFromMilli(k.TimestampLastUsed),
OwnerUserID: k.OwnerUserID, OwnerUserID: k.OwnerUserID,
AllChannels: k.AllChannels, AllChannels: k.AllChannels,
Channels: langext.ArrMap(strings.Split(k.Channels, ";"), func(v string) ChannelID { return ChannelID(v) }), Channels: channels,
Token: k.Token, Token: k.Token,
Permissions: ParseTokenPermissionList(k.Permissions), Permissions: ParseTokenPermissionList(k.Permissions),
MessagesSent: k.MessagesSent, MessagesSent: k.MessagesSent,

View File

@@ -12,6 +12,6 @@ func NewDummy() NotificationClient {
return &DummyConnector{} return &DummyConnector{}
} }
func (d DummyConnector) SendNotification(ctx context.Context, client models.Client, msg models.Message, compatTitleOverride *string) (string, error) { func (d DummyConnector) SendNotification(ctx context.Context, client models.Client, msg models.Message, compatTitleOverride *string, compatMsgIDOverride *string) (string, error) {
return "%DUMMY%", nil return "%DUMMY%", nil
} }

View File

@@ -15,6 +15,7 @@ import (
"io" "io"
"net/http" "net/http"
"strconv" "strconv"
"strings"
"time" "time"
) )
@@ -29,7 +30,9 @@ type FirebaseConnector struct {
func NewFirebaseConn(conf scn.Config) (NotificationClient, error) { func NewFirebaseConn(conf scn.Config) (NotificationClient, error) {
fbauth, err := NewAuth(conf.FirebaseTokenURI, conf.FirebaseProjectID, conf.FirebaseClientMail, conf.FirebasePrivateKey) pkey := strings.ReplaceAll(conf.FirebasePrivateKey, "\\n", "\n")
fbauth, err := NewAuth(conf.FirebaseTokenURI, conf.FirebaseProjectID, conf.FirebaseClientMail, pkey)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -50,13 +53,13 @@ type Notification struct {
Priority int Priority int
} }
func (fb FirebaseConnector) SendNotification(ctx context.Context, client models.Client, msg models.Message, compatTitleOverride *string) (string, error) { func (fb FirebaseConnector) SendNotification(ctx context.Context, client models.Client, msg models.Message, compatTitleOverride *string, compatMsgIDOverride *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"
jsonBody := gin.H{ jsonBody := gin.H{
"data": gin.H{ "data": gin.H{
"scn_msg_id": msg.MessageID.String(), "scn_msg_id": langext.Coalesce(compatMsgIDOverride, msg.MessageID.String()),
"usr_msg_id": langext.Coalesce(msg.UserMessageID, ""), "usr_msg_id": langext.Coalesce(msg.UserMessageID, ""),
"client_id": client.ClientID.String(), "client_id": client.ClientID.String(),
"timestamp": strconv.FormatInt(msg.Timestamp().Unix(), 10), "timestamp": strconv.FormatInt(msg.Timestamp().Unix(), 10),
@@ -124,5 +127,7 @@ func (fb FirebaseConnector) SendNotification(ctx context.Context, client models.
return "", err return "", err
} }
log.Info().Msg(fmt.Sprintf("Sucessfully pushed notification %s", msg.MessageID))
return respBody.Name, nil return respBody.Name, nil
} }

View File

@@ -6,5 +6,5 @@ import (
) )
type NotificationClient interface { type NotificationClient interface {
SendNotification(ctx context.Context, client models.Client, msg models.Message, compatTitleOverride *string) (string, error) SendNotification(ctx context.Context, client models.Client, msg models.Message, compatTitleOverride *string, compatMsgIDOverride *string) (string, error)
} }

View File

@@ -11,6 +11,7 @@ type SinkData struct {
Message models.Message Message models.Message
Client models.Client Client models.Client
CompatTitleOverride *string CompatTitleOverride *string
CompatMsgIDOverride *string
} }
type TestSink struct { type TestSink struct {
@@ -25,7 +26,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, compatTitleOverride *string) (string, error) { func (d *TestSink) SendNotification(ctx context.Context, client models.Client, msg models.Message, compatTitleOverride *string, compatMsgIDOverride *string) (string, error) {
id, err := langext.NewHexUUID() id, err := langext.NewHexUUID()
if err != nil { if err != nil {
return "", err return "", err
@@ -37,6 +38,7 @@ func (d *TestSink) SendNotification(ctx context.Context, client models.Client, m
Message: msg, Message: msg,
Client: client, Client: client,
CompatTitleOverride: compatTitleOverride, CompatTitleOverride: compatTitleOverride,
CompatMsgIDOverride: compatMsgIDOverride,
}) })
return key, nil return key, nil

View File

@@ -6,7 +6,7 @@
"contact": {}, "contact": {},
"version": "2.0" "version": "2.0"
}, },
"host": "scn.blackforestbytes.com", "host": "simplecloudnotifier.de",
"basePath": "/", "basePath": "/",
"paths": { "paths": {
"/": { "/": {

View File

@@ -681,7 +681,7 @@ definitions:
username: username:
type: string type: string
type: object type: object
host: scn.blackforestbytes.com host: simplecloudnotifier.de
info: info:
contact: {} contact: {}
description: API for SCN description: API for SCN

View File

@@ -364,7 +364,7 @@ func TestCompatInfo(t *testing.T) {
tt.AssertEqual(t, "message", "ok", r2["message"]) tt.AssertEqual(t, "message", "ok", r2["message"])
tt.AssertEqual(t, "quota", 1, r2["quota"]) tt.AssertEqual(t, "quota", 1, r2["quota"])
tt.AssertEqual(t, "quota_max", 50, r2["quota_max"]) tt.AssertEqual(t, "quota_max", 50, r2["quota_max"])
tt.AssertEqual(t, "unack_count", 0, r2["unack_count"]) tt.AssertEqual(t, "unack_count", 1, r2["unack_count"])
tt.AssertEqual(t, "user_id", userid, r2["user_id"]) tt.AssertEqual(t, "user_id", userid, r2["user_id"])
tt.AssertEqual(t, "user_key", userkey, r2["user_key"]) tt.AssertEqual(t, "user_key", userkey, r2["user_key"])
@@ -491,7 +491,7 @@ func TestCompatUpdateUserKey(t *testing.T) {
tt.AssertEqual(t, "message", "ok", r1["message"]) tt.AssertEqual(t, "message", "ok", r1["message"])
tt.AssertEqual(t, "quota", 1, r1["quota"]) tt.AssertEqual(t, "quota", 1, r1["quota"])
tt.AssertEqual(t, "quota_max", 50, r1["quota_max"]) tt.AssertEqual(t, "quota_max", 50, r1["quota_max"])
tt.AssertEqual(t, "unack_count", 0, r1["unack_count"]) tt.AssertEqual(t, "unack_count", 1, r1["unack_count"])
tt.AssertEqual(t, "user_id", userid, r1["user_id"]) tt.AssertEqual(t, "user_id", userid, r1["user_id"])
tt.AssertEqual(t, "user_key", newkey, r1["user_key"]) tt.AssertEqual(t, "user_key", newkey, r1["user_key"])
@@ -528,7 +528,7 @@ func TestCompatUpdateFCM(t *testing.T) {
tt.AssertEqual(t, "message", "ok", r1["message"]) tt.AssertEqual(t, "message", "ok", r1["message"])
tt.AssertEqual(t, "quota", 1, r1["quota"]) tt.AssertEqual(t, "quota", 1, r1["quota"])
tt.AssertEqual(t, "quota_max", 50, r1["quota_max"]) tt.AssertEqual(t, "quota_max", 50, r1["quota_max"])
tt.AssertEqual(t, "unack_count", 0, r1["unack_count"]) tt.AssertEqual(t, "unack_count", 1, r1["unack_count"])
tt.AssertEqual(t, "user_id", userid, r1["user_id"]) tt.AssertEqual(t, "user_id", userid, r1["user_id"])
tt.AssertEqual(t, "user_key", newkey, r1["user_key"]) tt.AssertEqual(t, "user_key", newkey, r1["user_key"])
@@ -732,3 +732,90 @@ func TestCompatTitlePatch(t *testing.T) {
tt.AssertStrRepEqual(t, "msg.ovrTitle", "[TestChan] HelloWorld_001", pusher.Last().CompatTitleOverride) tt.AssertStrRepEqual(t, "msg.ovrTitle", "[TestChan] HelloWorld_001", pusher.Last().CompatTitleOverride)
} }
func TestCompatAckCount(t *testing.T) {
_, baseUrl, stop := tt.StartSimpleWebserver(t)
defer stop()
r0 := tt.RequestGet[gin.H](t, baseUrl, fmt.Sprintf("/api/register.php?fcm_token=%s&pro=%s&pro_token=%s", "DUMMY_FCM", "0", ""))
tt.AssertEqual(t, "success", true, r0["success"])
userid := int64(r0["user_id"].(float64))
userkey := r0["user_key"].(string)
{
ri1 := tt.RequestGet[gin.H](t, baseUrl, fmt.Sprintf("/api/info.php?user_id=%d&user_key=%s", userid, userkey))
tt.AssertEqual(t, "unack_count", 0, ri1["unack_count"])
}
r1 := tt.RequestPost[gin.H](t, baseUrl, "/send.php", tt.FormData{
"user_id": fmt.Sprintf("%d", userid),
"user_key": userkey,
"title": "my title 11 & x",
})
tt.AssertEqual(t, "success", true, r1["success"])
r1scnid := int64(r1["scn_msg_id"].(float64))
{
ri1 := tt.RequestGet[gin.H](t, baseUrl, fmt.Sprintf("/api/info.php?user_id=%d&user_key=%s", userid, userkey))
tt.AssertEqual(t, "unack_count", 1, ri1["unack_count"])
}
r2 := tt.RequestPost[gin.H](t, baseUrl, "/send.php", tt.FormData{
"user_id": fmt.Sprintf("%d", userid),
"user_key": userkey,
"title": "my title 11 & x",
})
tt.AssertEqual(t, "success", true, r2["success"])
r2scnid := int64(r2["scn_msg_id"].(float64))
{
ri1 := tt.RequestGet[gin.H](t, baseUrl, fmt.Sprintf("/api/info.php?user_id=%d&user_key=%s", userid, userkey))
tt.AssertEqual(t, "unack_count", 2, ri1["unack_count"])
}
{
ack := tt.RequestGet[gin.H](t, baseUrl, fmt.Sprintf("/api/ack.php?user_id=%d&user_key=%s&scn_msg_id=%d", userid, userkey, r1scnid))
tt.AssertEqual(t, "success", true, ack["success"])
tt.AssertEqual(t, "prev_ack", 0, ack["prev_ack"])
tt.AssertEqual(t, "new_ack", 1, ack["new_ack"])
tt.AssertEqual(t, "message", "ok", ack["message"])
}
{
ri1 := tt.RequestGet[gin.H](t, baseUrl, fmt.Sprintf("/api/info.php?user_id=%d&user_key=%s", userid, userkey))
tt.AssertEqual(t, "unack_count", 1, ri1["unack_count"])
}
{
ack := tt.RequestGet[gin.H](t, baseUrl, fmt.Sprintf("/api/ack.php?user_id=%d&user_key=%s&scn_msg_id=%d", userid, userkey, r1scnid))
tt.AssertEqual(t, "success", true, ack["success"])
tt.AssertEqual(t, "prev_ack", 1, ack["prev_ack"])
tt.AssertEqual(t, "new_ack", 1, ack["new_ack"])
tt.AssertEqual(t, "message", "ok", ack["message"])
}
{
ri1 := tt.RequestGet[gin.H](t, baseUrl, fmt.Sprintf("/api/info.php?user_id=%d&user_key=%s", userid, userkey))
tt.AssertEqual(t, "unack_count", 1, ri1["unack_count"])
}
{
ri1 := tt.RequestGet[gin.H](t, baseUrl, fmt.Sprintf("/api/info.php?user_id=%d&user_key=%s", userid, userkey))
tt.AssertEqual(t, "unack_count", 1, ri1["unack_count"])
}
{
ack := tt.RequestGet[gin.H](t, baseUrl, fmt.Sprintf("/api/ack.php?user_id=%d&user_key=%s&scn_msg_id=%d", userid, userkey, r2scnid))
tt.AssertEqual(t, "success", true, ack["success"])
tt.AssertEqual(t, "prev_ack", 0, ack["prev_ack"])
tt.AssertEqual(t, "new_ack", 1, ack["new_ack"])
tt.AssertEqual(t, "message", "ok", ack["message"])
}
{
ri1 := tt.RequestGet[gin.H](t, baseUrl, fmt.Sprintf("/api/info.php?user_id=%d&user_key=%s", userid, userkey))
tt.AssertEqual(t, "unack_count", 0, ri1["unack_count"])
}
}

View File

@@ -383,6 +383,11 @@ func TestTokenKeysDowngradeSelf(t *testing.T) {
data := tt.InitSingleData(t, ws) data := tt.InitSingleData(t, ws)
chan0 := tt.RequestAuthPost[gin.H](t, data.AdminKey, baseUrl, fmt.Sprintf("/api/v2/users/%s/channels", data.UID), gin.H{
"name": "testchan1",
})
chanid := fmt.Sprintf("%v", chan0["channel_id"])
type keyobj struct { type keyobj struct {
AllChannels bool `json:"all_channels"` AllChannels bool `json:"all_channels"`
Channels []string `json:"channels"` Channels []string `json:"channels"`
@@ -415,7 +420,7 @@ func TestTokenKeysDowngradeSelf(t *testing.T) {
}, 400, apierr.CANNOT_SELFUPDATE_KEY) }, 400, apierr.CANNOT_SELFUPDATE_KEY)
tt.RequestAuthPatchShouldFail(t, data.AdminKey, baseUrl, fmt.Sprintf("/api/v2/users/%s/keys/%s", data.UID, ak), gin.H{ tt.RequestAuthPatchShouldFail(t, data.AdminKey, baseUrl, fmt.Sprintf("/api/v2/users/%s/keys/%s", data.UID, ak), gin.H{
"channels": []string{"main"}, "channels": []string{chanid},
}, 400, apierr.CANNOT_SELFUPDATE_KEY) }, 400, apierr.CANNOT_SELFUPDATE_KEY)
tt.RequestAuthPatch[tt.Void](t, data.AdminKey, baseUrl, fmt.Sprintf("/api/v2/users/%s/keys/%s", data.UID, ak), gin.H{ tt.RequestAuthPatch[tt.Void](t, data.AdminKey, baseUrl, fmt.Sprintf("/api/v2/users/%s/keys/%s", data.UID, ak), gin.H{
@@ -606,3 +611,64 @@ func TestTokenKeysMessageCounter(t *testing.T) {
assertCounter(4, 4, 0) assertCounter(4, 4, 0)
} }
func TestTokenKeysCreateDefaultParam(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"`
Token string `json:"token"` // only in create
}
chan0 := tt.RequestAuthPost[gin.H](t, data.AdminKey, baseUrl, fmt.Sprintf("/api/v2/users/%s/channels", data.UID), gin.H{
"name": "testchan1",
})
chanid := fmt.Sprintf("%v", chan0["channel_id"])
{
key2 := tt.RequestAuthPost[keyobj](t, data.AdminKey, baseUrl, fmt.Sprintf("/api/v2/users/%s/keys", data.UID), gin.H{
"name": "K2",
"permissions": "CS",
})
tt.AssertEqual(t, "Name", "K2", key2.Name)
tt.AssertEqual(t, "Permissions", "CS", key2.Permissions)
tt.AssertEqual(t, "AllChannels", true, key2.AllChannels)
tt.AssertEqual(t, "Channels.Len", 0, len(key2.Channels))
}
{
key2 := tt.RequestAuthPost[keyobj](t, data.AdminKey, baseUrl, fmt.Sprintf("/api/v2/users/%s/keys", data.UID), gin.H{
"name": "K3",
"permissions": "CS",
"channels": []string{chanid},
})
tt.AssertEqual(t, "Name", "K3", key2.Name)
tt.AssertEqual(t, "Permissions", "CS", key2.Permissions)
tt.AssertEqual(t, "AllChannels", false, key2.AllChannels)
tt.AssertEqual(t, "Channels.Len", 1, len(key2.Channels))
}
{
key2 := tt.RequestAuthPost[keyobj](t, data.AdminKey, baseUrl, fmt.Sprintf("/api/v2/users/%s/keys", data.UID), gin.H{
"name": "K4",
"permissions": "CS",
"all_channels": false,
})
tt.AssertEqual(t, "Name", "K4", key2.Name)
tt.AssertEqual(t, "Permissions", "CS", key2.Permissions)
tt.AssertEqual(t, "AllChannels", false, key2.AllChannels)
tt.AssertEqual(t, "Channels.Len", 0, len(key2.Channels))
}
}

View File

@@ -300,7 +300,7 @@ pre, pre span
} }
.display_none { .display_none {
display: none; display: none !important;
} }
#theme-switch { #theme-switch {

View File

@@ -23,12 +23,12 @@
<div class="row responsive-label"> <div class="row responsive-label">
<div class="col-sm-12 col-md-3"><label for="uid" class="doc">UserID</label></div> <div class="col-sm-12 col-md-3"><label for="uid" class="doc">UserID</label></div>
<div class="col-sm-12 col-md"><input placeholder="UserID" id="uid" class="doc" type="number"></div> <div class="col-sm-12 col-md"><input placeholder="UserID" id="uid" class="doc" type="text" pattern="USR[A-Za-z0-9]{21}"></div>
</div> </div>
<div class="row responsive-label"> <div class="row responsive-label">
<div class="col-sm-12 col-md-3"><label for="ukey" class="doc">Authentification Key</label></div> <div class="col-sm-12 col-md-3"><label for="ukey" class="doc">Authentification Key</label></div>
<div class="col-sm-12 col-md"><input placeholder="Key" id="ukey" class="doc" type="text" maxlength="64"></div> <div class="col-sm-12 col-md"><input placeholder="Key" id="ukey" class="doc" type="text" pattern="[A-Za-z0-9]{64}"></div>
</div> </div>
<div class="row responsive-label"> <div class="row responsive-label">

View File

@@ -17,7 +17,6 @@ function send()
uid.classList.remove('input-invalid'); uid.classList.remove('input-invalid');
key.classList.remove('input-invalid'); key.classList.remove('input-invalid');
msg.classList.remove('input-invalid');
cnt.classList.remove('input-invalid'); cnt.classList.remove('input-invalid');
pio.classList.remove('input-invalid'); pio.classList.remove('input-invalid');
@@ -30,7 +29,7 @@ function send()
if (cha.value !== '') data.append('channel', cha.value); if (cha.value !== '') data.append('channel', cha.value);
let xhr = new XMLHttpRequest(); let xhr = new XMLHttpRequest();
xhr.open('POST', '/send.php', true); xhr.open('POST', '/', true);
xhr.onreadystatechange = function () xhr.onreadystatechange = function ()
{ {
if (xhr.readyState !== 4) return; if (xhr.readyState !== 4) return;

View File

@@ -1,8 +1,8 @@
<div class="highlight"><pre><span></span><span class="ch">#!/usr/bin/env bash</span> <div class="highlight"><pre><span></span><span class="ch">#!/usr/bin/env bash</span>
<span class="c1">#</span> <span class="c1">#</span>
<span class="c1"># Wrapper around SCN ( https://scn.blackforestbytes.com/ )</span> <span class="c1"># Wrapper around SCN ( https://simplecloudnotifier.de/ )</span>
<span class="c1"># ========================================================</span> <span class="c1"># ======================================================</span>
<span class="c1">#</span> <span class="c1">#</span>
<span class="c1"># ./scn_send [@channel] title [content] [priority]</span> <span class="c1"># ./scn_send [@channel] title [content] [priority]</span>
<span class="c1">#</span> <span class="c1">#</span>
@@ -36,7 +36,7 @@ usage<span class="o">()</span><span class="w"> </span><span class="o">{</span>
<span class="nv">args</span><span class="o">=(</span><span class="w"> </span><span class="s2">&quot;</span><span class="nv">$@</span><span class="s2">&quot;</span><span class="w"> </span><span class="o">)</span> <span class="nv">args</span><span class="o">=(</span><span class="w"> </span><span class="s2">&quot;</span><span class="nv">$@</span><span class="s2">&quot;</span><span class="w"> </span><span class="o">)</span>
<span class="nv">title</span><span class="o">=</span><span class="nv">$1</span> <span class="nv">title</span><span class="o">=</span><span class="s2">&quot;&quot;</span>
<span class="nv">content</span><span class="o">=</span><span class="s2">&quot;&quot;</span> <span class="nv">content</span><span class="o">=</span><span class="s2">&quot;&quot;</span>
<span class="nv">channel</span><span class="o">=</span><span class="s2">&quot;&quot;</span> <span class="nv">channel</span><span class="o">=</span><span class="s2">&quot;&quot;</span>
<span class="nv">priority</span><span class="o">=</span><span class="m">1</span> <span class="nv">priority</span><span class="o">=</span><span class="m">1</span>
@@ -52,7 +52,7 @@ usage<span class="o">()</span><span class="w"> </span><span class="o">{</span>
<span class="k">if</span><span class="w"> </span><span class="o">[[</span><span class="w"> </span><span class="s2">&quot;</span><span class="si">${</span><span class="nv">args</span><span class="p">[0]</span><span class="si">}</span><span class="s2">&quot;</span><span class="w"> </span><span class="o">=</span>~<span class="w"> </span>^@.*<span class="w"> </span><span class="o">]]</span><span class="p">;</span><span class="w"> </span><span class="k">then</span> <span class="k">if</span><span class="w"> </span><span class="o">[[</span><span class="w"> </span><span class="s2">&quot;</span><span class="si">${</span><span class="nv">args</span><span class="p">[0]</span><span class="si">}</span><span class="s2">&quot;</span><span class="w"> </span><span class="o">=</span>~<span class="w"> </span>^@.*<span class="w"> </span><span class="o">]]</span><span class="p">;</span><span class="w"> </span><span class="k">then</span>
<span class="w"> </span><span class="nv">channel</span><span class="o">=</span><span class="s2">&quot;</span><span class="si">${</span><span class="nv">args</span><span class="p">[0]</span><span class="si">}</span><span class="s2">&quot;</span> <span class="w"> </span><span class="nv">channel</span><span class="o">=</span><span class="s2">&quot;</span><span class="si">${</span><span class="nv">args</span><span class="p">[0]</span><span class="si">}</span><span class="s2">&quot;</span>
<span class="w"> </span><span class="nb">unset</span><span class="w"> </span><span class="s2">&quot;args[0]&quot;</span> <span class="w"> </span><span class="nv">args</span><span class="o">=(</span><span class="s2">&quot;</span><span class="si">${</span><span class="nv">args</span><span class="p">[@]:</span><span class="nv">1</span><span class="si">}</span><span class="s2">&quot;</span><span class="o">)</span>
<span class="w"> </span><span class="nv">channel</span><span class="o">=</span><span class="s2">&quot;</span><span class="si">${</span><span class="nv">channel</span><span class="p">:</span><span class="nv">1</span><span class="si">}</span><span class="s2">&quot;</span> <span class="w"> </span><span class="nv">channel</span><span class="o">=</span><span class="s2">&quot;</span><span class="si">${</span><span class="nv">channel</span><span class="p">:</span><span class="nv">1</span><span class="si">}</span><span class="s2">&quot;</span>
<span class="k">fi</span> <span class="k">fi</span>
@@ -63,24 +63,49 @@ usage<span class="o">()</span><span class="w"> </span><span class="o">{</span>
<span class="k">fi</span> <span class="k">fi</span>
<span class="nv">title</span><span class="o">=</span><span class="s2">&quot;</span><span class="si">${</span><span class="nv">args</span><span class="p">[0]</span><span class="si">}</span><span class="s2">&quot;</span> <span class="nv">title</span><span class="o">=</span><span class="s2">&quot;</span><span class="si">${</span><span class="nv">args</span><span class="p">[0]</span><span class="si">}</span><span class="s2">&quot;</span>
<span class="nv">args</span><span class="o">=(</span><span class="s2">&quot;</span><span class="si">${</span><span class="nv">args</span><span class="p">[@]:</span><span class="nv">1</span><span class="si">}</span><span class="s2">&quot;</span><span class="o">)</span>
<span class="nv">content</span><span class="o">=</span><span class="s2">&quot;&quot;</span> <span class="nv">content</span><span class="o">=</span><span class="s2">&quot;&quot;</span>
<span class="k">if</span><span class="w"> </span><span class="o">[</span><span class="w"> </span><span class="si">${#</span><span class="nv">args</span><span class="p">[@]</span><span class="si">}</span><span class="w"> </span>-gt<span class="w"> </span><span class="m">1</span><span class="w"> </span><span class="o">]</span><span class="p">;</span><span class="w"> </span><span class="k">then</span> <span class="k">if</span><span class="w"> </span><span class="o">[</span><span class="w"> </span><span class="si">${#</span><span class="nv">args</span><span class="p">[@]</span><span class="si">}</span><span class="w"> </span>-gt<span class="w"> </span><span class="m">0</span><span class="w"> </span><span class="o">]</span><span class="p">;</span><span class="w"> </span><span class="k">then</span>
<span class="w"> </span><span class="nv">content</span><span class="o">=</span><span class="s2">&quot;</span><span class="si">${</span><span class="nv">args</span><span class="p">[0]</span><span class="si">}</span><span class="s2">&quot;</span> <span class="w"> </span><span class="nv">content</span><span class="o">=</span><span class="s2">&quot;</span><span class="si">${</span><span class="nv">args</span><span class="p">[0]</span><span class="si">}</span><span class="s2">&quot;</span>
<span class="w"> </span><span class="nb">unset</span><span class="w"> </span><span class="s2">&quot;args[0]&quot;</span> <span class="w"> </span><span class="nv">args</span><span class="o">=(</span><span class="s2">&quot;</span><span class="si">${</span><span class="nv">args</span><span class="p">[@]:</span><span class="nv">1</span><span class="si">}</span><span class="s2">&quot;</span><span class="o">)</span>
<span class="k">fi</span> <span class="k">fi</span>
<span class="k">if</span><span class="w"> </span><span class="o">[</span><span class="w"> </span><span class="si">${#</span><span class="nv">args</span><span class="p">[@]</span><span class="si">}</span><span class="w"> </span>-gt<span class="w"> </span><span class="m">1</span><span class="w"> </span><span class="o">]</span><span class="p">;</span><span class="w"> </span><span class="k">then</span> <span class="k">if</span><span class="w"> </span><span class="o">[</span><span class="w"> </span><span class="si">${#</span><span class="nv">args</span><span class="p">[@]</span><span class="si">}</span><span class="w"> </span>-gt<span class="w"> </span><span class="m">0</span><span class="w"> </span><span class="o">]</span><span class="p">;</span><span class="w"> </span><span class="k">then</span>
<span class="w"> </span><span class="nv">priority</span><span class="o">=</span><span class="s2">&quot;</span><span class="si">${</span><span class="nv">args</span><span class="p">[0]</span><span class="si">}</span><span class="s2">&quot;</span> <span class="w"> </span><span class="nv">priority</span><span class="o">=</span><span class="s2">&quot;</span><span class="si">${</span><span class="nv">args</span><span class="p">[0]</span><span class="si">}</span><span class="s2">&quot;</span>
<span class="w"> </span><span class="nb">unset</span><span class="w"> </span><span class="s2">&quot;args[0]&quot;</span> <span class="w"> </span><span class="nv">args</span><span class="o">=(</span><span class="s2">&quot;</span><span class="si">${</span><span class="nv">args</span><span class="p">[@]:</span><span class="nv">1</span><span class="si">}</span><span class="s2">&quot;</span><span class="o">)</span>
<span class="k">fi</span> <span class="k">fi</span>
<span class="k">if</span><span class="w"> </span><span class="o">[</span><span class="w"> </span><span class="si">${#</span><span class="nv">args</span><span class="p">[@]</span><span class="si">}</span><span class="w"> </span>-gt<span class="w"> </span><span class="m">1</span><span class="w"> </span><span class="o">]</span><span class="p">;</span><span class="w"> </span><span class="k">then</span> <span class="k">if</span><span class="w"> </span><span class="o">[</span><span class="w"> </span><span class="si">${#</span><span class="nv">args</span><span class="p">[@]</span><span class="si">}</span><span class="w"> </span>-gt<span class="w"> </span><span class="m">0</span><span class="w"> </span><span class="o">]</span><span class="p">;</span><span class="w"> </span><span class="k">then</span>
<span class="w"> </span>rederr<span class="w"> </span><span class="s2">&quot;Too many arguments to scn_send&quot;</span> <span class="w"> </span>rederr<span class="w"> </span><span class="s2">&quot;Too many arguments to scn_send&quot;</span>
<span class="w"> </span>usage <span class="w"> </span>usage
<span class="w"> </span><span class="nb">exit</span><span class="w"> </span><span class="m">1</span> <span class="w"> </span><span class="nb">exit</span><span class="w"> </span><span class="m">1</span>
<span class="k">fi</span> <span class="k">fi</span>
<span class="nv">curlparams</span><span class="o">=()</span>
<span class="nv">curlparams</span><span class="o">+=(</span><span class="w"> </span><span class="s2">&quot;--data&quot;</span><span class="w"> </span><span class="s2">&quot;user_id=</span><span class="si">${</span><span class="nv">SCN_UID</span><span class="si">}</span><span class="s2">&quot;</span><span class="w"> </span><span class="o">)</span>
<span class="nv">curlparams</span><span class="o">+=(</span><span class="w"> </span><span class="s2">&quot;--data&quot;</span><span class="w"> </span><span class="s2">&quot;key=</span><span class="si">${</span><span class="nv">SCN_KEY</span><span class="si">}</span><span class="s2">&quot;</span><span class="w"> </span><span class="o">)</span>
<span class="nv">curlparams</span><span class="o">+=(</span><span class="w"> </span><span class="s2">&quot;--data&quot;</span><span class="w"> </span><span class="s2">&quot;title=</span><span class="nv">$title</span><span class="s2">&quot;</span><span class="w"> </span><span class="o">)</span>
<span class="nv">curlparams</span><span class="o">+=(</span><span class="w"> </span><span class="s2">&quot;--data&quot;</span><span class="w"> </span><span class="s2">&quot;timestamp=</span><span class="nv">$sendtime</span><span class="s2">&quot;</span><span class="w"> </span><span class="o">)</span>
<span class="nv">curlparams</span><span class="o">+=(</span><span class="w"> </span><span class="s2">&quot;--data&quot;</span><span class="w"> </span><span class="s2">&quot;msg_id=</span><span class="nv">$usr_msg_id</span><span class="s2">&quot;</span><span class="w"> </span><span class="o">)</span>
<span class="k">if</span><span class="w"> </span><span class="o">[[</span><span class="w"> </span>-n<span class="w"> </span><span class="s2">&quot;</span><span class="nv">$content</span><span class="s2">&quot;</span><span class="w"> </span><span class="o">]]</span><span class="p">;</span><span class="w"> </span><span class="k">then</span>
<span class="w"> </span><span class="nv">curlparams</span><span class="o">+=(</span><span class="s2">&quot;--data&quot;</span><span class="w"> </span><span class="s2">&quot;content=</span><span class="nv">$content</span><span class="s2">&quot;</span><span class="o">)</span>
<span class="k">fi</span>
<span class="k">if</span><span class="w"> </span><span class="o">[[</span><span class="w"> </span>-n<span class="w"> </span><span class="s2">&quot;</span><span class="nv">$priority</span><span class="s2">&quot;</span><span class="w"> </span><span class="o">]]</span><span class="p">;</span><span class="w"> </span><span class="k">then</span>
<span class="w"> </span><span class="nv">curlparams</span><span class="o">+=(</span><span class="s2">&quot;--data&quot;</span><span class="w"> </span><span class="s2">&quot;priority=</span><span class="nv">$priority</span><span class="s2">&quot;</span><span class="o">)</span>
<span class="k">fi</span>
<span class="k">if</span><span class="w"> </span><span class="o">[[</span><span class="w"> </span>-n<span class="w"> </span><span class="s2">&quot;</span><span class="nv">$channel</span><span class="s2">&quot;</span><span class="w"> </span><span class="o">]]</span><span class="p">;</span><span class="w"> </span><span class="k">then</span>
<span class="w"> </span><span class="nv">curlparams</span><span class="o">+=(</span><span class="s2">&quot;--data&quot;</span><span class="w"> </span><span class="s2">&quot;channel=</span><span class="nv">$channel</span><span class="s2">&quot;</span><span class="o">)</span>
<span class="k">fi</span>
<span class="k">if</span><span class="w"> </span><span class="o">[[</span><span class="w"> </span>-n<span class="w"> </span><span class="s2">&quot;</span><span class="nv">$sender</span><span class="s2">&quot;</span><span class="w"> </span><span class="o">]]</span><span class="p">;</span><span class="w"> </span><span class="k">then</span>
<span class="w"> </span><span class="nv">curlparams</span><span class="o">+=(</span><span class="s2">&quot;--data&quot;</span><span class="w"> </span><span class="s2">&quot;sender_name=</span><span class="nv">$sender</span><span class="s2">&quot;</span><span class="o">)</span>
<span class="k">fi</span>
<span class="k">while</span><span class="w"> </span><span class="nb">true</span><span class="w"> </span><span class="p">;</span><span class="w"> </span><span class="k">do</span> <span class="k">while</span><span class="w"> </span><span class="nb">true</span><span class="w"> </span><span class="p">;</span><span class="w"> </span><span class="k">do</span>
@@ -89,16 +114,8 @@ usage<span class="o">()</span><span class="w"> </span><span class="o">{</span>
<span class="w"> </span><span class="nv">curlresp</span><span class="o">=</span><span class="k">$(</span>curl<span class="w"> </span>--silent<span class="w"> </span><span class="se">\</span> <span class="w"> </span><span class="nv">curlresp</span><span class="o">=</span><span class="k">$(</span>curl<span class="w"> </span>--silent<span class="w"> </span><span class="se">\</span>
<span class="w"> </span>--output<span class="w"> </span><span class="s2">&quot;</span><span class="si">${</span><span class="nv">outf</span><span class="si">}</span><span class="s2">&quot;</span><span class="w"> </span><span class="se">\</span> <span class="w"> </span>--output<span class="w"> </span><span class="s2">&quot;</span><span class="si">${</span><span class="nv">outf</span><span class="si">}</span><span class="s2">&quot;</span><span class="w"> </span><span class="se">\</span>
<span class="w"> </span>--write-out<span class="w"> </span><span class="s2">&quot;%{http_code}&quot;</span><span class="w"> </span><span class="se">\</span> <span class="w"> </span>--write-out<span class="w"> </span><span class="s2">&quot;%{http_code}&quot;</span><span class="w"> </span><span class="se">\</span>
<span class="w"> </span>--data<span class="w"> </span><span class="s2">&quot;user_id=</span><span class="nv">$user_id</span><span class="s2">&quot;</span><span class="w"> </span><span class="se">\</span> <span class="w"> </span><span class="s2">&quot;</span><span class="si">${</span><span class="nv">curlparams</span><span class="p">[@]</span><span class="si">}</span><span class="s2">&quot;</span><span class="w"> </span><span class="se">\</span>
<span class="w"> </span>--data<span class="w"> </span><span class="s2">&quot;key=</span><span class="nv">$user_key</span><span class="s2">&quot;</span><span class="w"> </span><span class="se">\</span> <span class="w"> </span><span class="s2">&quot;https://simplecloudnotifier.de/&quot;</span><span class="w"> </span><span class="k">)</span>
<span class="w"> </span>--data<span class="w"> </span><span class="s2">&quot;title=</span><span class="nv">$title</span><span class="s2">&quot;</span><span class="w"> </span><span class="se">\</span>
<span class="w"> </span>--data<span class="w"> </span><span class="s2">&quot;timestamp=</span><span class="nv">$sendtime</span><span class="s2">&quot;</span><span class="w"> </span><span class="se">\</span>
<span class="w"> </span>--data<span class="w"> </span><span class="s2">&quot;content=</span><span class="nv">$content</span><span class="s2">&quot;</span><span class="w"> </span><span class="se">\</span>
<span class="w"> </span>--data<span class="w"> </span><span class="s2">&quot;priority=</span><span class="nv">$priority</span><span class="s2">&quot;</span><span class="w"> </span><span class="se">\</span>
<span class="w"> </span>--data<span class="w"> </span><span class="s2">&quot;msg_id=</span><span class="nv">$usr_msg_id</span><span class="s2">&quot;</span><span class="w"> </span><span class="se">\</span>
<span class="w"> </span>--data<span class="w"> </span><span class="s2">&quot;channel=</span><span class="nv">$channel</span><span class="s2">&quot;</span><span class="w"> </span><span class="se">\</span>
<span class="w"> </span>--data<span class="w"> </span><span class="s2">&quot;sender_name=</span><span class="nv">$sender</span><span class="s2">&quot;</span><span class="w"> </span><span class="se">\</span>
<span class="w"> </span><span class="s2">&quot;https://scn.blackforestbytes.com/&quot;</span><span class="w"> </span><span class="k">)</span>
<span class="w"> </span><span class="nv">curlout</span><span class="o">=</span><span class="s2">&quot;</span><span class="k">$(</span>cat<span class="w"> </span><span class="s2">&quot;</span><span class="nv">$outf</span><span class="s2">&quot;</span><span class="k">)</span><span class="s2">&quot;</span> <span class="w"> </span><span class="nv">curlout</span><span class="o">=</span><span class="s2">&quot;</span><span class="k">$(</span>cat<span class="w"> </span><span class="s2">&quot;</span><span class="nv">$outf</span><span class="s2">&quot;</span><span class="k">)</span><span class="s2">&quot;</span>
<span class="w"> </span>rm<span class="w"> </span><span class="s2">&quot;</span><span class="nv">$outf</span><span class="s2">&quot;</span> <span class="w"> </span>rm<span class="w"> </span><span class="s2">&quot;</span><span class="nv">$outf</span><span class="s2">&quot;</span>

View File

@@ -1,8 +1,8 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# #
# Wrapper around SCN ( https://scn.blackforestbytes.com/ ) # Wrapper around SCN ( https://simplecloudnotifier.de/ )
# ======================================================== # ======================================================
# #
# ./scn_send [@channel] title [content] [priority] # ./scn_send [@channel] title [content] [priority]
# #
@@ -36,7 +36,7 @@ function green() { if cfgcol; then echo -e "\x1B[32m$1\x1B[0m"; else ec
args=( "$@" ) args=( "$@" )
title=$1 title=""
content="" content=""
channel="" channel=""
priority=1 priority=1
@@ -52,7 +52,7 @@ fi
if [[ "${args[0]}" =~ ^@.* ]]; then if [[ "${args[0]}" =~ ^@.* ]]; then
channel="${args[0]}" channel="${args[0]}"
unset "args[0]" args=("${args[@]:1}")
channel="${channel:1}" channel="${channel:1}"
fi fi
@@ -63,24 +63,49 @@ if [ ${#args[@]} -lt 1 ]; then
fi fi
title="${args[0]}" title="${args[0]}"
args=("${args[@]:1}")
content="" content=""
if [ ${#args[@]} -gt 1 ]; then if [ ${#args[@]} -gt 0 ]; then
content="${args[0]}" content="${args[0]}"
unset "args[0]" args=("${args[@]:1}")
fi fi
if [ ${#args[@]} -gt 1 ]; then if [ ${#args[@]} -gt 0 ]; then
priority="${args[0]}" priority="${args[0]}"
unset "args[0]" args=("${args[@]:1}")
fi fi
if [ ${#args[@]} -gt 1 ]; then if [ ${#args[@]} -gt 0 ]; then
rederr "Too many arguments to scn_send" rederr "Too many arguments to scn_send"
usage usage
exit 1 exit 1
fi fi
curlparams=()
curlparams+=( "--data" "user_id=${SCN_UID}" )
curlparams+=( "--data" "key=${SCN_KEY}" )
curlparams+=( "--data" "title=$title" )
curlparams+=( "--data" "timestamp=$sendtime" )
curlparams+=( "--data" "msg_id=$usr_msg_id" )
if [[ -n "$content" ]]; then
curlparams+=("--data" "content=$content")
fi
if [[ -n "$priority" ]]; then
curlparams+=("--data" "priority=$priority")
fi
if [[ -n "$channel" ]]; then
curlparams+=("--data" "channel=$channel")
fi
if [[ -n "$sender" ]]; then
curlparams+=("--data" "sender_name=$sender")
fi
while true ; do while true ; do
@@ -89,16 +114,8 @@ while true ; do
curlresp=$(curl --silent \ curlresp=$(curl --silent \
--output "${outf}" \ --output "${outf}" \
--write-out "%{http_code}" \ --write-out "%{http_code}" \
--data "user_id=$user_id" \ "${curlparams[@]}" \
--data "key=$user_key" \ "https://simplecloudnotifier.de/" )
--data "title=$title" \
--data "timestamp=$sendtime" \
--data "content=$content" \
--data "priority=$priority" \
--data "msg_id=$usr_msg_id" \
--data "channel=$channel" \
--data "sender_name=$sender" \
"https://scn.blackforestbytes.com/" )
curlout="$(cat "$outf")" curlout="$(cat "$outf")"
rm "$outf" rm "$outf"