Move to string-ids for all entities (compat translation for existing data)

This commit is contained in:
Mike Schwörer 2023-01-14 00:48:51 +01:00
parent acd7de0dee
commit 82bc887767
Signed by: Mikescher
GPG Key ID: D3C7172E0A70F8CF
42 changed files with 1218 additions and 541 deletions

View File

@ -7,5 +7,6 @@
<option name="processLiterals" value="true" />
<option name="processComments" value="true" />
</inspection_tool>
<inspection_tool class="SqlRedundantOrderingDirectionInspection" enabled="false" level="WARNING" enabled_by_default="false" />
</profile>
</component>

View File

@ -23,6 +23,8 @@
-> how does it work with existing data?
-> do i care, there are only 2 active users... (are there?)
- convert existing user-ids on compat /send endpoint
- error logging as goroutine, gets all errors via channel,
(channel buffered - nonblocking send, second channel that gets a message when sender failed )
(then all errors end up in _second_ sqlite table)
@ -57,10 +59,14 @@
- (?) desktop client for notifications
- (?) add querylog (similar to requestlog/errorlog) - only for main-db
#### LATER
- Pagination for ListChannels / ListSubscriptions / ListClients / ListChannelSubscriptions / ListUserSubscriptions
- weblogin, webapp, ...
- cannot open sqlite in dbbrowsr (cannot parse schema?)
-> https://github.com/sqlitebrowser/sqlitebrowser/issues/292 -> https://github.com/sqlitebrowser/sqlitebrowser/issues/29266
- Pagination for ListChannels / ListSubscriptions / ListClients / ListChannelSubscriptions / ListUserSubscriptions
- cannot open sqlite in dbbrowsr (cannot parse schema?)
-> https://github.com/sqlitebrowser/sqlitebrowser/issues/292 -> https://github.com/sqlitebrowser/sqlitebrowser/issues/29266

View File

@ -151,7 +151,7 @@ func (h APIHandler) CreateUser(g *gin.Context) ginresp.HTTPResponse {
// @Router /api/users/{uid} [GET]
func (h APIHandler) GetUser(g *gin.Context) ginresp.HTTPResponse {
type uri struct {
UserID models.UserID `uri:"uid"`
UserID models.UserID `uri:"uid" binding:"entityid"`
}
var u uri
@ -200,7 +200,7 @@ func (h APIHandler) GetUser(g *gin.Context) ginresp.HTTPResponse {
// @Router /api/users/{uid} [PATCH]
func (h APIHandler) UpdateUser(g *gin.Context) ginresp.HTTPResponse {
type uri struct {
UserID models.UserID `uri:"uid"`
UserID models.UserID `uri:"uid" binding:"entityid"`
}
type body struct {
Username *string `json:"username"`
@ -306,7 +306,7 @@ func (h APIHandler) UpdateUser(g *gin.Context) ginresp.HTTPResponse {
// @Router /api/users/{uid}/clients [GET]
func (h APIHandler) ListClients(g *gin.Context) ginresp.HTTPResponse {
type uri struct {
UserID models.UserID `uri:"uid"`
UserID models.UserID `uri:"uid" binding:"entityid"`
}
type response struct {
Clients []models.ClientJSON `json:"clients"`
@ -351,8 +351,8 @@ func (h APIHandler) ListClients(g *gin.Context) ginresp.HTTPResponse {
// @Router /api/users/{uid}/clients/{cid} [GET]
func (h APIHandler) GetClient(g *gin.Context) ginresp.HTTPResponse {
type uri struct {
UserID models.UserID `uri:"uid"`
ClientID models.ClientID `uri:"cid"`
UserID models.UserID `uri:"uid" binding:"entityid"`
ClientID models.ClientID `uri:"cid" binding:"entityid"`
}
var u uri
@ -395,7 +395,7 @@ func (h APIHandler) GetClient(g *gin.Context) ginresp.HTTPResponse {
// @Router /api/users/{uid}/clients [POST]
func (h APIHandler) AddClient(g *gin.Context) ginresp.HTTPResponse {
type uri struct {
UserID models.UserID `uri:"uid"`
UserID models.UserID `uri:"uid" binding:"entityid"`
}
type body struct {
FCMToken string `json:"fcm_token" binding:"required"`
@ -456,8 +456,8 @@ func (h APIHandler) AddClient(g *gin.Context) ginresp.HTTPResponse {
// @Router /api/users/{uid}/clients/{cid} [DELETE]
func (h APIHandler) DeleteClient(g *gin.Context) ginresp.HTTPResponse {
type uri struct {
UserID models.UserID `uri:"uid"`
ClientID models.ClientID `uri:"cid"`
UserID models.UserID `uri:"uid" binding:"entityid"`
ClientID models.ClientID `uri:"cid" binding:"entityid"`
}
var u uri
@ -511,7 +511,7 @@ func (h APIHandler) DeleteClient(g *gin.Context) ginresp.HTTPResponse {
// @Router /api/users/{uid}/channels [GET]
func (h APIHandler) ListChannels(g *gin.Context) ginresp.HTTPResponse {
type uri struct {
UserID models.UserID `uri:"uid"`
UserID models.UserID `uri:"uid" binding:"entityid"`
}
type query struct {
Selector *string `json:"selector" form:"selector" enums:"owned,subscribed_any,all_any,subscribed,all"`
@ -603,8 +603,8 @@ func (h APIHandler) ListChannels(g *gin.Context) ginresp.HTTPResponse {
// @Router /api/users/{uid}/channels/{cid} [GET]
func (h APIHandler) GetChannel(g *gin.Context) ginresp.HTTPResponse {
type uri struct {
UserID models.UserID `uri:"uid"`
ChannelID models.ChannelID `uri:"cid"`
UserID models.UserID `uri:"uid" binding:"entityid"`
ChannelID models.ChannelID `uri:"cid" binding:"entityid"`
}
var u uri
@ -647,7 +647,7 @@ func (h APIHandler) GetChannel(g *gin.Context) ginresp.HTTPResponse {
// @Router /api/users/{uid}/channels [POST]
func (h APIHandler) CreateChannel(g *gin.Context) ginresp.HTTPResponse {
type uri struct {
UserID models.UserID `uri:"uid"`
UserID models.UserID `uri:"uid" binding:"entityid"`
}
type body struct {
Name string `json:"name"`
@ -744,8 +744,8 @@ func (h APIHandler) CreateChannel(g *gin.Context) ginresp.HTTPResponse {
// @Router /api/users/{uid}/channels/{cid} [PATCH]
func (h APIHandler) UpdateChannel(g *gin.Context) ginresp.HTTPResponse {
type uri struct {
UserID models.UserID `uri:"uid"`
ChannelID models.ChannelID `uri:"cid"`
UserID models.UserID `uri:"uid" binding:"entityid"`
ChannelID models.ChannelID `uri:"cid" binding:"entityid"`
}
type body struct {
RefreshSubscribeKey *bool `json:"subscribe_key"`
@ -869,8 +869,8 @@ func (h APIHandler) UpdateChannel(g *gin.Context) ginresp.HTTPResponse {
// @Router /api/users/{uid}/channels/{cid}/messages [GET]
func (h APIHandler) ListChannelMessages(g *gin.Context) ginresp.HTTPResponse {
type uri struct {
ChannelUserID models.UserID `uri:"uid"`
ChannelID models.ChannelID `uri:"cid"`
ChannelUserID models.UserID `uri:"uid" binding:"entityid"`
ChannelID models.ChannelID `uri:"cid" binding:"entityid"`
}
type query struct {
PageSize *int `json:"page_size" form:"page_size"`
@ -972,7 +972,7 @@ func (h APIHandler) ListChannelMessages(g *gin.Context) ginresp.HTTPResponse {
// @Router /api/users/{uid}/subscriptions [GET]
func (h APIHandler) ListUserSubscriptions(g *gin.Context) ginresp.HTTPResponse {
type uri struct {
UserID models.UserID `uri:"uid"`
UserID models.UserID `uri:"uid" binding:"entityid"`
}
type query struct {
Selector *string `json:"selector" form:"selector" enums:"owner_all,owner_confirmed,owner_unconfirmed,incoming_all,incoming_confirmed,incoming_unconfirmed"`
@ -1069,8 +1069,8 @@ func (h APIHandler) ListUserSubscriptions(g *gin.Context) ginresp.HTTPResponse {
// @Router /api/users/{uid}/channels/{cid}/subscriptions [GET]
func (h APIHandler) ListChannelSubscriptions(g *gin.Context) ginresp.HTTPResponse {
type uri struct {
UserID models.UserID `uri:"uid"`
ChannelID models.ChannelID `uri:"cid"`
UserID models.UserID `uri:"uid" binding:"entityid"`
ChannelID models.ChannelID `uri:"cid" binding:"entityid"`
}
type response struct {
Subscriptions []models.SubscriptionJSON `json:"subscriptions"`
@ -1123,8 +1123,8 @@ func (h APIHandler) ListChannelSubscriptions(g *gin.Context) ginresp.HTTPRespons
// @Router /api/users/{uid}/subscriptions/{sid} [GET]
func (h APIHandler) GetSubscription(g *gin.Context) ginresp.HTTPResponse {
type uri struct {
UserID models.UserID `uri:"uid"`
SubscriptionID models.SubscriptionID `uri:"sid"`
UserID models.UserID `uri:"uid" binding:"entityid"`
SubscriptionID models.SubscriptionID `uri:"sid" binding:"entityid"`
}
var u uri
@ -1170,8 +1170,8 @@ func (h APIHandler) GetSubscription(g *gin.Context) ginresp.HTTPResponse {
// @Router /api/users/{uid}/subscriptions/{sid} [DELETE]
func (h APIHandler) CancelSubscription(g *gin.Context) ginresp.HTTPResponse {
type uri struct {
UserID models.UserID `uri:"uid"`
SubscriptionID models.SubscriptionID `uri:"sid"`
UserID models.UserID `uri:"uid" binding:"entityid"`
SubscriptionID models.SubscriptionID `uri:"sid" binding:"entityid"`
}
var u uri
@ -1223,12 +1223,12 @@ func (h APIHandler) CancelSubscription(g *gin.Context) ginresp.HTTPResponse {
// @Router /api/users/{uid}/subscriptions [POST]
func (h APIHandler) CreateSubscription(g *gin.Context) ginresp.HTTPResponse {
type uri struct {
UserID models.UserID `uri:"uid"`
UserID models.UserID `uri:"uid" binding:"entityid"`
}
type body struct {
ChannelOwnerUserID *models.UserID `json:"channel_owner_user_id"`
ChannelOwnerUserID *models.UserID `json:"channel_owner_user_id" binding:"entityid"`
ChannelInternalName *string `json:"channel_internal_name"`
ChannelID *models.ChannelID `json:"channel_id"`
ChannelID *models.ChannelID `json:"channel_id" binding:"entityid"`
}
type query struct {
ChanSubscribeKey *string `json:"chan_subscribe_key" form:"chan_subscribe_key"`
@ -1312,8 +1312,8 @@ func (h APIHandler) CreateSubscription(g *gin.Context) ginresp.HTTPResponse {
// @Router /api/users/{uid}/subscriptions/{sid} [PATCH]
func (h APIHandler) UpdateSubscription(g *gin.Context) ginresp.HTTPResponse {
type uri struct {
UserID models.UserID `uri:"uid"`
SubscriptionID models.SubscriptionID `uri:"sid"`
UserID models.UserID `uri:"uid" binding:"entityid"`
SubscriptionID models.SubscriptionID `uri:"sid" binding:"entityid"`
}
type body struct {
Confirmed *bool `form:"confirmed"`
@ -1454,7 +1454,7 @@ func (h APIHandler) ListMessages(g *gin.Context) ginresp.HTTPResponse {
// @ID api-messages-get
// @Tags API-v2
//
// @Param mid path int true "SCNMessageID"
// @Param mid path int true "MessageID"
//
// @Success 200 {object} models.MessageJSON
// @Failure 400 {object} ginresp.apiError "supplied values/parameters cannot be parsed / are invalid"
@ -1465,7 +1465,7 @@ func (h APIHandler) ListMessages(g *gin.Context) ginresp.HTTPResponse {
// @Router /api/messages/{mid} [PATCH]
func (h APIHandler) GetMessage(g *gin.Context) ginresp.HTTPResponse {
type uri struct {
MessageID models.SCNMessageID `uri:"mid"`
MessageID models.MessageID `uri:"mid" binding:"entityid"`
}
var u uri
@ -1524,7 +1524,7 @@ func (h APIHandler) GetMessage(g *gin.Context) ginresp.HTTPResponse {
// @ID api-messages-delete
// @Tags API-v2
//
// @Param mid path int true "SCNMessageID"
// @Param mid path int true "MessageID"
//
// @Success 200 {object} models.MessageJSON
// @Failure 400 {object} ginresp.apiError "supplied values/parameters cannot be parsed / are invalid"
@ -1535,7 +1535,7 @@ func (h APIHandler) GetMessage(g *gin.Context) ginresp.HTTPResponse {
// @Router /api/messages/{mid} [DELETE]
func (h APIHandler) DeleteMessage(g *gin.Context) ginresp.HTTPResponse {
type uri struct {
MessageID models.SCNMessageID `uri:"mid"`
MessageID models.MessageID `uri:"mid" binding:"entityid"`
}
var u uri
@ -1561,12 +1561,12 @@ func (h APIHandler) DeleteMessage(g *gin.Context) ginresp.HTTPResponse {
return ginresp.APIError(g, 401, apierr.USER_AUTH_FAILED, "You are not authorized for this action", nil)
}
err = h.database.DeleteMessage(ctx, msg.SCNMessageID)
err = h.database.DeleteMessage(ctx, msg.MessageID)
if err != nil {
return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to delete message", err)
}
err = h.database.CancelPendingDeliveries(ctx, msg.SCNMessageID)
err = h.database.CancelPendingDeliveries(ctx, msg.MessageID)
if err != nil {
return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to cancel deliveries", err)
}

View File

@ -1,6 +1,8 @@
package handler
import (
"blackforestbytes.com/simplecloudnotifier/api/apierr"
hl "blackforestbytes.com/simplecloudnotifier/api/apihighlight"
"blackforestbytes.com/simplecloudnotifier/api/ginresp"
primarydb "blackforestbytes.com/simplecloudnotifier/db/impl/primary"
"blackforestbytes.com/simplecloudnotifier/logic"
@ -24,6 +26,56 @@ func NewCompatHandler(app *logic.Application) CompatHandler {
}
}
// SendMessageCompat swaggerdoc
//
// @Deprecated
//
// @Summary Send a new message (compatibility)
// @Description All parameter can be set via query-parameter or form-data body. Only UserID, UserKey and Title are required
// @Tags External
//
// @Param query_data query handler.SendMessageCompat.combined false " "
// @Param form_data formData handler.SendMessageCompat.combined false " "
//
// @Success 200 {object} handler.sendMessageInternal.response
// @Failure 400 {object} ginresp.apiError
// @Failure 401 {object} ginresp.apiError
// @Failure 403 {object} ginresp.apiError
// @Failure 500 {object} ginresp.apiError
//
// @Router /send.php [POST]
func (h MessageHandler) SendMessageCompat(g *gin.Context) ginresp.HTTPResponse {
type combined struct {
UserID *int64 `json:"user_id" form:"user_id"`
UserKey *string `json:"user_key" form:"user_key"`
Title *string `json:"title" form:"title"`
Content *string `json:"content" form:"content"`
Priority *int `json:"priority" form:"priority"`
UserMessageID *string `json:"msg_id" form:"msg_id"`
SendTimestamp *float64 `json:"timestamp" form:"timestamp"`
}
var f combined
var q combined
ctx, errResp := h.app.StartRequest(g, nil, &q, nil, &f)
if errResp != nil {
return *errResp
}
defer ctx.Cancel()
data := dataext.ObjectMerge(f, q)
newid, err := h.database.ConvertCompatID(ctx, langext.Coalesce(data.UserID, -1), "userid")
if err != nil {
return ginresp.SendAPIError(g, 500, apierr.DATABASE_ERROR, hl.NONE, "Failed to query userid<old>", err)
}
if newid == nil {
return ginresp.SendAPIError(g, 400, apierr.USER_NOT_FOUND, hl.USER_ID, "User not found (compat)", nil)
}
return h.sendMessageInternal(g, ctx, langext.Ptr(models.UserID(*newid)), data.UserKey, nil, nil, data.Title, data.Content, data.Priority, data.UserMessageID, data.SendTimestamp, nil)
}
// Register swaggerdoc
//
// @Summary Register a new account
@ -121,10 +173,15 @@ func (h CompatHandler) Register(g *gin.Context) ginresp.HTTPResponse {
return ginresp.CompatAPIError(0, "Failed to create client in db")
}
oldid, err := h.database.CreateCompatID(ctx, "userid", user.UserID.String())
if err != nil {
return ginresp.SendAPIError(g, 500, apierr.DATABASE_ERROR, hl.NONE, "Failed to create userid<old>", err)
}
return ctx.FinishSuccess(ginresp.JSON(http.StatusOK, response{
Success: true,
Message: "New user registered",
UserID: user.UserID.IntID(),
UserID: oldid,
UserKey: user.AdminKey,
QuotaUsed: user.QuotaUsedToday(),
QuotaMax: user.QuotaPerDay(),
@ -184,7 +241,15 @@ func (h CompatHandler) Info(g *gin.Context) ginresp.HTTPResponse {
return ginresp.CompatAPIError(102, "Missing parameter [[user_key]]")
}
user, err := h.database.GetUser(ctx, models.UserID(*data.UserID))
useridCompNew, err := h.database.ConvertCompatID(ctx, *data.UserID, "userid")
if err != nil {
return ginresp.SendAPIError(g, 500, apierr.DATABASE_ERROR, hl.NONE, "Failed to query userid<old>", err)
}
if useridCompNew == nil {
return ginresp.SendAPIError(g, 400, apierr.USER_NOT_FOUND, hl.USER_ID, "User not found (compat)", nil)
}
user, err := h.database.GetUser(ctx, models.UserID(*useridCompNew))
if err == sql.ErrNoRows {
return ginresp.CompatAPIError(201, "User not found")
}
@ -206,7 +271,7 @@ func (h CompatHandler) Info(g *gin.Context) ginresp.HTTPResponse {
return ctx.FinishSuccess(ginresp.JSON(http.StatusOK, response{
Success: true,
Message: "ok",
UserID: user.UserID.IntID(),
UserID: *data.UserID,
UserKey: user.AdminKey,
QuotaUsed: user.QuotaUsedToday(),
QuotaMax: user.QuotaPerDay(),
@ -269,7 +334,15 @@ func (h CompatHandler) Ack(g *gin.Context) ginresp.HTTPResponse {
return ginresp.CompatAPIError(103, "Missing parameter [[scn_msg_id]]")
}
user, err := h.database.GetUser(ctx, models.UserID(*data.UserID))
useridCompNew, err := h.database.ConvertCompatID(ctx, *data.UserID, "userid")
if err != nil {
return ginresp.SendAPIError(g, 500, apierr.DATABASE_ERROR, hl.NONE, "Failed to query userid<old>", err)
}
if useridCompNew == nil {
return ginresp.SendAPIError(g, 400, apierr.USER_NOT_FOUND, hl.USER_ID, "User not found (compat)", nil)
}
user, err := h.database.GetUser(ctx, models.UserID(*useridCompNew))
if err == sql.ErrNoRows {
return ginresp.CompatAPIError(201, "User not found")
}
@ -336,7 +409,15 @@ func (h CompatHandler) Requery(g *gin.Context) ginresp.HTTPResponse {
return ginresp.CompatAPIError(102, "Missing parameter [[user_key]]")
}
user, err := h.database.GetUser(ctx, models.UserID(*data.UserID))
useridCompNew, err := h.database.ConvertCompatID(ctx, *data.UserID, "userid")
if err != nil {
return ginresp.SendAPIError(g, 500, apierr.DATABASE_ERROR, hl.NONE, "Failed to query userid<old>", err)
}
if useridCompNew == nil {
return ginresp.SendAPIError(g, 400, apierr.USER_NOT_FOUND, hl.USER_ID, "User not found (compat)", nil)
}
user, err := h.database.GetUser(ctx, models.UserID(*useridCompNew))
if err == sql.ErrNoRows {
return ginresp.CompatAPIError(201, "User not found")
}
@ -409,7 +490,15 @@ func (h CompatHandler) Update(g *gin.Context) ginresp.HTTPResponse {
return ginresp.CompatAPIError(102, "Missing parameter [[user_key]]")
}
user, err := h.database.GetUser(ctx, models.UserID(*data.UserID))
useridCompNew, err := h.database.ConvertCompatID(ctx, *data.UserID, "userid")
if err != nil {
return ginresp.SendAPIError(g, 500, apierr.DATABASE_ERROR, hl.NONE, "Failed to query userid<old>", err)
}
if useridCompNew == nil {
return ginresp.SendAPIError(g, 400, apierr.USER_NOT_FOUND, hl.USER_ID, "User not found (compat)", nil)
}
user, err := h.database.GetUser(ctx, models.UserID(*useridCompNew))
if err == sql.ErrNoRows {
return ginresp.CompatAPIError(201, "User not found")
}
@ -461,7 +550,7 @@ func (h CompatHandler) Update(g *gin.Context) ginresp.HTTPResponse {
return ctx.FinishSuccess(ginresp.JSON(http.StatusOK, response{
Success: true,
Message: "user updated",
UserID: user.UserID.IntID(),
UserID: *data.UserID,
UserKey: user.AdminKey,
QuotaUsed: user.QuotaUsedToday(),
QuotaMax: user.QuotaPerDay(),
@ -521,7 +610,15 @@ func (h CompatHandler) Expand(g *gin.Context) ginresp.HTTPResponse {
return ginresp.CompatAPIError(103, "Missing parameter [[scn_msg_id]]")
}
user, err := h.database.GetUser(ctx, models.UserID(*data.UserID))
useridCompNew, err := h.database.ConvertCompatID(ctx, *data.UserID, "userid")
if err != nil {
return ginresp.SendAPIError(g, 500, apierr.DATABASE_ERROR, hl.NONE, "Failed to query userid<old>", err)
}
if useridCompNew == nil {
return ginresp.SendAPIError(g, 400, apierr.USER_NOT_FOUND, hl.USER_ID, "User not found (compat)", nil)
}
user, err := h.database.GetUser(ctx, models.UserID(*useridCompNew))
if err == sql.ErrNoRows {
return ginresp.CompatAPIError(201, "User not found")
}
@ -533,7 +630,15 @@ func (h CompatHandler) Expand(g *gin.Context) ginresp.HTTPResponse {
return ginresp.CompatAPIError(204, "Authentification failed")
}
msg, err := h.database.GetMessage(ctx, models.SCNMessageID(*data.MessageID), false)
messageCompNew, err := h.database.ConvertCompatID(ctx, *data.MessageID, "messageid")
if err != nil {
return ginresp.SendAPIError(g, 500, apierr.DATABASE_ERROR, hl.NONE, "Failed to query messagid<old>", err)
}
if messageCompNew == nil {
return ginresp.CompatAPIError(301, "Message not found")
}
msg, err := h.database.GetMessage(ctx, models.MessageID(*messageCompNew), false)
if err == sql.ErrNoRows {
return ginresp.CompatAPIError(301, "Message not found")
}
@ -551,7 +656,7 @@ func (h CompatHandler) Expand(g *gin.Context) ginresp.HTTPResponse {
Priority: msg.Priority,
Timestamp: msg.Timestamp().Unix(),
UserMessageID: msg.UserMessageID,
SCNMessageID: msg.SCNMessageID.IntID(),
SCNMessageID: *data.MessageID,
},
}))
}
@ -617,7 +722,15 @@ func (h CompatHandler) Upgrade(g *gin.Context) ginresp.HTTPResponse {
return ginresp.CompatAPIError(104, "Missing parameter [[pro_token]]")
}
user, err := h.database.GetUser(ctx, models.UserID(*data.UserID))
useridCompNew, err := h.database.ConvertCompatID(ctx, *data.UserID, "userid")
if err != nil {
return ginresp.SendAPIError(g, 500, apierr.DATABASE_ERROR, hl.NONE, "Failed to query userid<old>", err)
}
if useridCompNew == nil {
return ginresp.SendAPIError(g, 400, apierr.USER_NOT_FOUND, hl.USER_ID, "User not found (compat)", nil)
}
user, err := h.database.GetUser(ctx, models.UserID(*useridCompNew))
if err == sql.ErrNoRows {
return ginresp.CompatAPIError(201, "User not found")
}
@ -662,7 +775,7 @@ func (h CompatHandler) Upgrade(g *gin.Context) ginresp.HTTPResponse {
return ctx.FinishSuccess(ginresp.JSON(http.StatusOK, response{
Success: true,
Message: "user updated",
UserID: user.UserID.IntID(),
UserID: *data.UserID,
QuotaUsed: user.QuotaUsedToday(),
QuotaMax: user.QuotaPerDay(),
IsPro: user.IsPro,

View File

@ -31,48 +31,6 @@ func NewMessageHandler(app *logic.Application) MessageHandler {
}
}
// SendMessageCompat swaggerdoc
//
// @Deprecated
//
// @Summary Send a new message (compatibility)
// @Description All parameter can be set via query-parameter or form-data body. Only UserID, UserKey and Title are required
// @Tags External
//
// @Param query_data query handler.SendMessageCompat.combined false " "
// @Param form_data formData handler.SendMessageCompat.combined false " "
//
// @Success 200 {object} handler.sendMessageInternal.response
// @Failure 400 {object} ginresp.apiError
// @Failure 401 {object} ginresp.apiError
// @Failure 403 {object} ginresp.apiError
// @Failure 500 {object} ginresp.apiError
//
// @Router /send.php [POST]
func (h MessageHandler) SendMessageCompat(g *gin.Context) ginresp.HTTPResponse {
type combined struct {
UserID *models.UserID `json:"user_id" form:"user_id"`
UserKey *string `json:"user_key" form:"user_key"`
Title *string `json:"title" form:"title"`
Content *string `json:"content" form:"content"`
Priority *int `json:"priority" form:"priority"`
UserMessageID *string `json:"msg_id" form:"msg_id"`
SendTimestamp *float64 `json:"timestamp" form:"timestamp"`
}
var f combined
var q combined
ctx, errResp := h.app.StartRequest(g, nil, &q, nil, &f)
if errResp != nil {
return *errResp
}
defer ctx.Cancel()
data := dataext.ObjectMerge(f, q)
return h.sendMessageInternal(g, ctx, data.UserID, data.UserKey, nil, nil, data.Title, data.Content, data.Priority, data.UserMessageID, data.SendTimestamp, nil)
}
// SendMessage swaggerdoc
//
// @Summary Send a new message
@ -123,16 +81,16 @@ func (h MessageHandler) SendMessage(g *gin.Context) ginresp.HTTPResponse {
func (h MessageHandler) sendMessageInternal(g *gin.Context, ctx *logic.AppContext, UserID *models.UserID, UserKey *string, Channel *string, ChanKey *string, Title *string, Content *string, Priority *int, UserMessageID *string, SendTimestamp *float64, SenderName *string) ginresp.HTTPResponse {
type response struct {
Success bool `json:"success"`
ErrorID apierr.APIError `json:"error"`
ErrorHighlight int `json:"errhighlight"`
Message string `json:"message"`
SuppressSend bool `json:"suppress_send"`
MessageCount int `json:"messagecount"`
Quota int `json:"quota"`
IsPro bool `json:"is_pro"`
QuotaMax int `json:"quota_max"`
SCNMessageID models.SCNMessageID `json:"scn_msg_id"`
Success bool `json:"success"`
ErrorID apierr.APIError `json:"error"`
ErrorHighlight int `json:"errhighlight"`
Message string `json:"message"`
SuppressSend bool `json:"suppress_send"`
MessageCount int `json:"messagecount"`
Quota int `json:"quota"`
IsPro bool `json:"is_pro"`
QuotaMax int `json:"quota_max"`
SCNMessageID models.MessageID `json:"scn_msg_id"`
}
if Title != nil {
@ -212,7 +170,7 @@ func (h MessageHandler) sendMessageInternal(g *gin.Context, ctx *logic.AppContex
Quota: user.QuotaUsedToday(),
IsPro: user.IsPro,
QuotaMax: user.QuotaPerDay(),
SCNMessageID: msg.SCNMessageID,
SCNMessageID: msg.MessageID,
}))
}
}
@ -317,6 +275,6 @@ func (h MessageHandler) sendMessageInternal(g *gin.Context, ctx *logic.AppContex
Quota: user.QuotaUsedToday() + 1,
IsPro: user.IsPro,
QuotaMax: user.QuotaPerDay(),
SCNMessageID: msg.SCNMessageID,
SCNMessageID: msg.MessageID,
}))
}

View File

@ -5,8 +5,12 @@ import (
"blackforestbytes.com/simplecloudnotifier/api/ginresp"
"blackforestbytes.com/simplecloudnotifier/api/handler"
"blackforestbytes.com/simplecloudnotifier/logic"
"blackforestbytes.com/simplecloudnotifier/models"
"blackforestbytes.com/simplecloudnotifier/swagger"
"errors"
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
"github.com/go-playground/validator/v10"
)
type Router struct {
@ -44,7 +48,16 @@ func NewRouter(app *logic.Application) *Router {
// @tag.name Common
//
// @BasePath /
func (r *Router) Init(e *gin.Engine) {
func (r *Router) Init(e *gin.Engine) error {
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
err := v.RegisterValidation("entityid", models.ValidateEntityID, true)
if err != nil {
return err
}
} else {
return errors.New("failed to add validators - wrong engine")
}
// ================ General ================
@ -94,7 +107,7 @@ func (r *Router) Init(e *gin.Engine) {
// ================ Compat (v1) ================
compat := e.Group("/api/")
compat := e.Group("/api")
{
compat.GET("/register.php", r.Wrap(r.compatHandler.Register))
compat.GET("/info.php", r.Wrap(r.compatHandler.Info))
@ -150,6 +163,9 @@ func (r *Router) Init(e *gin.Engine) {
e.NoRoute(r.Wrap(r.commonHandler.NoRoute))
}
// ================
return nil
}
func (r *Router) Wrap(fn ginresp.WHandlerFunc) gin.HandlerFunc {

View File

@ -63,7 +63,11 @@ func main() {
app.Init(conf, ginengine, nc, apc, []logic.Job{jobRetry, jobReqCollector})
router.Init(ginengine)
err = router.Init(ginengine)
if err != nil {
log.Fatal().Err(err).Msg("failed to init router")
return
}
app.Run()
}

View File

@ -54,6 +54,7 @@ type DBConfig struct {
ConnMaxIdleTime time.Duration `env:"CONNEXTIONMAXIDLETIME"`
CheckForeignKeys bool `env:"CHECKFOREIGNKEYS"`
SingleConn bool `env:"SINGLECONNECTION"`
BusyTimeout time.Duration `env:"BUSYTIMEOUT"`
}
var Conf Config
@ -76,6 +77,7 @@ var configLocHost = func() Config {
MaxIdleConns: 5,
ConnMaxLifetime: 60 * time.Minute,
ConnMaxIdleTime: 60 * time.Minute,
BusyTimeout: 100 * time.Millisecond,
},
DBRequests: DBConfig{
File: ".run-data/loc_requests.sqlite3",
@ -87,6 +89,7 @@ var configLocHost = func() Config {
MaxIdleConns: 5,
ConnMaxLifetime: 60 * time.Minute,
ConnMaxIdleTime: 60 * time.Minute,
BusyTimeout: 500 * time.Millisecond,
},
DBLogs: DBConfig{
File: ".run-data/loc_logs.sqlite3",
@ -98,6 +101,7 @@ var configLocHost = func() Config {
MaxIdleConns: 5,
ConnMaxLifetime: 60 * time.Minute,
ConnMaxIdleTime: 60 * time.Minute,
BusyTimeout: 500 * time.Millisecond,
},
RequestTimeout: 16 * time.Second,
RequestMaxRetry: 8,
@ -142,6 +146,7 @@ var configLocDocker = func() Config {
MaxIdleConns: 5,
ConnMaxLifetime: 60 * time.Minute,
ConnMaxIdleTime: 60 * time.Minute,
BusyTimeout: 100 * time.Millisecond,
},
DBRequests: DBConfig{
File: "/data/docker_scn_requests.sqlite3",
@ -153,6 +158,7 @@ var configLocDocker = func() Config {
MaxIdleConns: 5,
ConnMaxLifetime: 60 * time.Minute,
ConnMaxIdleTime: 60 * time.Minute,
BusyTimeout: 500 * time.Millisecond,
},
DBLogs: DBConfig{
File: "/data/docker_scn_logs.sqlite3",
@ -164,6 +170,7 @@ var configLocDocker = func() Config {
MaxIdleConns: 5,
ConnMaxLifetime: 60 * time.Minute,
ConnMaxIdleTime: 60 * time.Minute,
BusyTimeout: 500 * time.Millisecond,
},
RequestTimeout: 16 * time.Second,
RequestMaxRetry: 8,
@ -207,6 +214,7 @@ var configDev = func() Config {
MaxIdleConns: 5,
ConnMaxLifetime: 60 * time.Minute,
ConnMaxIdleTime: 60 * time.Minute,
BusyTimeout: 100 * time.Millisecond,
},
DBRequests: DBConfig{
File: "/data/scn_requests.sqlite3",
@ -218,6 +226,7 @@ var configDev = func() Config {
MaxIdleConns: 5,
ConnMaxLifetime: 60 * time.Minute,
ConnMaxIdleTime: 60 * time.Minute,
BusyTimeout: 500 * time.Millisecond,
},
DBLogs: DBConfig{
File: "/data/scn_logs.sqlite3",
@ -229,6 +238,7 @@ var configDev = func() Config {
MaxIdleConns: 5,
ConnMaxLifetime: 60 * time.Minute,
ConnMaxIdleTime: 60 * time.Minute,
BusyTimeout: 500 * time.Millisecond,
},
RequestTimeout: 16 * time.Second,
RequestMaxRetry: 8,
@ -272,6 +282,7 @@ var configStag = func() Config {
MaxIdleConns: 5,
ConnMaxLifetime: 60 * time.Minute,
ConnMaxIdleTime: 60 * time.Minute,
BusyTimeout: 100 * time.Millisecond,
},
DBRequests: DBConfig{
File: "/data/scn_requests.sqlite3",
@ -283,6 +294,7 @@ var configStag = func() Config {
MaxIdleConns: 5,
ConnMaxLifetime: 60 * time.Minute,
ConnMaxIdleTime: 60 * time.Minute,
BusyTimeout: 500 * time.Millisecond,
},
DBLogs: DBConfig{
File: "/data/scn_logs.sqlite3",
@ -294,6 +306,7 @@ var configStag = func() Config {
MaxIdleConns: 5,
ConnMaxLifetime: 60 * time.Minute,
ConnMaxIdleTime: 60 * time.Minute,
BusyTimeout: 500 * time.Millisecond,
},
RequestTimeout: 16 * time.Second,
RequestMaxRetry: 8,
@ -337,6 +350,7 @@ var configProd = func() Config {
MaxIdleConns: 5,
ConnMaxLifetime: 60 * time.Minute,
ConnMaxIdleTime: 60 * time.Minute,
BusyTimeout: 100 * time.Millisecond,
},
DBRequests: DBConfig{
File: "/data/scn_requests.sqlite3",
@ -348,6 +362,7 @@ var configProd = func() Config {
MaxIdleConns: 5,
ConnMaxLifetime: 60 * time.Minute,
ConnMaxIdleTime: 60 * time.Minute,
BusyTimeout: 500 * time.Millisecond,
},
DBLogs: DBConfig{
File: "/data/scn_logs.sqlite3",
@ -359,6 +374,7 @@ var configProd = func() Config {
MaxIdleConns: 5,
ConnMaxLifetime: 60 * time.Minute,
ConnMaxIdleTime: 60 * time.Minute,
BusyTimeout: 500 * time.Millisecond,
},
RequestTimeout: 16 * time.Second,
RequestMaxRetry: 8,

View File

@ -19,14 +19,14 @@ const (
type CursorToken struct {
Mode Mode
Timestamp int64
Id int64
Id string
Direction string
FilterHash string
}
type cursorTokenSerialize struct {
Timestamp *int64 `json:"ts,omitempty"`
Id *int64 `json:"id,omitempty"`
Id *string `json:"id,omitempty"`
Direction *string `json:"dir,omitempty"`
FilterHash *string `json:"f,omitempty"`
}
@ -35,7 +35,7 @@ func Start() CursorToken {
return CursorToken{
Mode: CTMStart,
Timestamp: 0,
Id: 0,
Id: "",
Direction: "",
FilterHash: "",
}
@ -45,13 +45,13 @@ func End() CursorToken {
return CursorToken{
Mode: CTMEnd,
Timestamp: 0,
Id: 0,
Id: "",
Direction: "",
FilterHash: "",
}
}
func Normal(ts time.Time, id int64, dir string, filter string) CursorToken {
func Normal(ts time.Time, id string, dir string, filter string) CursorToken {
return CursorToken{
Mode: CTMNormal,
Timestamp: ts.UnixMilli(),
@ -76,7 +76,7 @@ func (c *CursorToken) Token() string {
sertok := cursorTokenSerialize{}
if c.Id != 0 {
if c.Id != "" {
sertok.Id = &c.Id
}

View File

@ -24,7 +24,7 @@ type Database struct {
func NewLogsDatabase(cfg server.Config) (*Database, error) {
conf := cfg.DBLogs
url := fmt.Sprintf("file:%s?_journal=%s&_timeout=%d&_fk=%s", conf.File, conf.Journal, conf.Timeout.Milliseconds(), langext.FormatBool(conf.CheckForeignKeys, "true", "false"))
url := fmt.Sprintf("file:%s?_journal=%s&_timeout=%d&_fk=%s&_busy_timeout=%d", conf.File, conf.Journal, conf.Timeout.Milliseconds(), langext.FormatBool(conf.CheckForeignKeys, "true", "false"), conf.BusyTimeout.Milliseconds())
xdb, err := sqlx.Open("sqlite3", url)
if err != nil {

View File

@ -1,9 +1,10 @@
CREATE TABLE `logs`
(
log_id INTEGER PRIMARY KEY,
timestamp_created INTEGER NOT NULL
log_id TEXT NOT NULL,
timestamp_created INTEGER NOT NULL,
PRIMARY KEY (log_id)
) STRICT;

View File

@ -89,7 +89,10 @@ func (db *Database) CreateChannel(ctx TxContext, userid models.UserID, dispName
now := time.Now().UTC()
res, err := tx.Exec(ctx, "INSERT INTO channels (owner_user_id, display_name, internal_name, subscribe_key, send_key, timestamp_created) VALUES (:ouid, :dnam, :inam, :subkey, :sendkey, :ts)", sq.PP{
channelid := models.NewChannelID()
_, err = tx.Exec(ctx, "INSERT INTO channels (channel_id, owner_user_id, display_name, internal_name, subscribe_key, send_key, timestamp_created) VALUES (:cid, :ouid, :dnam, :inam, :subkey, :sendkey, :ts)", sq.PP{
"cid": channelid,
"ouid": userid,
"dnam": dispName,
"inam": intName,
@ -101,13 +104,8 @@ func (db *Database) CreateChannel(ctx TxContext, userid models.UserID, dispName
return models.Channel{}, err
}
liid, err := res.LastInsertId()
if err != nil {
return models.Channel{}, err
}
return models.Channel{
ChannelID: models.ChannelID(liid),
ChannelID: channelid,
OwnerUserID: userid,
DisplayName: dispName,
InternalName: intName,
@ -125,7 +123,9 @@ func (db *Database) ListChannelsByOwner(ctx TxContext, userid models.UserID, sub
return nil, err
}
rows, err := tx.Query(ctx, "SELECT channels.*, sub.* FROM channels LEFT JOIN subscriptions AS sub ON channels.channel_id = sub.channel_id AND sub.subscriber_user_id = :subuid WHERE owner_user_id = :ouid", sq.PP{
order := " ORDER BY channels.timestamp_created ASC, channels.channel_id ASC "
rows, err := tx.Query(ctx, "SELECT channels.*, sub.* FROM channels LEFT JOIN subscriptions AS sub ON channels.channel_id = sub.channel_id AND sub.subscriber_user_id = :subuid WHERE owner_user_id = :ouid"+order, sq.PP{
"ouid": userid,
"subuid": subUserID,
})
@ -154,7 +154,9 @@ func (db *Database) ListChannelsBySubscriber(ctx TxContext, userid models.UserID
confCond = " AND sub.confirmed = 0"
}
rows, err := tx.Query(ctx, "SELECT channels.*, sub.* FROM channels LEFT JOIN subscriptions AS sub on channels.channel_id = sub.channel_id AND sub.subscriber_user_id = :subuid WHERE sub.subscription_id IS NOT NULL "+confCond, sq.PP{
order := " ORDER BY channels.timestamp_created ASC, channels.channel_id ASC "
rows, err := tx.Query(ctx, "SELECT channels.*, sub.* FROM channels LEFT JOIN subscriptions AS sub on channels.channel_id = sub.channel_id AND sub.subscriber_user_id = :subuid WHERE sub.subscription_id IS NOT NULL "+confCond+order, sq.PP{
"subuid": userid,
})
if err != nil {
@ -182,7 +184,9 @@ func (db *Database) ListChannelsByAccess(ctx TxContext, userid models.UserID, co
confCond = "OR (sub.subscription_id IS NOT NULL AND sub.confirmed = 0)"
}
rows, err := tx.Query(ctx, "SELECT channels.*, sub.* FROM channels LEFT JOIN subscriptions AS sub on channels.channel_id = sub.channel_id AND sub.subscriber_user_id = :subuid WHERE owner_user_id = :ouid "+confCond, sq.PP{
order := " ORDER BY channels.timestamp_created ASC, channels.channel_id ASC "
rows, err := tx.Query(ctx, "SELECT channels.*, sub.* FROM channels LEFT JOIN subscriptions AS sub on channels.channel_id = sub.channel_id AND sub.subscriber_user_id = :subuid WHERE owner_user_id = :ouid "+confCond+order, sq.PP{
"ouid": userid,
"subuid": userid,
})

View File

@ -15,7 +15,10 @@ func (db *Database) CreateClient(ctx TxContext, userid models.UserID, ctype mode
now := time.Now().UTC()
res, err := tx.Exec(ctx, "INSERT INTO clients (user_id, type, fcm_token, timestamp_created, agent_model, agent_version) VALUES (:uid, :typ, :fcm, :ts, :am, :av)", sq.PP{
clientid := models.NewClientID()
_, err = tx.Exec(ctx, "INSERT INTO clients (client_id, user_id, type, fcm_token, timestamp_created, agent_model, agent_version) VALUES (:cid, :uid, :typ, :fcm, :ts, :am, :av)", sq.PP{
"cid": clientid,
"uid": userid,
"typ": string(ctype),
"fcm": fcmToken,
@ -27,13 +30,8 @@ func (db *Database) CreateClient(ctx TxContext, userid models.UserID, ctype mode
return models.Client{}, err
}
liid, err := res.LastInsertId()
if err != nil {
return models.Client{}, err
}
return models.Client{
ClientID: models.ClientID(liid),
ClientID: clientid,
UserID: userid,
Type: ctype,
FCMToken: langext.Ptr(fcmToken),
@ -63,7 +61,7 @@ func (db *Database) ListClients(ctx TxContext, userid models.UserID) ([]models.C
return nil, err
}
rows, err := tx.Query(ctx, "SELECT * FROM clients WHERE user_id = :uid", sq.PP{"uid": userid})
rows, err := tx.Query(ctx, "SELECT * FROM clients WHERE user_id = :uid ORDER BY clients.timestamp_created DESC, clients.client_id ASC", sq.PP{"uid": userid})
if err != nil {
return nil, err
}

View File

@ -0,0 +1,100 @@
package primary
import (
"database/sql"
"errors"
"gogs.mikescher.com/BlackForestBytes/goext/sq"
)
func (db *Database) CreateCompatID(ctx TxContext, idtype string, newid string) (int64, error) {
tx, err := ctx.GetOrCreateTransaction(db)
if err != nil {
return 0, err
}
rows, err := tx.Query(ctx, "SELECT COALESCE(MAX(old), 0) FROM compat_ids", sq.PP{})
if err != nil {
return 0, err
}
if !rows.Next() {
return 0, errors.New("failed to query MAX(old)")
}
var oldid int64
err = rows.Scan(&oldid)
if err != nil {
return 0, err
}
oldid++
_, err = tx.Exec(ctx, "INSERT INTO compat_ids (old, new, type) VALUES (:old, :new, :typ)", sq.PP{
"old": oldid,
"new": newid,
"typ": idtype,
})
if err != nil {
return 0, err
}
return oldid, nil
}
func (db *Database) ConvertCompatID(ctx TxContext, oldid int64, idtype string) (*string, error) {
tx, err := ctx.GetOrCreateTransaction(db)
if err != nil {
return nil, err
}
rows, err := tx.Query(ctx, "SELECT new FROM compat_ids WHERE old = :old AND type = :typ", sq.PP{
"old": oldid,
"typ": idtype,
})
if err != nil {
return nil, err
}
if !rows.Next() {
return nil, nil
}
var newid string
err = rows.Scan(&newid)
if err == sql.ErrNoRows {
return nil, nil
}
if err != nil {
return nil, err
}
return &newid, nil
}
func (db *Database) ConvertToCompatID(ctx TxContext, newid string) (*int64, *string, error) {
tx, err := ctx.GetOrCreateTransaction(db)
if err != nil {
return nil, nil, err
}
rows, err := tx.Query(ctx, "SELECT old, type FROM compat_ids WHERE new = :new", sq.PP{"new": newid})
if err != nil {
return nil, nil, err
}
if !rows.Next() {
return nil, nil, nil
}
var oldid int64
var idtype string
err = rows.Scan(&oldid, &idtype)
if err == sql.ErrNoRows {
return nil, nil, nil
}
if err != nil {
return nil, nil, err
}
return &oldid, &idtype, nil
}

View File

@ -24,7 +24,7 @@ type Database struct {
func NewPrimaryDatabase(cfg server.Config) (*Database, error) {
conf := cfg.DBMain
url := fmt.Sprintf("file:%s?_journal=%s&_timeout=%d&_fk=%s", conf.File, conf.Journal, conf.Timeout.Milliseconds(), langext.FormatBool(conf.CheckForeignKeys, "true", "false"))
url := fmt.Sprintf("file:%s?_journal=%s&_timeout=%d&_fk=%s&_busy_timeout=%d", conf.File, conf.Journal, conf.Timeout.Milliseconds(), langext.FormatBool(conf.CheckForeignKeys, "true", "false"), conf.BusyTimeout.Milliseconds())
xdb, err := sqlx.Open("sqlite3", url)
if err != nil {

View File

@ -17,8 +17,11 @@ func (db *Database) CreateRetryDelivery(ctx TxContext, client models.Client, msg
now := time.Now().UTC()
next := scn.NextDeliveryTimestamp(now)
res, err := tx.Exec(ctx, "INSERT INTO deliveries (scn_message_id, receiver_user_id, receiver_client_id, timestamp_created, timestamp_finalized, status, fcm_message_id, next_delivery) VALUES (:mid, :ruid, :rcid, :tsc, :tsf, :stat, :fcm, :next)", sq.PP{
"mid": msg.SCNMessageID,
deliveryid := models.NewDeliveryID()
_, err = tx.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": deliveryid,
"mid": msg.MessageID,
"ruid": client.UserID,
"rcid": client.ClientID,
"tsc": time2DB(now),
@ -31,14 +34,9 @@ func (db *Database) CreateRetryDelivery(ctx TxContext, client models.Client, msg
return models.Delivery{}, err
}
liid, err := res.LastInsertId()
if err != nil {
return models.Delivery{}, err
}
return models.Delivery{
DeliveryID: models.DeliveryID(liid),
SCNMessageID: msg.SCNMessageID,
DeliveryID: deliveryid,
MessageID: msg.MessageID,
ReceiverUserID: client.UserID,
ReceiverClientID: client.ClientID,
TimestampCreated: now,
@ -58,8 +56,11 @@ func (db *Database) CreateSuccessDelivery(ctx TxContext, client models.Client, m
now := time.Now().UTC()
res, err := tx.Exec(ctx, "INSERT INTO deliveries (scn_message_id, receiver_user_id, receiver_client_id, timestamp_created, timestamp_finalized, status, fcm_message_id, next_delivery) VALUES (:mid, :ruid, :rcid, :tsc, :tsf, :stat, :fcm, :next)", sq.PP{
"mid": msg.SCNMessageID,
deliveryid := models.NewDeliveryID()
_, err = tx.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": deliveryid,
"mid": msg.MessageID,
"ruid": client.UserID,
"rcid": client.ClientID,
"tsc": time2DB(now),
@ -72,14 +73,9 @@ func (db *Database) CreateSuccessDelivery(ctx TxContext, client models.Client, m
return models.Delivery{}, err
}
liid, err := res.LastInsertId()
if err != nil {
return models.Delivery{}, err
}
return models.Delivery{
DeliveryID: models.DeliveryID(liid),
SCNMessageID: msg.SCNMessageID,
DeliveryID: deliveryid,
MessageID: msg.MessageID,
ReceiverUserID: client.UserID,
ReceiverClientID: client.ClientID,
TimestampCreated: now,
@ -97,7 +93,7 @@ func (db *Database) ListRetrieableDeliveries(ctx TxContext, pageSize int) ([]mod
return nil, err
}
rows, err := tx.Query(ctx, "SELECT * FROM deliveries WHERE status = 'RETRY' AND next_delivery < :next LIMIT :lim", sq.PP{
rows, err := tx.Query(ctx, "SELECT * FROM deliveries WHERE status = 'RETRY' AND next_delivery < :next LIMIT :lim ORDER BY next_delivery ASC", sq.PP{
"next": time2DB(time.Now()),
"lim": pageSize,
})
@ -169,15 +165,15 @@ func (db *Database) SetDeliveryRetry(ctx TxContext, delivery models.Delivery) er
return nil
}
func (db *Database) CancelPendingDeliveries(ctx TxContext, scnMessageID models.SCNMessageID) error {
func (db *Database) CancelPendingDeliveries(ctx TxContext, messageID models.MessageID) error {
tx, err := ctx.GetOrCreateTransaction(db)
if err != nil {
return err
}
_, err = tx.Exec(ctx, "UPDATE deliveries SET status = 'FAILED', next_delivery = NULL, timestamp_finalized = :ts WHERE scn_message_id = :mid AND status = 'RETRY'", sq.PP{
_, err = tx.Exec(ctx, "UPDATE deliveries SET status = 'FAILED', next_delivery = NULL, timestamp_finalized = :ts WHERE message_id = :mid AND status = 'RETRY'", sq.PP{
"ts": time.Now(),
"mid": scnMessageID,
"mid": messageID,
})
if err != nil {
return err

View File

@ -30,7 +30,7 @@ func (db *Database) GetMessageByUserMessageID(ctx TxContext, usrMsgId string) (*
return &msg, nil
}
func (db *Database) GetMessage(ctx TxContext, scnMessageID models.SCNMessageID, allowDeleted bool) (models.Message, error) {
func (db *Database) GetMessage(ctx TxContext, scnMessageID models.MessageID, allowDeleted bool) (models.Message, error) {
tx, err := ctx.GetOrCreateTransaction(db)
if err != nil {
return models.Message{}, err
@ -38,9 +38,9 @@ func (db *Database) GetMessage(ctx TxContext, scnMessageID models.SCNMessageID,
var sqlcmd string
if allowDeleted {
sqlcmd = "SELECT * FROM messages WHERE scn_message_id = :mid LIMIT 1"
sqlcmd = "SELECT * FROM messages WHERE message_id = :mid LIMIT 1"
} else {
sqlcmd = "SELECT * FROM messages WHERE scn_message_id = :mid AND deleted=0 LIMIT 1"
sqlcmd = "SELECT * FROM messages WHERE message_id = :mid AND deleted=0 LIMIT 1"
}
rows, err := tx.Query(ctx, sqlcmd, sq.PP{"mid": scnMessageID})
@ -64,7 +64,10 @@ func (db *Database) CreateMessage(ctx TxContext, senderUserID models.UserID, cha
now := time.Now().UTC()
res, err := tx.Exec(ctx, "INSERT INTO messages (sender_user_id, owner_user_id, channel_internal_name, channel_id, timestamp_real, timestamp_client, title, content, priority, usr_message_id, sender_ip, sender_name) VALUES (:suid, :ouid, :cnam, :cid, :tsr, :tsc, :tit, :cnt, :prio, :umid, :ip, :snam)", sq.PP{
messageid := models.NewMessageID()
_, err = tx.Exec(ctx, "INSERT INTO messages (message_id, sender_user_id, owner_user_id, channel_internal_name, channel_id, timestamp_real, timestamp_client, title, content, priority, usr_message_id, sender_ip, sender_name) VALUES (:mid, :suid, :ouid, :cnam, :cid, :tsr, :tsc, :tit, :cnt, :prio, :umid, :ip, :snam)", sq.PP{
"mid": messageid,
"suid": senderUserID,
"ouid": channel.OwnerUserID,
"cnam": channel.InternalName,
@ -82,13 +85,8 @@ func (db *Database) CreateMessage(ctx TxContext, senderUserID models.UserID, cha
return models.Message{}, err
}
liid, err := res.LastInsertId()
if err != nil {
return models.Message{}, err
}
return models.Message{
SCNMessageID: models.SCNMessageID(liid),
MessageID: messageid,
SenderUserID: senderUserID,
OwnerUserID: channel.OwnerUserID,
ChannelInternalName: channel.InternalName,
@ -104,13 +102,13 @@ func (db *Database) CreateMessage(ctx TxContext, senderUserID models.UserID, cha
}, nil
}
func (db *Database) DeleteMessage(ctx TxContext, scnMessageID models.SCNMessageID) error {
func (db *Database) DeleteMessage(ctx TxContext, messageID models.MessageID) error {
tx, err := ctx.GetOrCreateTransaction(db)
if err != nil {
return err
}
_, err = tx.Exec(ctx, "UPDATE messages SET deleted=1 WHERE scn_message_id = :mid AND deleted=0", sq.PP{"mid": scnMessageID})
_, err = tx.Exec(ctx, "UPDATE messages SET deleted=1 WHERE message_id = :mid AND deleted=0", sq.PP{"mid": messageID})
if err != nil {
return err
}
@ -130,12 +128,12 @@ func (db *Database) ListMessages(ctx TxContext, filter models.MessageFilter, pag
pageCond := "1=1"
if inTok.Mode == cursortoken.CTMNormal {
pageCond = "timestamp_real < :tokts OR (timestamp_real = :tokts AND scn_message_id < :tokid )"
pageCond = "timestamp_real < :tokts OR (timestamp_real = :tokts AND message_id < :tokid )"
}
filterCond, filterJoin, prepParams, err := filter.SQL()
orderClause := "ORDER BY COALESCE(timestamp_client, timestamp_real) DESC LIMIT :lim"
orderClause := "ORDER BY COALESCE(timestamp_client, timestamp_real) DESC, message_id DESC LIMIT :lim"
sqlQuery := "SELECT " + "messages.*" + " FROM messages " + filterJoin + " WHERE ( " + pageCond + " ) AND ( " + filterCond + " ) " + orderClause
@ -156,7 +154,7 @@ func (db *Database) ListMessages(ctx TxContext, filter models.MessageFilter, pag
if len(data) <= pageSize {
return data, cursortoken.End(), nil
} else {
outToken := cursortoken.Normal(data[pageSize-1].Timestamp(), data[pageSize-1].SCNMessageID.IntID(), "DESC", filter.Hash())
outToken := cursortoken.Normal(data[pageSize-1].Timestamp(), data[pageSize-1].MessageID.String(), "DESC", filter.Hash())
return data[0:pageSize], outToken, nil
}
}

View File

@ -20,7 +20,7 @@ CREATE TABLE `users`
DROP TABLE IF EXISTS `messages`;
CREATE TABLE `messages`
(
`scn_message_id` INT(11) NOT NULL AUTO_INCREMENT,
`message_id` INT(11) NOT NULL AUTO_INCREMENT,
`sender_user_id` INT(11) NOT NULL,
`timestamp_real` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
@ -34,5 +34,5 @@ CREATE TABLE `messages`
`fcm_message_id` VARCHAR(256) NULL,
`usr_message_id` VARCHAR(256) NULL,
PRIMARY KEY (`scn_message_id`)
PRIMARY KEY (`message_id`)
);

View File

@ -18,7 +18,7 @@ CREATE TABLE `users`
CREATE TABLE `messages`
(
`scn_message_id` INTEGER AUTO_INCREMENT,
`message_id` INTEGER AUTO_INCREMENT,
`sender_user_id` INTEGER NOT NULL,
`timestamp_real` TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
@ -32,7 +32,7 @@ CREATE TABLE `messages`
`fcm_message_id` TEXT NULL,
`usr_message_id` TEXT NULL,
PRIMARY KEY (`scn_message_id`)
PRIMARY KEY (`message_id`)
);
CREATE TABLE `meta`

View File

@ -1,6 +1,6 @@
CREATE TABLE users
(
user_id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id TEXT NOT NULL,
username TEXT NULL DEFAULT NULL,
@ -18,23 +18,27 @@ CREATE TABLE users
quota_used_day TEXT NULL DEFAULT NULL,
is_pro INTEGER CHECK(is_pro IN (0, 1)) NOT NULL DEFAULT 0,
pro_token TEXT NULL DEFAULT NULL
pro_token TEXT NULL DEFAULT NULL,
PRIMARY KEY (user_id)
) STRICT;
CREATE UNIQUE INDEX "idx_users_protoken" ON users (pro_token) WHERE pro_token IS NOT NULL;
CREATE TABLE clients
(
client_id INTEGER PRIMARY KEY AUTOINCREMENT,
client_id TEXT NOT NULL,
user_id INTEGER NOT NULL,
user_id TEXT NOT NULL,
type TEXT CHECK(type IN ('ANDROID', 'IOS')) NOT NULL,
fcm_token TEXT NULL,
timestamp_created INTEGER NOT NULL,
agent_model TEXT NOT NULL,
agent_version TEXT NOT NULL
agent_version TEXT NOT NULL,
PRIMARY KEY (client_id)
) STRICT;
CREATE INDEX "idx_clients_userid" ON clients (user_id);
CREATE UNIQUE INDEX "idx_clients_fcmtoken" ON clients (fcm_token);
@ -42,9 +46,9 @@ CREATE UNIQUE INDEX "idx_clients_fcmtoken" ON clients (fcm_token);
CREATE TABLE channels
(
channel_id INTEGER PRIMARY KEY AUTOINCREMENT,
channel_id TEXT NOT NULL,
owner_user_id INTEGER NOT NULL,
owner_user_id TEXT NOT NULL,
internal_name TEXT NOT NULL,
display_name TEXT NOT NULL,
@ -56,22 +60,26 @@ CREATE TABLE channels
timestamp_created INTEGER NOT NULL,
timestamp_lastsent INTEGER NULL DEFAULT NULL,
messages_sent INTEGER NOT NULL DEFAULT '0'
messages_sent INTEGER NOT NULL DEFAULT '0',
PRIMARY KEY (channel_id)
) STRICT;
CREATE UNIQUE INDEX "idx_channels_identity" ON channels (owner_user_id, internal_name);
CREATE TABLE subscriptions
(
subscription_id INTEGER PRIMARY KEY AUTOINCREMENT,
subscription_id TEXT NOT NULL,
subscriber_user_id INTEGER NOT NULL,
channel_owner_user_id INTEGER NOT NULL,
subscriber_user_id TEXT NOT NULL,
channel_owner_user_id TEXT NOT NULL,
channel_internal_name TEXT NOT NULL,
channel_id INTEGER NOT NULL,
channel_id TEXT NOT NULL,
timestamp_created INTEGER NOT NULL,
confirmed INTEGER CHECK(confirmed IN (0, 1)) NOT NULL
confirmed INTEGER CHECK(confirmed IN (0, 1)) NOT NULL,
PRIMARY KEY (subscription_id)
) STRICT;
CREATE UNIQUE INDEX "idx_subscriptions_ref" ON subscriptions (subscriber_user_id, channel_owner_user_id, channel_internal_name);
CREATE INDEX "idx_subscriptions_chan" ON subscriptions (channel_id);
@ -83,11 +91,11 @@ CREATE INDEX "idx_subscriptions_conf" ON subscriptions (confirmed);
CREATE TABLE messages
(
scn_message_id INTEGER PRIMARY KEY AUTOINCREMENT,
sender_user_id INTEGER NOT NULL,
owner_user_id INTEGER NOT NULL,
message_id TEXT NOT NULL,
sender_user_id TEXT NOT NULL,
owner_user_id TEXT NOT NULL,
channel_internal_name TEXT NOT NULL,
channel_id INTEGER NOT NULL,
channel_id TEXT NOT NULL,
sender_ip TEXT NOT NULL,
sender_name TEXT NULL,
@ -99,7 +107,9 @@ CREATE TABLE messages
priority INTEGER CHECK(priority IN (0, 1, 2)) NOT NULL,
usr_message_id TEXT NULL,
deleted INTEGER CHECK(deleted IN (0, 1)) NOT NULL DEFAULT '0'
deleted INTEGER CHECK(deleted IN (0, 1)) NOT NULL DEFAULT '0',
PRIMARY KEY (message_id)
) STRICT;
CREATE INDEX "idx_messages_owner_channel" ON messages (owner_user_id, channel_internal_name COLLATE BINARY);
CREATE INDEX "idx_messages_owner_channel_nc" ON messages (owner_user_id, channel_internal_name COLLATE NOCASE);
@ -123,31 +133,30 @@ CREATE VIRTUAL TABLE messages_fts USING fts5
tokenize = unicode61,
content = 'messages',
content_rowid = 'scn_message_id'
content_rowid = 'rowid'
);
CREATE TRIGGER fts_insert AFTER INSERT ON messages BEGIN
INSERT INTO messages_fts (rowid, channel_internal_name, sender_name, title, content) VALUES (new.scn_message_id, new.channel_internal_name, new.sender_name, new.title, new.content);
INSERT INTO messages_fts (rowid, channel_internal_name, sender_name, title, content) VALUES (new.rowid, new.channel_internal_name, new.sender_name, new.title, new.content);
END;
CREATE TRIGGER fts_update AFTER UPDATE ON messages BEGIN
INSERT INTO messages_fts (messages_fts, rowid, channel_internal_name, sender_name, title, content) VALUES ('delete', old.scn_message_id, old.channel_internal_name, old.sender_name, old.title, old.content);
INSERT INTO messages_fts ( rowid, channel_internal_name, sender_name, title, content) VALUES ( new.scn_message_id, new.channel_internal_name, new.sender_name, new.title, new.content);
INSERT INTO messages_fts (messages_fts, rowid, channel_internal_name, sender_name, title, content) VALUES ('delete', old.rowid, old.channel_internal_name, old.sender_name, old.title, old.content);
INSERT INTO messages_fts ( rowid, channel_internal_name, sender_name, title, content) VALUES ( new.rowid, new.channel_internal_name, new.sender_name, new.title, new.content);
END;
CREATE TRIGGER fts_delete AFTER DELETE ON messages BEGIN
INSERT INTO messages_fts (messages_fts, rowid, channel_internal_name, sender_name, title, content) VALUES ('delete', old.scn_message_id, old.channel_internal_name, old.sender_name, old.title, old.content);
INSERT INTO messages_fts (messages_fts, rowid, channel_internal_name, sender_name, title, content) VALUES ('delete', old.rowid, old.channel_internal_name, old.sender_name, old.title, old.content);
END;
CREATE TABLE deliveries
(
delivery_id INTEGER PRIMARY KEY AUTOINCREMENT,
delivery_id TEXT NOT NULL,
scn_message_id INTEGER NOT NULL,
receiver_user_id INTEGER NOT NULL,
receiver_client_id INTEGER NOT NULL,
message_id TEXT NOT NULL,
receiver_user_id TEXT NOT NULL,
receiver_client_id TEXT NOT NULL,
timestamp_created INTEGER NOT NULL,
timestamp_finalized INTEGER NULL,
@ -157,9 +166,21 @@ CREATE TABLE deliveries
retry_count INTEGER NOT NULL DEFAULT 0,
next_delivery INTEGER NULL DEFAULT NULL,
fcm_message_id TEXT NULL
fcm_message_id TEXT NULL,
PRIMARY KEY (delivery_id)
) STRICT;
CREATE INDEX "idx_deliveries_receiver" ON deliveries (scn_message_id, receiver_client_id);
CREATE INDEX "idx_deliveries_receiver" ON deliveries (message_id, receiver_client_id);
CREATE TABLE compat_ids
(
old INTEGER NOT NULL,
new TEXT NOT NULL,
type TEXT NOT NULL
) STRICT;
CREATE UNIQUE INDEX "idx_compatids_new" ON compat_ids (new);
CREATE UNIQUE INDEX "idx_compatids_old" ON compat_ids (old, type);
CREATE TABLE `meta`

View File

@ -15,7 +15,10 @@ func (db *Database) CreateSubscription(ctx TxContext, subscriberUID models.UserI
now := time.Now().UTC()
res, err := tx.Exec(ctx, "INSERT INTO subscriptions (subscriber_user_id, channel_owner_user_id, channel_internal_name, channel_id, timestamp_created, confirmed) VALUES (:suid, :ouid, :cnam, :cid, :ts, :conf)", sq.PP{
subscriptionid := models.NewSubscriptionID()
_, err = tx.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": subscriptionid,
"suid": subscriberUID,
"ouid": channel.OwnerUserID,
"cnam": channel.InternalName,
@ -27,13 +30,8 @@ func (db *Database) CreateSubscription(ctx TxContext, subscriberUID models.UserI
return models.Subscription{}, err
}
liid, err := res.LastInsertId()
if err != nil {
return models.Subscription{}, err
}
return models.Subscription{
SubscriptionID: models.SubscriptionID(liid),
SubscriptionID: subscriptionid,
SubscriberUserID: subscriberUID,
ChannelOwnerUserID: channel.OwnerUserID,
ChannelID: channel.ChannelID,
@ -49,7 +47,9 @@ func (db *Database) ListSubscriptionsByChannel(ctx TxContext, channelID models.C
return nil, err
}
rows, err := tx.Query(ctx, "SELECT * FROM subscriptions WHERE channel_id = :cid", sq.PP{"cid": channelID})
order := " ORDER BY subscriptions.timestamp_created DESC, subscriptions.subscription_id DESC "
rows, err := tx.Query(ctx, "SELECT * FROM subscriptions WHERE channel_id = :cid"+order, sq.PP{"cid": channelID})
if err != nil {
return nil, err
}
@ -75,7 +75,9 @@ func (db *Database) ListSubscriptionsByChannelOwner(ctx TxContext, ownerUserID m
cond = " AND confirmed = 0"
}
rows, err := tx.Query(ctx, "SELECT * FROM subscriptions WHERE channel_owner_user_id = :ouid"+cond, sq.PP{"ouid": ownerUserID})
order := " ORDER BY subscriptions.timestamp_created DESC, subscriptions.subscription_id DESC "
rows, err := tx.Query(ctx, "SELECT * FROM subscriptions WHERE channel_owner_user_id = :ouid"+cond+order, sq.PP{"ouid": ownerUserID})
if err != nil {
return nil, err
}
@ -101,7 +103,9 @@ func (db *Database) ListSubscriptionsBySubscriber(ctx TxContext, subscriberUserI
cond = " AND confirmed = 0"
}
rows, err := tx.Query(ctx, "SELECT * FROM subscriptions WHERE subscriber_user_id = :suid"+cond, sq.PP{"suid": subscriberUserID})
order := " ORDER BY subscriptions.timestamp_created DESC, subscriptions.subscription_id DESC "
rows, err := tx.Query(ctx, "SELECT * FROM subscriptions WHERE subscriber_user_id = :suid"+cond+order, sq.PP{"suid": subscriberUserID})
if err != nil {
return nil, err
}

View File

@ -16,7 +16,10 @@ func (db *Database) CreateUser(ctx TxContext, readKey string, sendKey string, ad
now := time.Now().UTC()
res, err := tx.Exec(ctx, "INSERT INTO users (username, read_key, send_key, admin_key, is_pro, pro_token, timestamp_created) VALUES (:un, :rk, :sk, :ak, :pro, :tok, :ts)", sq.PP{
userid := models.NewUserID()
_, err = tx.Exec(ctx, "INSERT INTO users (user_id, username, read_key, send_key, admin_key, is_pro, pro_token, timestamp_created) VALUES (:uid, :un, :rk, :sk, :ak, :pro, :tok, :ts)", sq.PP{
"uid": userid,
"un": username,
"rk": readKey,
"sk": sendKey,
@ -29,13 +32,8 @@ func (db *Database) CreateUser(ctx TxContext, readKey string, sendKey string, ad
return models.User{}, err
}
liid, err := res.LastInsertId()
if err != nil {
return models.User{}, err
}
return models.User{
UserID: models.UserID(liid),
UserID: userid,
Username: username,
ReadKey: readKey,
SendKey: sendKey,

View File

@ -24,7 +24,7 @@ type Database struct {
func NewRequestsDatabase(cfg server.Config) (*Database, error) {
conf := cfg.DBRequests
url := fmt.Sprintf("file:%s?_journal=%s&_timeout=%d&_fk=%s", conf.File, conf.Journal, conf.Timeout.Milliseconds(), langext.FormatBool(conf.CheckForeignKeys, "true", "false"))
url := fmt.Sprintf("file:%s?_journal=%s&_timeout=%d&_fk=%s&_busy_timeout=%d", conf.File, conf.Journal, conf.Timeout.Milliseconds(), langext.FormatBool(conf.CheckForeignKeys, "true", "false"), conf.BusyTimeout.Milliseconds())
xdb, err := sqlx.Open("sqlite3", url)
if err != nil {

View File

@ -7,11 +7,12 @@ import (
"time"
)
func (db *Database) InsertRequestLog(ctx context.Context, data models.RequestLogDB) (models.RequestLogDB, error) {
func (db *Database) InsertRequestLog(ctx context.Context, requestid models.RequestID, data models.RequestLogDB) (models.RequestLogDB, error) {
now := time.Now()
res, err := db.db.Exec(ctx, "INSERT INTO requests (method, uri, user_agent, authentication, request_body, request_body_size, request_content_type, remote_ip, userid, permissions, response_statuscode, response_body_size, response_body, response_content_type, retry_count, panicked, panic_str, processing_time, timestamp_created, timestamp_start, timestamp_finish) VALUES (:method, :uri, :user_agent, :authentication, :request_body, :request_body_size, :request_content_type, :remote_ip, :userid, :permissions, :response_statuscode, :response_body_size, :response_body, :response_content_type, :retry_count, :panicked, :panic_str, :processing_time, :timestamp_created, :timestamp_start, :timestamp_finish)", sq.PP{
_, err := db.db.Exec(ctx, "INSERT INTO requests (request_id, method, uri, user_agent, authentication, request_body, request_body_size, request_content_type, remote_ip, userid, permissions, response_statuscode, response_body_size, response_body, response_content_type, retry_count, panicked, panic_str, processing_time, timestamp_created, timestamp_start, timestamp_finish) VALUES (:request_id, :method, :uri, :user_agent, :authentication, :request_body, :request_body_size, :request_content_type, :remote_ip, :userid, :permissions, :response_statuscode, :response_body_size, :response_body, :response_content_type, :retry_count, :panicked, :panic_str, :processing_time, :timestamp_created, :timestamp_start, :timestamp_finish)", sq.PP{
"request_id": requestid,
"method": data.Method,
"uri": data.URI,
"user_agent": data.UserAgent,
@ -38,13 +39,8 @@ func (db *Database) InsertRequestLog(ctx context.Context, data models.RequestLog
return models.RequestLogDB{}, err
}
liid, err := res.LastInsertId()
if err != nil {
return models.RequestLogDB{}, err
}
return models.RequestLogDB{
RequestID: models.RequestID(liid),
RequestID: requestid,
Method: data.Method,
URI: data.URI,
UserAgent: data.UserAgent,

View File

@ -1,7 +1,7 @@
CREATE TABLE `requests`
(
request_id INTEGER PRIMARY KEY AUTOINCREMENT,
request_id TEXT NOT NULL,
method TEXT NOT NULL,
uri TEXT NOT NULL,
@ -15,8 +15,8 @@ CREATE TABLE `requests`
userid TEXT NULL,
permissions TEXT NULL,
response_statuscode INTEGER NOT NULL,
response_body_size INTEGER NOT NULL,
response_statuscode INTEGER NULL,
response_body_size INTEGER NULL,
response_body TEXT NULL,
response_content_type TEXT NOT NULL,
processing_time INTEGER NOT NULL,
@ -26,8 +26,9 @@ CREATE TABLE `requests`
timestamp_created INTEGER NOT NULL,
timestamp_start INTEGER NOT NULL,
timestamp_finish INTEGER NOT NULL
timestamp_finish INTEGER NOT NULL,
PRIMARY KEY (request_id)
) STRICT;

View File

@ -134,14 +134,14 @@ func (j *DeliveryRetryJob) redeliver(ctx *logic.SimpleContext, delivery models.D
client, err := j.app.Database.Primary.GetClient(ctx, delivery.ReceiverUserID, delivery.ReceiverClientID)
if err != nil {
log.Err(err).Int64("ReceiverUserID", delivery.ReceiverUserID.IntID()).Int64("ReceiverClientID", delivery.ReceiverClientID.IntID()).Msg("Failed to get client")
log.Err(err).Str("ReceiverUserID", delivery.ReceiverUserID.String()).Str("ReceiverClientID", delivery.ReceiverClientID.String()).Msg("Failed to get client")
ctx.RollbackTransaction()
return
}
msg, err := j.app.Database.Primary.GetMessage(ctx, delivery.SCNMessageID, true)
msg, err := j.app.Database.Primary.GetMessage(ctx, delivery.MessageID, true)
if err != nil {
log.Err(err).Int64("SCNMessageID", delivery.SCNMessageID.IntID()).Msg("Failed to get message")
log.Err(err).Str("MessageID", delivery.MessageID.String()).Msg("Failed to get message")
ctx.RollbackTransaction()
return
}
@ -149,7 +149,7 @@ func (j *DeliveryRetryJob) redeliver(ctx *logic.SimpleContext, delivery models.D
if msg.Deleted {
err = j.app.Database.Primary.SetDeliveryFailed(ctx, delivery)
if err != nil {
log.Err(err).Int64("SCNMessageID", delivery.SCNMessageID.IntID()).Int64("DeliveryID", delivery.DeliveryID.IntID()).Msg("Failed to update delivery")
log.Err(err).Str("MessageID", delivery.MessageID.String()).Str("DeliveryID", delivery.DeliveryID.String()).Msg("Failed to update delivery")
ctx.RollbackTransaction()
return
}
@ -159,22 +159,22 @@ func (j *DeliveryRetryJob) redeliver(ctx *logic.SimpleContext, delivery models.D
if err == nil {
err = j.app.Database.Primary.SetDeliverySuccess(ctx, delivery, *fcmDelivID)
if err != nil {
log.Err(err).Int64("SCNMessageID", delivery.SCNMessageID.IntID()).Int64("DeliveryID", delivery.DeliveryID.IntID()).Msg("Failed to update delivery")
log.Err(err).Str("MessageID", delivery.MessageID.String()).Str("DeliveryID", delivery.DeliveryID.String()).Msg("Failed to update delivery")
ctx.RollbackTransaction()
return
}
} else if delivery.RetryCount+1 > delivery.MaxRetryCount() {
err = j.app.Database.Primary.SetDeliveryFailed(ctx, delivery)
if err != nil {
log.Err(err).Int64("SCNMessageID", delivery.SCNMessageID.IntID()).Int64("DeliveryID", delivery.DeliveryID.IntID()).Msg("Failed to update delivery")
log.Err(err).Str("MessageID", delivery.MessageID.String()).Str("DeliveryID", delivery.DeliveryID.String()).Msg("Failed to update delivery")
ctx.RollbackTransaction()
return
}
log.Warn().Int64("SCNMessageID", delivery.SCNMessageID.IntID()).Int64("DeliveryID", delivery.DeliveryID.IntID()).Msg("Delivery failed after <max> retries (set to FAILURE)")
log.Warn().Str("MessageID", delivery.MessageID.String()).Str("DeliveryID", delivery.DeliveryID.String()).Msg("Delivery failed after <max> retries (set to FAILURE)")
} else {
err = j.app.Database.Primary.SetDeliveryRetry(ctx, delivery)
if err != nil {
log.Err(err).Int64("SCNMessageID", delivery.SCNMessageID.IntID()).Int64("DeliveryID", delivery.DeliveryID.IntID()).Msg("Failed to update delivery")
log.Err(err).Str("MessageID", delivery.MessageID.String()).Str("DeliveryID", delivery.DeliveryID.String()).Msg("Failed to update delivery")
ctx.RollbackTransaction()
return
}

View File

@ -72,11 +72,12 @@ mainLoop:
log.Error().Msg(fmt.Sprintf("Received unknown job signal: <%s> in job [%s]", signal, j.name))
}
case obj := <-j.app.RequestLogQueue:
err := j.insertLog(obj)
requestid := models.NewRequestID()
err := j.insertLog(requestid, obj)
if err != nil {
log.Error().Err(err).Msg(fmt.Sprintf("Failed to insert RequestLog {%s} into DB", obj.RequestID))
log.Error().Err(err).Msg(fmt.Sprintf("Failed to insert RequestLog {%s} into DB", requestid))
} else {
log.Debug().Msg(fmt.Sprintf("Inserted RequestLog '%s' into DB", obj.RequestID))
log.Debug().Msg(fmt.Sprintf("Inserted RequestLog '%s' into DB", requestid))
}
}
}
@ -86,12 +87,12 @@ mainLoop:
j.isRunning.Set(false)
}
func (j *RequestLogCollectorJob) insertLog(rl models.RequestLog) error {
func (j *RequestLogCollectorJob) insertLog(requestid models.RequestID, rl models.RequestLog) error {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
_, err := j.app.Database.Requests.InsertRequestLog(ctx, rl.DB())
_, err := j.app.Database.Requests.InsertRequestLog(ctx, requestid, rl.DB())
if err != nil {
return err
}

View File

@ -347,7 +347,7 @@ func (app *Application) DeliverMessage(ctx context.Context, client models.Client
if client.FCMToken != nil {
fcmDelivID, err := app.Pusher.SendNotification(ctx, client, msg)
if err != nil {
log.Warn().Int64("SCNMessageID", msg.SCNMessageID.IntID()).Int64("ClientID", client.ClientID.IntID()).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 langext.Ptr(fcmDelivID), nil

View File

@ -17,7 +17,7 @@ const (
type Delivery struct {
DeliveryID DeliveryID
SCNMessageID SCNMessageID
MessageID MessageID
ReceiverUserID UserID
ReceiverClientID ClientID
TimestampCreated time.Time
@ -31,7 +31,7 @@ type Delivery struct {
func (d Delivery) JSON() DeliveryJSON {
return DeliveryJSON{
DeliveryID: d.DeliveryID,
SCNMessageID: d.SCNMessageID,
MessageID: d.MessageID,
ReceiverUserID: d.ReceiverUserID,
ReceiverClientID: d.ReceiverClientID,
TimestampCreated: d.TimestampCreated.Format(time.RFC3339Nano),
@ -49,7 +49,7 @@ func (d Delivery) MaxRetryCount() int {
type DeliveryJSON struct {
DeliveryID DeliveryID `json:"delivery_id"`
SCNMessageID SCNMessageID `json:"scn_message_id"`
MessageID MessageID `json:"message_id"`
ReceiverUserID UserID `json:"receiver_user_id"`
ReceiverClientID ClientID `json:"receiver_client_id"`
TimestampCreated string `json:"timestamp_created"`
@ -62,7 +62,7 @@ type DeliveryJSON struct {
type DeliveryDB struct {
DeliveryID DeliveryID `db:"delivery_id"`
SCNMessageID SCNMessageID `db:"scn_message_id"`
MessageID MessageID `db:"message_id"`
ReceiverUserID UserID `db:"receiver_user_id"`
ReceiverClientID ClientID `db:"receiver_client_id"`
TimestampCreated int64 `db:"timestamp_created"`
@ -76,7 +76,7 @@ type DeliveryDB struct {
func (d DeliveryDB) Model() Delivery {
return Delivery{
DeliveryID: d.DeliveryID,
SCNMessageID: d.SCNMessageID,
MessageID: d.MessageID,
ReceiverUserID: d.ReceiverUserID,
ReceiverClientID: d.ReceiverClientID,
TimestampCreated: time.UnixMilli(d.TimestampCreated),

View File

@ -1,78 +1,376 @@
package models
import "strconv"
import (
"crypto/rand"
"errors"
"fmt"
"github.com/go-playground/validator/v10"
"github.com/rs/zerolog/log"
"gogs.mikescher.com/BlackForestBytes/goext/langext"
"math/big"
"reflect"
"regexp"
"strings"
)
type EntityID interface {
IntID() int64
String() string
Valid() error
Prefix() string
Raw() string
CheckString() string
Regex() *regexp.Regexp
}
type UserID int64
const idlen = 24
func (id UserID) IntID() int64 {
return int64(id)
const checklen = 1
const idCharset = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
const idCharsetLen = len(idCharset)
var charSetReverseMap = generateCharsetMap()
const (
prefixUserID = "USR"
prefixChannelID = "CHA"
prefixDeliveryID = "DEL"
prefixMessageID = "MSG"
prefixSubscriptionID = "SUB"
prefixClientID = "CLN"
prefixRequestID = "REQ"
)
var (
regexUserID = generateRegex(prefixUserID)
regexChannelID = generateRegex(prefixChannelID)
regexDeliveryID = generateRegex(prefixDeliveryID)
regexMessageID = generateRegex(prefixMessageID)
regexSubscriptionID = generateRegex(prefixSubscriptionID)
regexClientID = generateRegex(prefixClientID)
regexRequestID = generateRegex(prefixRequestID)
)
func generateRegex(prefix string) *regexp.Regexp {
return regexp.MustCompile(fmt.Sprintf("^%s[%s]{%d}[%s]{%d}$", prefix, idCharset, idlen-len(prefix)-checklen, idCharset, checklen))
}
func generateCharsetMap() []int {
result := make([]int, 128)
for i := 0; i < len(result); i++ {
result[i] = -1
}
for idx, chr := range idCharset {
result[int(chr)] = idx
}
return result
}
func generateID(prefix string) string {
k := ""
max := big.NewInt(int64(idCharsetLen))
checksum := 0
for i := 0; i < idlen-len(prefix)-checklen; i++ {
v, err := rand.Int(rand.Reader, max)
if err != nil {
panic(err)
}
v64 := v.Int64()
k += string(idCharset[v64])
checksum = (checksum + int(v64)) % (idCharsetLen)
}
checkstr := string(idCharset[checksum%idCharsetLen])
return prefix + k + checkstr
}
func validateID(prefix string, value string) error {
if len(value) != idlen {
return errors.New("id has the wrong length")
}
if !strings.HasPrefix(value, prefix) {
return errors.New("id is missing the correct prefix")
}
checksum := 0
for i := len(prefix); i < len(value)-checklen; i++ {
ichr := int(value[i])
if ichr < 0 || ichr >= len(charSetReverseMap) || charSetReverseMap[ichr] == -1 {
return errors.New("id contains invalid characters")
}
checksum = (checksum + charSetReverseMap[ichr]) % (idCharsetLen)
}
checkstr := string(idCharset[checksum%idCharsetLen])
if !strings.HasSuffix(value, checkstr) {
return errors.New("id checkstring is invalid")
}
return nil
}
func getRawData(prefix string, value string) string {
if len(value) != idlen {
return ""
}
return value[len(prefix) : idlen-checklen]
}
func getCheckString(prefix string, value string) string {
if len(value) != idlen {
return ""
}
return value[idlen-checklen:]
}
func ValidateEntityID(vfl validator.FieldLevel) bool {
if !vfl.Field().CanInterface() {
log.Error().Msgf("Failed to validate EntityID (cannot interface ?!?)")
return false
}
ifvalue := vfl.Field().Interface()
if value1, ok := ifvalue.(EntityID); ok {
if vfl.Field().Type().Kind() == reflect.Pointer && langext.IsNil(value1) {
return true
}
if err := value1.Valid(); err != nil {
log.Debug().Msgf("Failed to validate EntityID '%s' (%s)", value1.String(), err.Error())
return false
} else {
return true
}
} else {
log.Error().Msgf("Failed to validate EntityID (wrong type: %T)", ifvalue)
return false
}
}
// ------------------------------------------------------------
type UserID string
func NewUserID() UserID {
return UserID(generateID(prefixUserID))
}
func (id UserID) Valid() error {
return validateID(prefixUserID, string(id))
}
func (id UserID) String() string {
return strconv.FormatInt(int64(id), 10)
return string(id)
}
type ChannelID int64
func (id UserID) Prefix() string {
return prefixUserID
}
func (id ChannelID) IntID() int64 {
return int64(id)
func (id UserID) Raw() string {
return getRawData(prefixUserID, string(id))
}
func (id UserID) CheckString() string {
return getCheckString(prefixUserID, string(id))
}
func (id UserID) Regex() *regexp.Regexp {
return regexUserID
}
// ------------------------------------------------------------
type ChannelID string
func NewChannelID() ChannelID {
return ChannelID(generateID(prefixChannelID))
}
func (id ChannelID) Valid() error {
return validateID(prefixChannelID, string(id))
}
func (id ChannelID) String() string {
return strconv.FormatInt(int64(id), 10)
return string(id)
}
type DeliveryID int64
func (id ChannelID) Prefix() string {
return prefixChannelID
}
func (id DeliveryID) IntID() int64 {
return int64(id)
func (id ChannelID) Raw() string {
return getRawData(prefixChannelID, string(id))
}
func (id ChannelID) CheckString() string {
return getCheckString(prefixChannelID, string(id))
}
func (id ChannelID) Regex() *regexp.Regexp {
return regexChannelID
}
// ------------------------------------------------------------
type DeliveryID string
func NewDeliveryID() DeliveryID {
return DeliveryID(generateID(prefixDeliveryID))
}
func (id DeliveryID) Valid() error {
return validateID(prefixDeliveryID, string(id))
}
func (id DeliveryID) String() string {
return strconv.FormatInt(int64(id), 10)
return string(id)
}
type SCNMessageID int64
func (id SCNMessageID) IntID() int64 {
return int64(id)
func (id DeliveryID) Prefix() string {
return prefixDeliveryID
}
func (id SCNMessageID) String() string {
return strconv.FormatInt(int64(id), 10)
func (id DeliveryID) Raw() string {
return getRawData(prefixDeliveryID, string(id))
}
type SubscriptionID int64
func (id DeliveryID) CheckString() string {
return getCheckString(prefixDeliveryID, string(id))
}
func (id SubscriptionID) IntID() int64 {
return int64(id)
func (id DeliveryID) Regex() *regexp.Regexp {
return regexDeliveryID
}
// ------------------------------------------------------------
type MessageID string
func NewMessageID() MessageID {
return MessageID(generateID(prefixMessageID))
}
func (id MessageID) Valid() error {
return validateID(prefixMessageID, string(id))
}
func (id MessageID) String() string {
return string(id)
}
func (id MessageID) Prefix() string {
return prefixMessageID
}
func (id MessageID) Raw() string {
return getRawData(prefixMessageID, string(id))
}
func (id MessageID) CheckString() string {
return getCheckString(prefixMessageID, string(id))
}
func (id MessageID) Regex() *regexp.Regexp {
return regexMessageID
}
// ------------------------------------------------------------
type SubscriptionID string
func NewSubscriptionID() SubscriptionID {
return SubscriptionID(generateID(prefixSubscriptionID))
}
func (id SubscriptionID) Valid() error {
return validateID(prefixSubscriptionID, string(id))
}
func (id SubscriptionID) String() string {
return strconv.FormatInt(int64(id), 10)
return string(id)
}
type ClientID int64
func (id SubscriptionID) Prefix() string {
return prefixSubscriptionID
}
func (id ClientID) IntID() int64 {
return int64(id)
func (id SubscriptionID) Raw() string {
return getRawData(prefixSubscriptionID, string(id))
}
func (id SubscriptionID) CheckString() string {
return getCheckString(prefixSubscriptionID, string(id))
}
func (id SubscriptionID) Regex() *regexp.Regexp {
return regexSubscriptionID
}
// ------------------------------------------------------------
type ClientID string
func NewClientID() ClientID {
return ClientID(generateID(prefixClientID))
}
func (id ClientID) Valid() error {
return validateID(prefixClientID, string(id))
}
func (id ClientID) String() string {
return strconv.FormatInt(int64(id), 10)
return string(id)
}
type RequestID int64
func (id ClientID) Prefix() string {
return prefixClientID
}
func (id RequestID) IntID() int64 {
return int64(id)
func (id ClientID) Raw() string {
return getRawData(prefixClientID, string(id))
}
func (id ClientID) CheckString() string {
return getCheckString(prefixClientID, string(id))
}
func (id ClientID) Regex() *regexp.Regexp {
return regexClientID
}
// ------------------------------------------------------------
type RequestID string
func NewRequestID() RequestID {
return RequestID(generateID(prefixRequestID))
}
func (id RequestID) Valid() error {
return validateID(prefixRequestID, string(id))
}
func (id RequestID) String() string {
return strconv.FormatInt(int64(id), 10)
return string(id)
}
func (id RequestID) Prefix() string {
return prefixRequestID
}
func (id RequestID) Raw() string {
return getRawData(prefixRequestID, string(id))
}
func (id RequestID) CheckString() string {
return getCheckString(prefixRequestID, string(id))
}
func (id RequestID) Regex() *regexp.Regexp {
return regexRequestID
}

View File

@ -13,7 +13,7 @@ const (
)
type Message struct {
SCNMessageID SCNMessageID
MessageID MessageID
SenderUserID UserID
OwnerUserID UserID
ChannelInternalName string
@ -31,7 +31,7 @@ type Message struct {
func (m Message) FullJSON() MessageJSON {
return MessageJSON{
SCNMessageID: m.SCNMessageID,
MessageID: m.MessageID,
SenderUserID: m.SenderUserID,
OwnerUserID: m.OwnerUserID,
ChannelInternalName: m.ChannelInternalName,
@ -49,7 +49,7 @@ func (m Message) FullJSON() MessageJSON {
func (m Message) TrimmedJSON() MessageJSON {
return MessageJSON{
SCNMessageID: m.SCNMessageID,
MessageID: m.MessageID,
SenderUserID: m.SenderUserID,
OwnerUserID: m.OwnerUserID,
ChannelInternalName: m.ChannelInternalName,
@ -94,41 +94,41 @@ func (m Message) ShortContent() string {
}
type MessageJSON struct {
SCNMessageID SCNMessageID `json:"scn_message_id"`
SenderUserID UserID `json:"sender_user_id"`
OwnerUserID UserID `json:"owner_user_id"`
ChannelInternalName string `json:"channel_internal_name"`
ChannelID ChannelID `json:"channel_id"`
SenderName *string `json:"sender_name"`
SenderIP string `json:"sender_ip"`
Timestamp string `json:"timestamp"`
Title string `json:"title"`
Content *string `json:"content"`
Priority int `json:"priority"`
UserMessageID *string `json:"usr_message_id"`
Trimmed bool `json:"trimmed"`
MessageID MessageID `json:"message_id"`
SenderUserID UserID `json:"sender_user_id"`
OwnerUserID UserID `json:"owner_user_id"`
ChannelInternalName string `json:"channel_internal_name"`
ChannelID ChannelID `json:"channel_id"`
SenderName *string `json:"sender_name"`
SenderIP string `json:"sender_ip"`
Timestamp string `json:"timestamp"`
Title string `json:"title"`
Content *string `json:"content"`
Priority int `json:"priority"`
UserMessageID *string `json:"usr_message_id"`
Trimmed bool `json:"trimmed"`
}
type MessageDB struct {
SCNMessageID SCNMessageID `db:"scn_message_id"`
SenderUserID UserID `db:"sender_user_id"`
OwnerUserID UserID `db:"owner_user_id"`
ChannelInternalName string `db:"channel_internal_name"`
ChannelID ChannelID `db:"channel_id"`
SenderName *string `db:"sender_name"`
SenderIP string `db:"sender_ip"`
TimestampReal int64 `db:"timestamp_real"`
TimestampClient *int64 `db:"timestamp_client"`
Title string `db:"title"`
Content *string `db:"content"`
Priority int `db:"priority"`
UserMessageID *string `db:"usr_message_id"`
Deleted int `db:"deleted"`
MessageID MessageID `db:"message_id"`
SenderUserID UserID `db:"sender_user_id"`
OwnerUserID UserID `db:"owner_user_id"`
ChannelInternalName string `db:"channel_internal_name"`
ChannelID ChannelID `db:"channel_id"`
SenderName *string `db:"sender_name"`
SenderIP string `db:"sender_ip"`
TimestampReal int64 `db:"timestamp_real"`
TimestampClient *int64 `db:"timestamp_client"`
Title string `db:"title"`
Content *string `db:"content"`
Priority int `db:"priority"`
UserMessageID *string `db:"usr_message_id"`
Deleted int `db:"deleted"`
}
func (m MessageDB) Model() Message {
return Message{
SCNMessageID: m.SCNMessageID,
MessageID: m.MessageID,
SenderUserID: m.SenderUserID,
OwnerUserID: m.OwnerUserID,
ChannelInternalName: m.ChannelInternalName,

View File

@ -48,7 +48,7 @@ func (f MessageFilter) SQL() (string, string, sq.PP, error) {
joinClause += " LEFT JOIN subscriptions AS subs on messages.channel_id = subs.channel_id "
}
if f.SearchString != nil {
joinClause += " JOIN messages_fts AS mfts on (mfts.rowid = messages.scn_message_id) "
joinClause += " JOIN messages_fts AS mfts on (mfts.rowid = messages.rowid) "
}
sqlClauses := make([]string, 0)

View File

@ -56,7 +56,7 @@ func (fb FirebaseConnector) SendNotification(ctx context.Context, client models.
jsonBody := gin.H{
"data": gin.H{
"scn_msg_id": msg.SCNMessageID.String(),
"scn_msg_id": msg.MessageID.String(),
"usr_msg_id": langext.Coalesce(msg.UserMessageID, ""),
"client_id": client.ClientID.String(),
"timestamp": strconv.FormatInt(msg.Timestamp().Unix(), 10),

View File

@ -71,8 +71,8 @@
"in": "query"
},
{
"type": "integer",
"example": 7725,
"type": "string",
"example": "7725",
"name": "user_id",
"in": "query"
},
@ -144,8 +144,8 @@
"in": "formData"
},
{
"type": "integer",
"example": 7725,
"type": "string",
"example": "7725",
"name": "user_id",
"in": "formData"
},
@ -478,7 +478,7 @@
"parameters": [
{
"type": "integer",
"description": "SCNMessageID",
"description": "MessageID",
"name": "mid",
"in": "path",
"required": true
@ -527,7 +527,7 @@
"parameters": [
{
"type": "integer",
"description": "SCNMessageID",
"description": "MessageID",
"name": "mid",
"in": "path",
"required": true
@ -2093,8 +2093,8 @@
"in": "query"
},
{
"type": "integer",
"example": 7725,
"type": "string",
"example": "7725",
"name": "user_id",
"in": "query"
},
@ -2166,8 +2166,8 @@
"in": "formData"
},
{
"type": "integer",
"example": 7725,
"type": "string",
"example": "7725",
"name": "user_id",
"in": "formData"
},
@ -2414,13 +2414,13 @@
"type": "object",
"properties": {
"channel_id": {
"type": "integer"
"type": "string"
},
"channel_internal_name": {
"type": "string"
},
"channel_owner_user_id": {
"type": "integer"
"type": "string"
}
}
},
@ -2686,8 +2686,8 @@
"example": "Hello World"
},
"user_id": {
"type": "integer",
"example": 7725
"type": "string",
"example": "7725"
},
"user_key": {
"type": "string",
@ -2828,7 +2828,7 @@
"type": "integer"
},
"scn_msg_id": {
"type": "integer"
"type": "string"
},
"success": {
"type": "boolean"
@ -2842,7 +2842,7 @@
"type": "object",
"properties": {
"channel_id": {
"type": "integer"
"type": "string"
},
"description_name": {
"type": "string"
@ -2857,7 +2857,7 @@
"type": "integer"
},
"owner_user_id": {
"type": "integer"
"type": "string"
},
"send_key": {
"description": "can be nil, depending on endpoint",
@ -2888,7 +2888,7 @@
"type": "string"
},
"client_id": {
"type": "integer"
"type": "string"
},
"fcm_token": {
"type": "string"
@ -2900,7 +2900,7 @@
"type": "string"
},
"user_id": {
"type": "integer"
"type": "string"
}
}
},
@ -2934,7 +2934,7 @@
"type": "object",
"properties": {
"channel_id": {
"type": "integer"
"type": "string"
},
"channel_internal_name": {
"type": "string"
@ -2942,15 +2942,15 @@
"content": {
"type": "string"
},
"message_id": {
"type": "string"
},
"owner_user_id": {
"type": "integer"
"type": "string"
},
"priority": {
"type": "integer"
},
"scn_message_id": {
"type": "integer"
},
"sender_ip": {
"type": "string"
},
@ -2958,7 +2958,7 @@
"type": "string"
},
"sender_user_id": {
"type": "integer"
"type": "string"
},
"timestamp": {
"type": "string"
@ -2978,22 +2978,22 @@
"type": "object",
"properties": {
"channel_id": {
"type": "integer"
"type": "string"
},
"channel_internal_name": {
"type": "string"
},
"channel_owner_user_id": {
"type": "integer"
"type": "string"
},
"confirmed": {
"type": "boolean"
},
"subscriber_user_id": {
"type": "integer"
"type": "string"
},
"subscription_id": {
"type": "integer"
"type": "string"
},
"timestamp_created": {
"type": "string"
@ -3040,7 +3040,7 @@
"type": "string"
},
"user_id": {
"type": "integer"
"type": "string"
},
"username": {
"type": "string"
@ -3093,7 +3093,7 @@
"type": "string"
},
"user_id": {
"type": "integer"
"type": "string"
},
"username": {
"type": "string"

View File

@ -57,11 +57,11 @@ definitions:
handler.CreateSubscription.body:
properties:
channel_id:
type: integer
type: string
channel_internal_name:
type: string
channel_owner_user_id:
type: integer
type: string
type: object
handler.CreateUser.body:
properties:
@ -237,8 +237,8 @@ definitions:
example: Hello World
type: string
user_id:
example: 7725
type: integer
example: "7725"
type: string
user_key:
example: P3TNH8mvv14fm
type: string
@ -330,7 +330,7 @@ definitions:
quota_max:
type: integer
scn_msg_id:
type: integer
type: string
success:
type: boolean
suppress_send:
@ -339,7 +339,7 @@ definitions:
models.ChannelWithSubscriptionJSON:
properties:
channel_id:
type: integer
type: string
description_name:
type: string
display_name:
@ -349,7 +349,7 @@ definitions:
messages_sent:
type: integer
owner_user_id:
type: integer
type: string
send_key:
description: can be nil, depending on endpoint
type: string
@ -370,7 +370,7 @@ definitions:
agent_version:
type: string
client_id:
type: integer
type: string
fcm_token:
type: string
timestamp_created:
@ -378,7 +378,7 @@ definitions:
type:
type: string
user_id:
type: integer
type: string
type: object
models.CompatMessage:
properties:
@ -400,23 +400,23 @@ definitions:
models.MessageJSON:
properties:
channel_id:
type: integer
type: string
channel_internal_name:
type: string
content:
type: string
message_id:
type: string
owner_user_id:
type: integer
type: string
priority:
type: integer
scn_message_id:
type: integer
sender_ip:
type: string
sender_name:
type: string
sender_user_id:
type: integer
type: string
timestamp:
type: string
title:
@ -429,17 +429,17 @@ definitions:
models.SubscriptionJSON:
properties:
channel_id:
type: integer
type: string
channel_internal_name:
type: string
channel_owner_user_id:
type: integer
type: string
confirmed:
type: boolean
subscriber_user_id:
type: integer
type: string
subscription_id:
type: integer
type: string
timestamp_created:
type: string
type: object
@ -470,7 +470,7 @@ definitions:
timestamp_lastsent:
type: string
user_id:
type: integer
type: string
username:
type: string
type: object
@ -505,7 +505,7 @@ definitions:
timestamp_lastsent:
type: string
user_id:
type: integer
type: string
username:
type: string
type: object
@ -557,10 +557,10 @@ paths:
in: query
name: title
type: string
- example: 7725
- example: "7725"
in: query
name: user_id
type: integer
type: string
- example: P3TNH8mvv14fm
in: query
name: user_key
@ -606,10 +606,10 @@ paths:
in: formData
name: title
type: string
- example: 7725
- example: "7725"
in: formData
name: user_id
type: integer
type: string
- example: P3TNH8mvv14fm
in: formData
name: user_key
@ -836,7 +836,7 @@ paths:
ADMIN Key
operationId: api-messages-delete
parameters:
- description: SCNMessageID
- description: MessageID
in: path
name: mid
required: true
@ -872,7 +872,7 @@ paths:
The returned message is never trimmed
operationId: api-messages-get
parameters:
- description: SCNMessageID
- description: MessageID
in: path
name: mid
required: true
@ -1941,10 +1941,10 @@ paths:
in: query
name: title
type: string
- example: 7725
- example: "7725"
in: query
name: user_id
type: integer
type: string
- example: P3TNH8mvv14fm
in: query
name: user_key
@ -1990,10 +1990,10 @@ paths:
in: formData
name: title
type: string
- example: 7725
- example: "7725"
in: formData
name: user_id
type: integer
type: string
- example: P3TNH8mvv14fm
in: formData
name: user_key

View File

@ -21,7 +21,7 @@ func TestCreateChannel(t *testing.T) {
"fcm_token": "DUMMY_FCM",
})
uid := int(r0["user_id"].(float64))
uid := r0["user_id"].(string)
admintok := r0["admin_key"].(string)
type chanlist struct {
@ -29,28 +29,28 @@ func TestCreateChannel(t *testing.T) {
}
{
clist := tt.RequestAuthGet[chanlist](t, admintok, baseUrl, fmt.Sprintf("/api/users/%d/channels", uid))
clist := tt.RequestAuthGet[chanlist](t, admintok, baseUrl, fmt.Sprintf("/api/users/%s/channels", uid))
tt.AssertMappedSet(t, "channels", []string{}, clist.Channels, "display_name")
tt.AssertMappedSet(t, "channels", []string{}, clist.Channels, "internal_name")
}
tt.RequestAuthPost[gin.H](t, admintok, baseUrl, fmt.Sprintf("/api/users/%d/channels", uid), gin.H{
tt.RequestAuthPost[gin.H](t, admintok, baseUrl, fmt.Sprintf("/api/users/%s/channels", uid), gin.H{
"name": "test",
})
{
clist := tt.RequestAuthGet[chanlist](t, admintok, baseUrl, fmt.Sprintf("/api/users/%d/channels", uid))
clist := tt.RequestAuthGet[chanlist](t, admintok, baseUrl, fmt.Sprintf("/api/users/%s/channels", uid))
tt.AssertEqual(t, "chan.len", 1, len(clist.Channels))
tt.AssertMappedSet(t, "channels", []string{"test"}, clist.Channels, "display_name")
tt.AssertMappedSet(t, "channels", []string{"test"}, clist.Channels, "internal_name")
}
tt.RequestAuthPost[gin.H](t, admintok, baseUrl, fmt.Sprintf("/api/users/%d/channels", uid), gin.H{
tt.RequestAuthPost[gin.H](t, admintok, baseUrl, fmt.Sprintf("/api/users/%s/channels", uid), gin.H{
"name": "asdf",
})
{
clist := tt.RequestAuthGet[chanlist](t, admintok, baseUrl, fmt.Sprintf("/api/users/%d/channels", uid))
clist := tt.RequestAuthGet[chanlist](t, admintok, baseUrl, fmt.Sprintf("/api/users/%s/channels", uid))
tt.AssertMappedSet(t, "channels", []string{"asdf", "test"}, clist.Channels, "display_name")
tt.AssertMappedSet(t, "channels", []string{"asdf", "test"}, clist.Channels, "internal_name")
}
@ -67,10 +67,10 @@ func TestCreateChannelNameTooLong(t *testing.T) {
"fcm_token": "DUMMY_FCM",
})
uid := int(r0["user_id"].(float64))
uid := r0["user_id"].(string)
admintok := r0["admin_key"].(string)
tt.RequestAuthPostShouldFail(t, admintok, baseUrl, fmt.Sprintf("/api/users/%d/channels", uid), gin.H{
tt.RequestAuthPostShouldFail(t, admintok, baseUrl, fmt.Sprintf("/api/users/%s/channels", uid), gin.H{
"name": langext.StrRepeat("X", 121),
}, 400, apierr.CHANNEL_TOO_LONG)
}
@ -86,7 +86,7 @@ func TestChannelNameNormalization(t *testing.T) {
"fcm_token": "DUMMY_FCM",
})
uid := int(r0["user_id"].(float64))
uid := r0["user_id"].(string)
admintok := r0["admin_key"].(string)
type chanlist struct {
@ -94,57 +94,57 @@ func TestChannelNameNormalization(t *testing.T) {
}
{
clist := tt.RequestAuthGet[chanlist](t, admintok, baseUrl, fmt.Sprintf("/api/users/%d/channels", uid))
clist := tt.RequestAuthGet[chanlist](t, admintok, baseUrl, fmt.Sprintf("/api/users/%s/channels", uid))
tt.AssertMappedSet(t, "channels", []string{}, clist.Channels, "display_name")
tt.AssertMappedSet(t, "channels", []string{}, clist.Channels, "internal_name")
}
tt.RequestAuthPost[gin.H](t, admintok, baseUrl, fmt.Sprintf("/api/users/%d/channels", uid), gin.H{
tt.RequestAuthPost[gin.H](t, admintok, baseUrl, fmt.Sprintf("/api/users/%s/channels", uid), gin.H{
"name": "tESt",
})
{
clist := tt.RequestAuthGet[chanlist](t, admintok, baseUrl, fmt.Sprintf("/api/users/%d/channels", uid))
clist := tt.RequestAuthGet[chanlist](t, admintok, baseUrl, fmt.Sprintf("/api/users/%s/channels", uid))
tt.AssertMappedSet(t, "channels", []string{"tESt"}, clist.Channels, "display_name")
tt.AssertMappedSet(t, "channels", []string{"test"}, clist.Channels, "internal_name")
}
tt.RequestAuthPostShouldFail(t, admintok, baseUrl, fmt.Sprintf("/api/users/%d/channels", uid), gin.H{
tt.RequestAuthPostShouldFail(t, admintok, baseUrl, fmt.Sprintf("/api/users/%s/channels", uid), gin.H{
"name": "test",
}, 409, apierr.CHANNEL_ALREADY_EXISTS)
tt.RequestAuthPostShouldFail(t, admintok, baseUrl, fmt.Sprintf("/api/users/%d/channels", uid), gin.H{
tt.RequestAuthPostShouldFail(t, admintok, baseUrl, fmt.Sprintf("/api/users/%s/channels", uid), gin.H{
"name": "TEST",
}, 409, apierr.CHANNEL_ALREADY_EXISTS)
tt.RequestAuthPostShouldFail(t, admintok, baseUrl, fmt.Sprintf("/api/users/%d/channels", uid), gin.H{
tt.RequestAuthPostShouldFail(t, admintok, baseUrl, fmt.Sprintf("/api/users/%s/channels", uid), gin.H{
"name": "Test",
}, 409, apierr.CHANNEL_ALREADY_EXISTS)
tt.RequestAuthPostShouldFail(t, admintok, baseUrl, fmt.Sprintf("/api/users/%d/channels", uid), gin.H{
tt.RequestAuthPostShouldFail(t, admintok, baseUrl, fmt.Sprintf("/api/users/%s/channels", uid), gin.H{
"name": "Test ",
}, 409, apierr.CHANNEL_ALREADY_EXISTS)
tt.RequestAuthPostShouldFail(t, admintok, baseUrl, fmt.Sprintf("/api/users/%d/channels", uid), gin.H{
tt.RequestAuthPostShouldFail(t, admintok, baseUrl, fmt.Sprintf("/api/users/%s/channels", uid), gin.H{
"name": " Test",
}, 409, apierr.CHANNEL_ALREADY_EXISTS)
tt.RequestAuthPostShouldFail(t, admintok, baseUrl, fmt.Sprintf("/api/users/%d/channels", uid), gin.H{
tt.RequestAuthPostShouldFail(t, admintok, baseUrl, fmt.Sprintf("/api/users/%s/channels", uid), gin.H{
"name": "\rTeSt\n",
}, 409, apierr.CHANNEL_ALREADY_EXISTS)
{
clist := tt.RequestAuthGet[chanlist](t, admintok, baseUrl, fmt.Sprintf("/api/users/%d/channels", uid))
clist := tt.RequestAuthGet[chanlist](t, admintok, baseUrl, fmt.Sprintf("/api/users/%s/channels", uid))
tt.AssertMappedSet(t, "channels", []string{"tESt"}, clist.Channels, "display_name")
tt.AssertMappedSet(t, "channels", []string{"test"}, clist.Channels, "internal_name")
}
tt.RequestAuthPost[gin.H](t, admintok, baseUrl, fmt.Sprintf("/api/users/%d/channels", uid), gin.H{
tt.RequestAuthPost[gin.H](t, admintok, baseUrl, fmt.Sprintf("/api/users/%s/channels", uid), gin.H{
"name": " WeiRD_[\uF5FF]\\stUFf\r\n\t ",
})
{
clist := tt.RequestAuthGet[chanlist](t, admintok, baseUrl, fmt.Sprintf("/api/users/%d/channels", uid))
clist := tt.RequestAuthGet[chanlist](t, admintok, baseUrl, fmt.Sprintf("/api/users/%s/channels", uid))
tt.AssertMappedSet(t, "channels", []string{"tESt", "WeiRD_[\uF5FF]\\stUFf"}, clist.Channels, "display_name")
tt.AssertMappedSet(t, "channels", []string{"test", "weird_[\uF5FF]\\stuff"}, clist.Channels, "internal_name")
}
@ -181,7 +181,7 @@ func TestListChannelsDefault(t *testing.T) {
}
for k, v := range testdata {
r0 := tt.RequestAuthGet[chanlist](t, data.User[k].AdminKey, baseUrl, fmt.Sprintf("/api/users/%d/channels", data.User[k].UID))
r0 := tt.RequestAuthGet[chanlist](t, data.User[k].AdminKey, baseUrl, fmt.Sprintf("/api/users/%s/channels", data.User[k].UID))
tt.AssertMappedSet(t, fmt.Sprintf("%d->chanlist", k), v, r0.Channels, "internal_name")
}
}
@ -216,7 +216,7 @@ func TestListChannelsOwned(t *testing.T) {
}
for k, v := range testdata {
r0 := tt.RequestAuthGet[chanlist](t, data.User[k].AdminKey, baseUrl, fmt.Sprintf("/api/users/%d/channels?selector=%s", data.User[k].UID, "owned"))
r0 := tt.RequestAuthGet[chanlist](t, data.User[k].AdminKey, baseUrl, fmt.Sprintf("/api/users/%s/channels?selector=%s", data.User[k].UID, "owned"))
tt.AssertMappedSet(t, fmt.Sprintf("%d->chanlist", k), v, r0.Channels, "internal_name")
}
}
@ -251,7 +251,7 @@ func TestListChannelsSubscribedAny(t *testing.T) {
}
for k, v := range testdata {
r0 := tt.RequestAuthGet[chanlist](t, data.User[k].AdminKey, baseUrl, fmt.Sprintf("/api/users/%d/channels?selector=%s", data.User[k].UID, "subscribed_any"))
r0 := tt.RequestAuthGet[chanlist](t, data.User[k].AdminKey, baseUrl, fmt.Sprintf("/api/users/%s/channels?selector=%s", data.User[k].UID, "subscribed_any"))
tt.AssertMappedSet(t, fmt.Sprintf("%d->chanlist", k), v, r0.Channels, "internal_name")
}
}
@ -286,7 +286,7 @@ func TestListChannelsAllAny(t *testing.T) {
}
for k, v := range testdata {
r0 := tt.RequestAuthGet[chanlist](t, data.User[k].AdminKey, baseUrl, fmt.Sprintf("/api/users/%d/channels?selector=%s", data.User[k].UID, "all_any"))
r0 := tt.RequestAuthGet[chanlist](t, data.User[k].AdminKey, baseUrl, fmt.Sprintf("/api/users/%s/channels?selector=%s", data.User[k].UID, "all_any"))
tt.AssertMappedSet(t, fmt.Sprintf("%d->chanlist", k), v, r0.Channels, "internal_name")
}
}
@ -321,7 +321,7 @@ func TestListChannelsSubscribed(t *testing.T) {
}
for k, v := range testdata {
r0 := tt.RequestAuthGet[chanlist](t, data.User[k].AdminKey, baseUrl, fmt.Sprintf("/api/users/%d/channels?selector=%s", data.User[k].UID, "subscribed"))
r0 := tt.RequestAuthGet[chanlist](t, data.User[k].AdminKey, baseUrl, fmt.Sprintf("/api/users/%s/channels?selector=%s", data.User[k].UID, "subscribed"))
tt.AssertMappedSet(t, fmt.Sprintf("%d->chanlist", k), v, r0.Channels, "internal_name")
}
}
@ -356,7 +356,7 @@ func TestListChannelsAll(t *testing.T) {
}
for k, v := range testdata {
r0 := tt.RequestAuthGet[chanlist](t, data.User[k].AdminKey, baseUrl, fmt.Sprintf("/api/users/%d/channels?selector=%s", data.User[k].UID, "all"))
r0 := tt.RequestAuthGet[chanlist](t, data.User[k].AdminKey, baseUrl, fmt.Sprintf("/api/users/%s/channels?selector=%s", data.User[k].UID, "all"))
tt.AssertMappedSet(t, fmt.Sprintf("%d->chanlist", k), v, r0.Channels, "internal_name")
}
}
@ -372,7 +372,7 @@ func TestChannelUpdate(t *testing.T) {
"fcm_token": "DUMMY_FCM",
})
uid := int(r0["user_id"].(float64))
uid := r0["user_id"].(string)
admintok := r0["admin_key"].(string)
type chanlist struct {
@ -380,25 +380,25 @@ func TestChannelUpdate(t *testing.T) {
}
{
clist := tt.RequestAuthGet[chanlist](t, admintok, baseUrl, fmt.Sprintf("/api/users/%d/channels", uid))
clist := tt.RequestAuthGet[chanlist](t, admintok, baseUrl, fmt.Sprintf("/api/users/%s/channels", uid))
tt.AssertMappedSet(t, "channels", []string{}, clist.Channels, "display_name")
tt.AssertMappedSet(t, "channels", []string{}, clist.Channels, "internal_name")
}
chan0 := tt.RequestAuthPost[gin.H](t, admintok, baseUrl, fmt.Sprintf("/api/users/%d/channels", uid), gin.H{
chan0 := tt.RequestAuthPost[gin.H](t, admintok, baseUrl, fmt.Sprintf("/api/users/%s/channels", uid), gin.H{
"name": "server-alerts",
})
chanid := fmt.Sprintf("%v", chan0["channel_id"])
{
clist := tt.RequestAuthGet[chanlist](t, admintok, baseUrl, fmt.Sprintf("/api/users/%d/channels", uid))
clist := tt.RequestAuthGet[chanlist](t, admintok, baseUrl, fmt.Sprintf("/api/users/%s/channels", uid))
tt.AssertMappedSet(t, "channels", []string{"server-alerts"}, clist.Channels, "display_name")
tt.AssertMappedSet(t, "channels", []string{"server-alerts"}, clist.Channels, "internal_name")
tt.AssertEqual(t, "channels.descr", nil, clist.Channels[0]["description_name"])
}
{
chan1 := tt.RequestAuthGet[gin.H](t, admintok, baseUrl, fmt.Sprintf("/api/users/%d/channels/%s", uid, chanid))
chan1 := tt.RequestAuthGet[gin.H](t, admintok, baseUrl, fmt.Sprintf("/api/users/%s/channels/%s", uid, chanid))
tt.AssertEqual(t, "channels.display_name", "server-alerts", chan1["display_name"])
tt.AssertEqual(t, "channels.internal_name", "server-alerts", chan1["internal_name"])
tt.AssertEqual(t, "channels.description_name", nil, chan1["description_name"])
@ -408,12 +408,12 @@ func TestChannelUpdate(t *testing.T) {
// [1] update display_name
tt.RequestAuthPatch[tt.Void](t, admintok, baseUrl, fmt.Sprintf("/api/users/%d/channels/%s", uid, chanid), gin.H{
tt.RequestAuthPatch[tt.Void](t, admintok, baseUrl, fmt.Sprintf("/api/users/%s/channels/%s", uid, chanid), gin.H{
"display_name": "SERVER-ALERTS",
})
{
chan1 := tt.RequestAuthGet[gin.H](t, admintok, baseUrl, fmt.Sprintf("/api/users/%d/channels/%s", uid, chanid))
chan1 := tt.RequestAuthGet[gin.H](t, admintok, baseUrl, fmt.Sprintf("/api/users/%s/channels/%s", uid, chanid))
tt.AssertEqual(t, "channels.display_name", "SERVER-ALERTS", chan1["display_name"])
tt.AssertEqual(t, "channels.internal_name", "server-alerts", chan1["internal_name"])
tt.AssertEqual(t, "channels.description_name", nil, chan1["description_name"])
@ -423,70 +423,70 @@ func TestChannelUpdate(t *testing.T) {
// [2] fail to update display_name
tt.RequestAuthPatchShouldFail(t, admintok, baseUrl, fmt.Sprintf("/api/users/%d/channels/%s", uid, chanid), gin.H{
tt.RequestAuthPatchShouldFail(t, admintok, baseUrl, fmt.Sprintf("/api/users/%s/channels/%s", uid, chanid), gin.H{
"display_name": "SERVER-ALERTS2",
}, 400, apierr.CHANNEL_NAME_WOULD_CHANGE)
// [3] renew subscribe_key
tt.RequestAuthPatch[tt.Void](t, admintok, baseUrl, fmt.Sprintf("/api/users/%d/channels/%s", uid, chanid), gin.H{
tt.RequestAuthPatch[tt.Void](t, admintok, baseUrl, fmt.Sprintf("/api/users/%s/channels/%s", uid, chanid), gin.H{
"subscribe_key": true,
})
{
chan1 := tt.RequestAuthGet[gin.H](t, admintok, baseUrl, fmt.Sprintf("/api/users/%d/channels/%s", uid, chanid))
chan1 := tt.RequestAuthGet[gin.H](t, admintok, baseUrl, fmt.Sprintf("/api/users/%s/channels/%s", uid, chanid))
tt.AssertNotEqual(t, "channels.subscribe_key", chan0["subscribe_key"], chan1["subscribe_key"])
tt.AssertEqual(t, "channels.send_key", chan0["send_key"], chan1["send_key"])
}
// [4] renew send_key
tt.RequestAuthPatch[tt.Void](t, admintok, baseUrl, fmt.Sprintf("/api/users/%d/channels/%s", uid, chanid), gin.H{
tt.RequestAuthPatch[tt.Void](t, admintok, baseUrl, fmt.Sprintf("/api/users/%s/channels/%s", uid, chanid), gin.H{
"send_key": true,
})
{
chan1 := tt.RequestAuthGet[gin.H](t, admintok, baseUrl, fmt.Sprintf("/api/users/%d/channels/%s", uid, chanid))
chan1 := tt.RequestAuthGet[gin.H](t, admintok, baseUrl, fmt.Sprintf("/api/users/%s/channels/%s", uid, chanid))
tt.AssertNotEqual(t, "channels.subscribe_key", chan0["subscribe_key"], chan1["subscribe_key"])
tt.AssertNotEqual(t, "channels.send_key", chan0["send_key"], chan1["send_key"])
}
// [5] update description_name
tt.RequestAuthPatch[tt.Void](t, admintok, baseUrl, fmt.Sprintf("/api/users/%d/channels/%s", uid, chanid), gin.H{
tt.RequestAuthPatch[tt.Void](t, admintok, baseUrl, fmt.Sprintf("/api/users/%s/channels/%s", uid, chanid), gin.H{
"description_name": "hello World",
})
{
chan1 := tt.RequestAuthGet[gin.H](t, admintok, baseUrl, fmt.Sprintf("/api/users/%d/channels/%s", uid, chanid))
chan1 := tt.RequestAuthGet[gin.H](t, admintok, baseUrl, fmt.Sprintf("/api/users/%s/channels/%s", uid, chanid))
tt.AssertEqual(t, "channels.description_name", "hello World", chan1["description_name"])
}
// [6] update description_name
tt.RequestAuthPatch[tt.Void](t, admintok, baseUrl, fmt.Sprintf("/api/users/%d/channels/%s", uid, chanid), gin.H{
tt.RequestAuthPatch[tt.Void](t, admintok, baseUrl, fmt.Sprintf("/api/users/%s/channels/%s", uid, chanid), gin.H{
"description_name": " AXXhello World9 ",
})
{
chan1 := tt.RequestAuthGet[gin.H](t, admintok, baseUrl, fmt.Sprintf("/api/users/%d/channels/%s", uid, chanid))
chan1 := tt.RequestAuthGet[gin.H](t, admintok, baseUrl, fmt.Sprintf("/api/users/%s/channels/%s", uid, chanid))
tt.AssertEqual(t, "channels.description_name", "AXXhello World9", chan1["description_name"])
}
// [7] clear description_name
tt.RequestAuthPatch[tt.Void](t, admintok, baseUrl, fmt.Sprintf("/api/users/%d/channels/%s", uid, chanid), gin.H{
tt.RequestAuthPatch[tt.Void](t, admintok, baseUrl, fmt.Sprintf("/api/users/%s/channels/%s", uid, chanid), gin.H{
"description_name": "",
})
{
chan1 := tt.RequestAuthGet[gin.H](t, admintok, baseUrl, fmt.Sprintf("/api/users/%d/channels/%s", uid, chanid))
chan1 := tt.RequestAuthGet[gin.H](t, admintok, baseUrl, fmt.Sprintf("/api/users/%s/channels/%s", uid, chanid))
tt.AssertEqual(t, "channels.description_name", nil, chan1["description_name"])
}
// [8] fail to update description_name
tt.RequestAuthPatchShouldFail(t, admintok, baseUrl, fmt.Sprintf("/api/users/%d/channels/%s", uid, chanid), gin.H{
tt.RequestAuthPatchShouldFail(t, admintok, baseUrl, fmt.Sprintf("/api/users/%s/channels/%s", uid, chanid), gin.H{
"description_name": strings.Repeat("0123456789", 48),
}, 400, apierr.CHANNEL_DESCRIPTION_TOO_LONG)

View File

@ -1,3 +1,5 @@
package test
//TODO test compat methods
//TODO also test compat_id mapping

View File

@ -2,6 +2,7 @@ package test
import (
"blackforestbytes.com/simplecloudnotifier/api/apierr"
"blackforestbytes.com/simplecloudnotifier/models"
tt "blackforestbytes.com/simplecloudnotifier/test/util"
"fmt"
"github.com/gin-gonic/gin"
@ -48,7 +49,7 @@ func TestDeleteMessage(t *testing.T) {
"fcm_token": "DUMMY_FCM",
})
uid := int(r0["user_id"].(float64))
uid := r0["user_id"].(string)
sendtok := r0["send_key"].(string)
admintok := r0["admin_key"].(string)
@ -76,7 +77,7 @@ func TestDeleteMessageAndResendUsrMsgId(t *testing.T) {
"fcm_token": "DUMMY_FCM",
})
uid := int(r0["user_id"].(float64))
uid := r0["user_id"].(string)
sendtok := r0["send_key"].(string)
admintok := r0["admin_key"].(string)
@ -138,7 +139,18 @@ func TestGetMessageNotFound(t *testing.T) {
data := tt.InitDefaultData(t, ws)
tt.RequestAuthGetShouldFail(t, data.User[0].AdminKey, baseUrl, "/api/messages/8963586", 404, apierr.MESSAGE_NOT_FOUND)
tt.RequestAuthGetShouldFail(t, data.User[0].AdminKey, baseUrl, "/api/messages/"+models.NewMessageID().String(), 404, apierr.MESSAGE_NOT_FOUND)
}
func TestGetMessageInvalidID(t *testing.T) {
ws, baseUrl, stop := tt.StartSimpleWebserver(t)
defer stop()
data := tt.InitDefaultData(t, ws)
tt.RequestAuthGetShouldFail(t, data.User[0].AdminKey, baseUrl, "/api/messages/"+models.NewUserID().String(), 400, apierr.BINDFAIL_URI_PARAM)
tt.RequestAuthGetShouldFail(t, data.User[0].AdminKey, baseUrl, "/api/messages/"+"asdfxxx", 400, apierr.BINDFAIL_URI_PARAM)
}
func TestGetMessageFull(t *testing.T) {

View File

@ -2,6 +2,7 @@ package test
import (
"blackforestbytes.com/simplecloudnotifier/api/apierr"
"blackforestbytes.com/simplecloudnotifier/models"
"blackforestbytes.com/simplecloudnotifier/push"
tt "blackforestbytes.com/simplecloudnotifier/test/util"
"fmt"
@ -24,7 +25,7 @@ func TestSendSimpleMessageJSON(t *testing.T) {
"fcm_token": "DUMMY_FCM",
})
uid := int(r0["user_id"].(float64))
uid := r0["user_id"].(string)
admintok := r0["admin_key"].(string)
readtok := r0["read_key"].(string)
sendtok := r0["send_key"].(string)
@ -50,7 +51,7 @@ func TestSendSimpleMessageJSON(t *testing.T) {
tt.AssertEqual(t, "messageCount", 1, len(pusher.Data))
tt.AssertStrRepEqual(t, "msg.title", "HelloWorld_001", pusher.Last().Message.Title)
tt.AssertStrRepEqual(t, "msg.content", nil, pusher.Last().Message.Content)
tt.AssertStrRepEqual(t, "msg.scn_msg_id", msg1["scn_msg_id"], pusher.Last().Message.SCNMessageID)
tt.AssertStrRepEqual(t, "msg.scn_msg_id", msg1["scn_msg_id"], pusher.Last().Message.MessageID)
type mglist struct {
Messages []gin.H `json:"messages"`
@ -77,16 +78,16 @@ func TestSendSimpleMessageQuery(t *testing.T) {
"fcm_token": "DUMMY_FCM",
})
uid := int(r0["user_id"].(float64))
uid := r0["user_id"].(string)
admintok := r0["admin_key"].(string)
sendtok := r0["send_key"].(string)
msg1 := tt.RequestPost[gin.H](t, baseUrl, fmt.Sprintf("/?user_id=%d&user_key=%s&title=%s", uid, sendtok, url.QueryEscape("Hello World 2134")), nil)
msg1 := tt.RequestPost[gin.H](t, baseUrl, fmt.Sprintf("/?user_id=%s&user_key=%s&title=%s", uid, sendtok, url.QueryEscape("Hello World 2134")), nil)
tt.AssertEqual(t, "messageCount", 1, len(pusher.Data))
tt.AssertStrRepEqual(t, "msg.title", "Hello World 2134", pusher.Last().Message.Title)
tt.AssertStrRepEqual(t, "msg.content", nil, pusher.Last().Message.Content)
tt.AssertStrRepEqual(t, "msg.scn_msg_id", msg1["scn_msg_id"], pusher.Last().Message.SCNMessageID)
tt.AssertStrRepEqual(t, "msg.scn_msg_id", msg1["scn_msg_id"], pusher.Last().Message.MessageID)
type mglist struct {
Messages []gin.H `json:"messages"`
@ -113,20 +114,20 @@ func TestSendSimpleMessageForm(t *testing.T) {
"fcm_token": "DUMMY_FCM",
})
uid := int(r0["user_id"].(float64))
uid := r0["user_id"].(string)
admintok := r0["admin_key"].(string)
sendtok := r0["send_key"].(string)
msg1 := tt.RequestPost[gin.H](t, baseUrl, "/", tt.FormData{
"user_key": sendtok,
"user_id": fmt.Sprintf("%d", uid),
"user_id": uid,
"title": "Hello World 9999 [$$$]",
})
tt.AssertEqual(t, "messageCount", 1, len(pusher.Data))
tt.AssertStrRepEqual(t, "msg.title", "Hello World 9999 [$$$]", pusher.Last().Message.Title)
tt.AssertStrRepEqual(t, "msg.content", nil, pusher.Last().Message.Content)
tt.AssertStrRepEqual(t, "msg.scn_msg_id", msg1["scn_msg_id"], pusher.Last().Message.SCNMessageID)
tt.AssertStrRepEqual(t, "msg.scn_msg_id", msg1["scn_msg_id"], pusher.Last().Message.MessageID)
type mglist struct {
Messages []gin.H `json:"messages"`
@ -153,10 +154,10 @@ func TestSendSimpleMessageFormAndQuery(t *testing.T) {
"fcm_token": "DUMMY_FCM",
})
uid := int(r0["user_id"].(float64))
uid := r0["user_id"].(string)
sendtok := r0["send_key"].(string)
msg1 := tt.RequestPost[gin.H](t, baseUrl, fmt.Sprintf("/?user_id=%d&user_key=%s&title=%s", uid, sendtok, url.QueryEscape("1111111")), tt.FormData{
msg1 := tt.RequestPost[gin.H](t, baseUrl, fmt.Sprintf("/?user_id=%s&user_key=%s&title=%s", uid, sendtok, url.QueryEscape("1111111")), tt.FormData{
"user_key": "ERR",
"user_id": "999999",
"title": "2222222",
@ -164,7 +165,7 @@ func TestSendSimpleMessageFormAndQuery(t *testing.T) {
tt.AssertEqual(t, "messageCount", 1, len(pusher.Data))
tt.AssertStrRepEqual(t, "msg.title", "1111111", pusher.Last().Message.Title)
tt.AssertStrRepEqual(t, "msg.scn_msg_id", msg1["scn_msg_id"], pusher.Last().Message.SCNMessageID)
tt.AssertStrRepEqual(t, "msg.scn_msg_id", msg1["scn_msg_id"], pusher.Last().Message.MessageID)
}
func TestSendSimpleMessageJSONAndQuery(t *testing.T) {
@ -180,18 +181,19 @@ func TestSendSimpleMessageJSONAndQuery(t *testing.T) {
"fcm_token": "DUMMY_FCM",
})
uid := int(r0["user_id"].(float64))
uid := r0["user_id"].(string)
sendtok := r0["send_key"].(string)
msg1 := tt.RequestPost[gin.H](t, baseUrl, fmt.Sprintf("/?user_id=%d&user_key=%s&title=%s", uid, sendtok, url.QueryEscape("1111111")), gin.H{
// query overwrite body
msg1 := tt.RequestPost[gin.H](t, baseUrl, fmt.Sprintf("/?user_id=%s&user_key=%s&title=%s", uid, sendtok, url.QueryEscape("1111111")), gin.H{
"user_key": "ERR",
"user_id": 999999,
"user_id": models.NewUserID(),
"title": "2222222",
})
tt.AssertEqual(t, "messageCount", 1, len(pusher.Data))
tt.AssertStrRepEqual(t, "msg.title", "1111111", pusher.Last().Message.Title)
tt.AssertStrRepEqual(t, "msg.scn_msg_id", msg1["scn_msg_id"], pusher.Last().Message.SCNMessageID)
tt.AssertStrRepEqual(t, "msg.scn_msg_id", msg1["scn_msg_id"], pusher.Last().Message.MessageID)
}
func TestSendSimpleMessageAlt1(t *testing.T) {
@ -207,7 +209,7 @@ func TestSendSimpleMessageAlt1(t *testing.T) {
"fcm_token": "DUMMY_FCM",
})
uid := int(r0["user_id"].(float64))
uid := r0["user_id"].(string)
admintok := r0["admin_key"].(string)
readtok := r0["read_key"].(string)
sendtok := r0["send_key"].(string)
@ -227,7 +229,7 @@ func TestSendSimpleMessageAlt1(t *testing.T) {
tt.AssertEqual(t, "messageCount", 1, len(pusher.Data))
tt.AssertStrRepEqual(t, "msg.title", "HelloWorld_001", pusher.Last().Message.Title)
tt.AssertStrRepEqual(t, "msg.content", nil, pusher.Last().Message.Content)
tt.AssertStrRepEqual(t, "msg.scn_msg_id", msg1["scn_msg_id"], pusher.Last().Message.SCNMessageID)
tt.AssertStrRepEqual(t, "msg.scn_msg_id", msg1["scn_msg_id"], pusher.Last().Message.MessageID)
type mglist struct {
Messages []gin.H `json:"messages"`
@ -254,7 +256,7 @@ func TestSendContentMessage(t *testing.T) {
"fcm_token": "DUMMY_FCM",
})
uid := int(r0["user_id"].(float64))
uid := r0["user_id"].(string)
admintok := r0["admin_key"].(string)
sendtok := r0["send_key"].(string)
@ -268,7 +270,7 @@ func TestSendContentMessage(t *testing.T) {
tt.AssertEqual(t, "messageCount", 1, len(pusher.Data))
tt.AssertStrRepEqual(t, "msg.title", "HelloWorld_042", pusher.Last().Message.Title)
tt.AssertStrRepEqual(t, "msg.content", "I am Content\nasdf", pusher.Last().Message.Content)
tt.AssertStrRepEqual(t, "msg.scn_msg_id", msg1["scn_msg_id"], pusher.Last().Message.SCNMessageID)
tt.AssertStrRepEqual(t, "msg.scn_msg_id", msg1["scn_msg_id"], pusher.Last().Message.MessageID)
type mglist struct {
Messages []gin.H `json:"messages"`
@ -299,7 +301,7 @@ func TestSendWithSendername(t *testing.T) {
"fcm_token": "DUMMY_FCM",
})
uid := int(r0["user_id"].(float64))
uid := r0["user_id"].(string)
sendtok := r0["send_key"].(string)
admintok := r0["admin_key"].(string)
@ -315,7 +317,7 @@ func TestSendWithSendername(t *testing.T) {
tt.AssertStrRepEqual(t, "msg.title", "HelloWorld_xyz", pusher.Last().Message.Title)
tt.AssertStrRepEqual(t, "msg.content", "Unicode: 日本 - yäy\000\n\t\x00...", pusher.Last().Message.Content)
tt.AssertStrRepEqual(t, "msg.SenderName", "localhorst", pusher.Last().Message.SenderName)
tt.AssertStrRepEqual(t, "msg.scn_msg_id", msg1["scn_msg_id"], pusher.Last().Message.SCNMessageID)
tt.AssertStrRepEqual(t, "msg.scn_msg_id", msg1["scn_msg_id"], pusher.Last().Message.MessageID)
type mglist struct {
Messages []gin.H `json:"messages"`
@ -348,7 +350,7 @@ func TestSendLongContent(t *testing.T) {
"fcm_token": "DUMMY_FCM",
})
uid := int(r0["user_id"].(float64))
uid := r0["user_id"].(string)
admintok := r0["admin_key"].(string)
sendtok := r0["send_key"].(string)
@ -367,7 +369,7 @@ func TestSendLongContent(t *testing.T) {
tt.AssertEqual(t, "messageCount", 1, len(pusher.Data))
tt.AssertStrRepEqual(t, "msg.title", "HelloWorld_042", pusher.Last().Message.Title)
tt.AssertStrRepEqual(t, "msg.content", longContent, pusher.Last().Message.Content)
tt.AssertStrRepEqual(t, "msg.scn_msg_id", msg1["scn_msg_id"], pusher.Last().Message.SCNMessageID)
tt.AssertStrRepEqual(t, "msg.scn_msg_id", msg1["scn_msg_id"], pusher.Last().Message.MessageID)
type mglist struct {
Messages []gin.H `json:"messages"`
@ -405,7 +407,7 @@ func TestSendTooLongContent(t *testing.T) {
"fcm_token": "DUMMY_FCM",
})
uid := int(r0["user_id"].(float64))
uid := r0["user_id"].(string)
sendtok := r0["send_key"].(string)
longContent := ""
@ -433,7 +435,7 @@ func TestSendLongContentPro(t *testing.T) {
"pro_token": "ANDROID|v2|PURCHASED:DUMMY_TOK_XX",
})
uid := int(r0["user_id"].(float64))
uid := r0["user_id"].(string)
sendtok := r0["send_key"].(string)
{
@ -519,7 +521,7 @@ func TestSendTooLongTitle(t *testing.T) {
"fcm_token": "DUMMY_FCM",
})
uid := int(r0["user_id"].(float64))
uid := r0["user_id"].(string)
sendtok := r0["send_key"].(string)
tt.RequestPostShouldFail(t, baseUrl, "/", gin.H{
@ -542,7 +544,7 @@ func TestSendIdempotent(t *testing.T) {
"fcm_token": "DUMMY_FCM",
})
uid := int(r0["user_id"].(float64))
uid := r0["user_id"].(string)
readtok := r0["admin_key"].(string)
sendtok := r0["send_key"].(string)
@ -555,7 +557,7 @@ func TestSendIdempotent(t *testing.T) {
})
tt.AssertEqual(t, "messageCount", 1, len(pusher.Data))
tt.AssertStrRepEqual(t, "msg.scn_msg_id", msg1["scn_msg_id"], pusher.Last().Message.SCNMessageID)
tt.AssertStrRepEqual(t, "msg.scn_msg_id", msg1["scn_msg_id"], pusher.Last().Message.MessageID)
tt.AssertStrRepEqual(t, "msg.suppress_send", msg1["suppress_send"], false)
tt.AssertStrRepEqual(t, "msg.msg_id", "c0235a49-dabc-4cdc-a0ce-453966e0c2d5", pusher.Last().Message.UserMessageID)
tt.AssertStrRepEqual(t, "msg.title", "Hello SCN", pusher.Last().Message.Title)
@ -578,7 +580,7 @@ func TestSendIdempotent(t *testing.T) {
tt.AssertEqual(t, "messageCount", 1, len(pusher.Data))
tt.AssertStrRepEqual(t, "msg.scn_msg_id", msg1["scn_msg_id"], msg2["scn_msg_id"])
tt.AssertStrRepEqual(t, "msg.scn_msg_id", msg2["scn_msg_id"], pusher.Last().Message.SCNMessageID)
tt.AssertStrRepEqual(t, "msg.scn_msg_id", msg2["scn_msg_id"], pusher.Last().Message.MessageID)
tt.AssertStrRepEqual(t, "msg.suppress_send", msg2["suppress_send"], true)
tt.AssertStrRepEqual(t, "msg.msg_id", "c0235a49-dabc-4cdc-a0ce-453966e0c2d5", pusher.Last().Message.UserMessageID)
tt.AssertStrRepEqual(t, "msg.title", "Hello SCN", pusher.Last().Message.Title)
@ -620,7 +622,7 @@ func TestSendWithPriority(t *testing.T) {
"fcm_token": "DUMMY_FCM",
})
uid := int(r0["user_id"].(float64))
uid := r0["user_id"].(string)
sendtok := r0["send_key"].(string)
admintok := r0["admin_key"].(string)
@ -713,7 +715,7 @@ func TestSendInvalidPriority(t *testing.T) {
"fcm_token": "DUMMY_FCM",
})
uid := int(r0["user_id"].(float64))
uid := r0["user_id"].(string)
sendtok := r0["send_key"].(string)
admintok := r0["admin_key"].(string)
@ -765,11 +767,9 @@ func TestSendInvalidPriority(t *testing.T) {
"priority": 9999,
}, 400, apierr.INVALID_PRIO)
struid := fmt.Sprintf("%d", uid)
tt.RequestPostShouldFail(t, baseUrl, "/", tt.FormData{
"user_key": sendtok,
"user_id": struid,
"user_id": uid,
"title": "(title)",
"content": "(content)",
"priority": "-1",
@ -777,7 +777,7 @@ func TestSendInvalidPriority(t *testing.T) {
tt.RequestPostShouldFail(t, baseUrl, "/", tt.FormData{
"user_key": sendtok,
"user_id": struid,
"user_id": uid,
"title": "(title)",
"content": "(content)",
"priority": "4",
@ -785,7 +785,7 @@ func TestSendInvalidPriority(t *testing.T) {
tt.RequestPostShouldFail(t, baseUrl, "/", tt.FormData{
"user_key": sendtok,
"user_id": struid,
"user_id": uid,
"title": "(title)",
"content": "(content)",
"priority": "9999",
@ -793,7 +793,7 @@ func TestSendInvalidPriority(t *testing.T) {
tt.RequestPostShouldFail(t, baseUrl, "/", tt.FormData{
"user_key": admintok,
"user_id": struid,
"user_id": uid,
"title": "(title)",
"content": "(content)",
"priority": "-1",
@ -801,7 +801,7 @@ func TestSendInvalidPriority(t *testing.T) {
tt.RequestPostShouldFail(t, baseUrl, "/", tt.FormData{
"user_key": admintok,
"user_id": struid,
"user_id": uid,
"title": "(title)",
"content": "(content)",
"priority": "4",
@ -809,7 +809,7 @@ func TestSendInvalidPriority(t *testing.T) {
tt.RequestPostShouldFail(t, baseUrl, "/", tt.FormData{
"user_key": admintok,
"user_id": struid,
"user_id": uid,
"title": "(title)",
"content": "(content)",
"priority": "9999",
@ -831,7 +831,7 @@ func TestSendWithTimestamp(t *testing.T) {
"fcm_token": "DUMMY_FCM",
})
uid := int(r0["user_id"].(float64))
uid := r0["user_id"].(string)
sendtok := r0["send_key"].(string)
admintok := r0["admin_key"].(string)
@ -839,7 +839,7 @@ func TestSendWithTimestamp(t *testing.T) {
msg1 := tt.RequestPost[gin.H](t, baseUrl, "/", tt.FormData{
"user_key": sendtok,
"user_id": fmt.Sprintf("%d", uid),
"user_id": fmt.Sprintf("%s", uid),
"title": "TTT",
"timestamp": fmt.Sprintf("%d", ts),
})
@ -849,7 +849,7 @@ func TestSendWithTimestamp(t *testing.T) {
tt.AssertStrRepEqual(t, "msg.TimestampClient", ts, pusher.Last().Message.TimestampClient.Unix())
tt.AssertStrRepEqual(t, "msg.Timestamp", ts, pusher.Last().Message.Timestamp().Unix())
tt.AssertNotStrRepEqual(t, "msg.ts", pusher.Last().Message.TimestampClient, pusher.Last().Message.TimestampReal)
tt.AssertStrRepEqual(t, "msg.scn_msg_id", msg1["scn_msg_id"], pusher.Last().Message.SCNMessageID)
tt.AssertStrRepEqual(t, "msg.scn_msg_id", msg1["scn_msg_id"], pusher.Last().Message.MessageID)
type mglist struct {
Messages []gin.H `json:"messages"`
@ -888,33 +888,33 @@ func TestSendInvalidTimestamp(t *testing.T) {
"fcm_token": "DUMMY_FCM",
})
uid := int(r0["user_id"].(float64))
uid := r0["user_id"].(string)
sendtok := r0["send_key"].(string)
tt.RequestPostShouldFail(t, baseUrl, "/", tt.FormData{
"user_key": sendtok,
"user_id": fmt.Sprintf("%d", uid),
"user_id": fmt.Sprintf("%s", uid),
"title": "TTT",
"timestamp": "-10000",
}, 400, apierr.TIMESTAMP_OUT_OF_RANGE)
tt.RequestPostShouldFail(t, baseUrl, "/", tt.FormData{
"user_key": sendtok,
"user_id": fmt.Sprintf("%d", uid),
"user_id": fmt.Sprintf("%s", uid),
"title": "TTT",
"timestamp": "0",
}, 400, apierr.TIMESTAMP_OUT_OF_RANGE)
tt.RequestPostShouldFail(t, baseUrl, "/", tt.FormData{
"user_key": sendtok,
"user_id": fmt.Sprintf("%d", uid),
"user_id": fmt.Sprintf("%s", uid),
"title": "TTT",
"timestamp": fmt.Sprintf("%d", time.Now().Unix()-int64(25*time.Hour.Seconds())),
}, 400, apierr.TIMESTAMP_OUT_OF_RANGE)
tt.RequestPostShouldFail(t, baseUrl, "/", tt.FormData{
"user_key": sendtok,
"user_id": fmt.Sprintf("%d", uid),
"user_id": fmt.Sprintf("%s", uid),
"title": "TTT",
"timestamp": fmt.Sprintf("%d", time.Now().Unix()+int64(25*time.Hour.Seconds())),
}, 400, apierr.TIMESTAMP_OUT_OF_RANGE)
@ -947,28 +947,28 @@ func TestSendInvalidTimestamp(t *testing.T) {
"timestamp": time.Now().Unix() + int64(25*time.Hour.Seconds()),
}, 400, apierr.TIMESTAMP_OUT_OF_RANGE)
tt.RequestPostShouldFail(t, baseUrl, fmt.Sprintf("/?user_key=%s&user_id=%d&title=%s&timestamp=%d",
tt.RequestPostShouldFail(t, baseUrl, fmt.Sprintf("/?user_key=%s&user_id=%s&title=%s&timestamp=%d",
sendtok,
uid,
"TTT",
-10000,
), nil, 400, apierr.TIMESTAMP_OUT_OF_RANGE)
tt.RequestPostShouldFail(t, baseUrl, fmt.Sprintf("/?user_key=%s&user_id=%d&title=%s&timestamp=%d",
tt.RequestPostShouldFail(t, baseUrl, fmt.Sprintf("/?user_key=%s&user_id=%s&title=%s&timestamp=%d",
sendtok,
uid,
"TTT",
0,
), nil, 400, apierr.TIMESTAMP_OUT_OF_RANGE)
tt.RequestPostShouldFail(t, baseUrl, fmt.Sprintf("/?user_key=%s&user_id=%d&title=%s&timestamp=%d",
tt.RequestPostShouldFail(t, baseUrl, fmt.Sprintf("/?user_key=%s&user_id=%s&title=%s&timestamp=%d",
sendtok,
uid,
"TTT",
time.Now().Unix()-int64(25*time.Hour.Seconds()),
), nil, 400, apierr.TIMESTAMP_OUT_OF_RANGE)
tt.RequestPostShouldFail(t, baseUrl, fmt.Sprintf("/?user_key=%s&user_id=%d&title=%s&timestamp=%d",
tt.RequestPostShouldFail(t, baseUrl, fmt.Sprintf("/?user_key=%s&user_id=%s&title=%s&timestamp=%d",
sendtok,
uid,
"TTT",
@ -978,40 +978,34 @@ func TestSendInvalidTimestamp(t *testing.T) {
tt.AssertEqual(t, "messageCount", 0, len(pusher.Data))
}
func TestSendCompat(t *testing.T) {
func TestSendCompatWithOldUser(t *testing.T) {
ws, baseUrl, stop := tt.StartSimpleWebserver(t)
defer stop()
pusher := ws.Pusher.(*push.TestSink)
r0 := tt.RequestPost[gin.H](t, baseUrl, "/api/users", gin.H{
"agent_model": "DUMMY_PHONE",
"agent_version": "4X",
"client_type": "ANDROID",
"fcm_token": "DUMMY_FCM",
})
r0 := tt.RequestGet[gin.H](t, baseUrl, "/api/register.php?fcm_token=DUMMY_FCM&pro=0&pro_token=")
uid := int(r0["user_id"].(float64))
admintok := r0["admin_key"].(string)
readtok := r0["read_key"].(string)
sendtok := r0["send_key"].(string)
uidold := int64(r0["user_id"].(float64))
admintok := r0["user_key"].(string)
msg1 := tt.RequestPost[gin.H](t, baseUrl, "/send.php", tt.FormData{
"user_key": sendtok,
"user_id": fmt.Sprintf("%d", uid),
"user_key": admintok,
"user_id": fmt.Sprintf("%d", uidold),
"title": "HelloWorld_001",
})
// does not allow json - only form & query
tt.RequestPostShouldFail(t, baseUrl, "/send.php", gin.H{
"user_key": readtok,
"user_id": uid,
"user_key": admintok,
"user_id": uidold,
"title": "HelloWorld_001",
}, 0, 0)
}, 400, 0)
tt.AssertEqual(t, "messageCount", 1, len(pusher.Data))
tt.AssertStrRepEqual(t, "msg.title", "HelloWorld_001", pusher.Last().Message.Title)
tt.AssertStrRepEqual(t, "msg.content", nil, pusher.Last().Message.Content)
tt.AssertStrRepEqual(t, "msg.scn_msg_id", msg1["scn_msg_id"], pusher.Last().Message.SCNMessageID)
tt.AssertStrRepEqual(t, "msg.scn_msg_id", msg1["scn_msg_id"], pusher.Last().Message.MessageID)
type mglist struct {
Messages []gin.H `json:"messages"`
@ -1024,12 +1018,103 @@ func TestSendCompat(t *testing.T) {
tt.AssertStrRepEqual(t, "msg.title", "HelloWorld_001", msg1Get["title"])
tt.AssertStrRepEqual(t, "msg.channel_internal_name", "main", msg1Get["channel_internal_name"])
msg2 := tt.RequestPost[gin.H](t, baseUrl, fmt.Sprintf("/send.php?user_key=%s&user_id=%d&title=%s", sendtok, uid, "HelloWorld_002"), nil)
msg2 := tt.RequestPost[gin.H](t, baseUrl, fmt.Sprintf("/send.php?user_key=%s&user_id=%d&title=%s", admintok, uidold, "HelloWorld_002"), nil)
tt.AssertEqual(t, "messageCount", 2, len(pusher.Data))
tt.AssertStrRepEqual(t, "msg.title", "HelloWorld_002", pusher.Last().Message.Title)
tt.AssertStrRepEqual(t, "msg.content", nil, pusher.Last().Message.Content)
tt.AssertStrRepEqual(t, "msg.scn_msg_id", msg2["scn_msg_id"], pusher.Last().Message.SCNMessageID)
tt.AssertStrRepEqual(t, "msg.scn_msg_id", msg2["scn_msg_id"], pusher.Last().Message.MessageID)
tt.RequestAuthGet[gin.H](t, admintok, baseUrl, "/api/messages/"+fmt.Sprintf("%v", msg2["scn_msg_id"]))
content3 := "039c1817-76ee-44ab-972a-4cec0a15a791\n" +
"046f59ea-9a49-4060-93e6-8a4e14134faf\n" +
"ab566fbe-9020-41b6-afa6-94f3d8d7c7b4\n" +
"d52e5f7d-26a8-45b9-befc-da44a3f112da\n" +
"d19fae55-d52a-4753-b9f1-66a935d68b1e\n" +
"99a4099d-44d5-497a-a69b-18e277400d6e\n" +
"a55757aa-afaa-420e-afaf-f3951e9e2434\n" +
"ee58f5fc-b384-49f4-bc2c-c5b3c7bd54b7\n" +
"5a7008d9-dd15-406a-83d1-fd6209c56141\n"
ts3 := time.Now().Unix() - int64(time.Hour.Seconds())
msg3 := tt.RequestPost[gin.H](t, baseUrl, "/send.php", tt.FormData{
"user_key": admintok,
"user_id": fmt.Sprintf("%d", uidold),
"title": "HelloWorld_003",
"content": content3,
"priority": "2",
"msg_id": "8a2c7e92-86f3-4d69-897a-571286954030",
"timestamp": fmt.Sprintf("%d", ts3),
})
tt.RequestAuthGet[gin.H](t, admintok, baseUrl, "/api/messages/"+fmt.Sprintf("%v", msg3["scn_msg_id"]))
tt.AssertEqual(t, "messageCount", 3, len(pusher.Data))
tt.AssertStrRepEqual(t, "msg.Title", "HelloWorld_003", pusher.Last().Message.Title)
tt.AssertStrRepEqual(t, "msg.Content", content3, pusher.Last().Message.Content)
tt.AssertStrRepEqual(t, "msg.MessageID", msg3["scn_msg_id"], pusher.Last().Message.MessageID)
tt.AssertStrRepEqual(t, "msg.Priority", 2, pusher.Last().Message.Priority)
tt.AssertStrRepEqual(t, "msg.UserMessageID", "8a2c7e92-86f3-4d69-897a-571286954030", pusher.Last().Message.UserMessageID)
tt.AssertStrRepEqual(t, "msg.UserMessageID", ts3, pusher.Last().Message.Timestamp().Unix())
}
func TestSendCompatWithNewUser(t *testing.T) {
ws, baseUrl, stop := tt.StartSimpleWebserver(t)
defer stop()
pusher := ws.Pusher.(*push.TestSink)
r0 := tt.RequestPost[gin.H](t, baseUrl, "/api/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)
readtok := r0["read_key"].(string)
sendtok := r0["send_key"].(string)
uidold := tt.CreateCompatID(t, ws, "userid", uid)
msg1 := tt.RequestPost[gin.H](t, baseUrl, "/send.php", tt.FormData{
"user_key": sendtok,
"user_id": fmt.Sprintf("%d", uidold),
"title": "HelloWorld_001",
})
// does not allow json - only form & query
tt.RequestPostShouldFail(t, baseUrl, "/send.php", gin.H{
"user_key": readtok,
"user_id": uidold,
"title": "HelloWorld_001",
}, 400, 0)
tt.AssertEqual(t, "messageCount", 1, len(pusher.Data))
tt.AssertStrRepEqual(t, "msg.title", "HelloWorld_001", pusher.Last().Message.Title)
tt.AssertStrRepEqual(t, "msg.content", nil, pusher.Last().Message.Content)
tt.AssertStrRepEqual(t, "msg.scn_msg_id", msg1["scn_msg_id"], pusher.Last().Message.MessageID)
type mglist struct {
Messages []gin.H `json:"messages"`
}
msgList1 := tt.RequestAuthGet[mglist](t, admintok, baseUrl, "/api/messages")
tt.AssertEqual(t, "len(messages)", 1, len(msgList1.Messages))
msg1Get := tt.RequestAuthGet[gin.H](t, admintok, baseUrl, "/api/messages/"+fmt.Sprintf("%v", msg1["scn_msg_id"]))
tt.AssertStrRepEqual(t, "msg.title", "HelloWorld_001", msg1Get["title"])
tt.AssertStrRepEqual(t, "msg.channel_internal_name", "main", msg1Get["channel_internal_name"])
msg2 := tt.RequestPost[gin.H](t, baseUrl, fmt.Sprintf("/send.php?user_key=%s&user_id=%d&title=%s", sendtok, uidold, "HelloWorld_002"), nil)
tt.AssertEqual(t, "messageCount", 2, len(pusher.Data))
tt.AssertStrRepEqual(t, "msg.title", "HelloWorld_002", pusher.Last().Message.Title)
tt.AssertStrRepEqual(t, "msg.content", nil, pusher.Last().Message.Content)
tt.AssertStrRepEqual(t, "msg.scn_msg_id", msg2["scn_msg_id"], pusher.Last().Message.MessageID)
tt.RequestAuthGet[gin.H](t, admintok, baseUrl, "/api/messages/"+fmt.Sprintf("%v", msg2["scn_msg_id"]))
@ -1046,7 +1131,7 @@ func TestSendCompat(t *testing.T) {
msg3 := tt.RequestPost[gin.H](t, baseUrl, "/send.php", tt.FormData{
"user_key": sendtok,
"user_id": fmt.Sprintf("%d", uid),
"user_id": fmt.Sprintf("%d", uidold),
"title": "HelloWorld_003",
"content": content3,
"priority": "2",
@ -1059,7 +1144,7 @@ func TestSendCompat(t *testing.T) {
tt.AssertEqual(t, "messageCount", 3, len(pusher.Data))
tt.AssertStrRepEqual(t, "msg.Title", "HelloWorld_003", pusher.Last().Message.Title)
tt.AssertStrRepEqual(t, "msg.Content", content3, pusher.Last().Message.Content)
tt.AssertStrRepEqual(t, "msg.SCNMessageID", msg3["scn_msg_id"], pusher.Last().Message.SCNMessageID)
tt.AssertStrRepEqual(t, "msg.MessageID", msg3["scn_msg_id"], pusher.Last().Message.MessageID)
tt.AssertStrRepEqual(t, "msg.Priority", 2, pusher.Last().Message.Priority)
tt.AssertStrRepEqual(t, "msg.UserMessageID", "8a2c7e92-86f3-4d69-897a-571286954030", pusher.Last().Message.UserMessageID)
tt.AssertStrRepEqual(t, "msg.UserMessageID", ts3, pusher.Last().Message.Timestamp().Unix())
@ -1077,7 +1162,7 @@ func TestSendToNewChannel(t *testing.T) {
"fcm_token": "DUMMY_FCM",
})
uid := int(r0["user_id"].(float64))
uid := r0["user_id"].(string)
sendtok := r0["send_key"].(string)
admintok := r0["admin_key"].(string)
@ -1086,7 +1171,7 @@ func TestSendToNewChannel(t *testing.T) {
}
{
chan0 := tt.RequestAuthGet[chanlist](t, admintok, baseUrl, fmt.Sprintf("/api/users/%d/channels", uid))
chan0 := tt.RequestAuthGet[chanlist](t, admintok, baseUrl, fmt.Sprintf("/api/users/%s/channels", uid))
tt.AssertEqual(t, "chan-count", 0, len(chan0.Channels))
}
@ -1097,7 +1182,7 @@ func TestSendToNewChannel(t *testing.T) {
})
{
clist := tt.RequestAuthGet[chanlist](t, admintok, baseUrl, fmt.Sprintf("/api/users/%d/channels", uid))
clist := tt.RequestAuthGet[chanlist](t, admintok, baseUrl, fmt.Sprintf("/api/users/%s/channels", uid))
tt.AssertMappedSet(t, "channels", []string{"main"}, clist.Channels, "display_name")
tt.AssertMappedSet(t, "channels", []string{"main"}, clist.Channels, "internal_name")
}
@ -1111,7 +1196,7 @@ func TestSendToNewChannel(t *testing.T) {
})
{
clist := tt.RequestAuthGet[chanlist](t, admintok, baseUrl, fmt.Sprintf("/api/users/%d/channels", uid))
clist := tt.RequestAuthGet[chanlist](t, admintok, baseUrl, fmt.Sprintf("/api/users/%s/channels", uid))
tt.AssertMappedSet(t, "channels", []string{"main"}, clist.Channels, "display_name")
tt.AssertMappedSet(t, "channels", []string{"main"}, clist.Channels, "internal_name")
}
@ -1125,7 +1210,7 @@ func TestSendToNewChannel(t *testing.T) {
})
{
clist := tt.RequestAuthGet[chanlist](t, admintok, baseUrl, fmt.Sprintf("/api/users/%d/channels", uid))
clist := tt.RequestAuthGet[chanlist](t, admintok, baseUrl, fmt.Sprintf("/api/users/%s/channels", uid))
tt.AssertMappedSet(t, "channels", []string{"main", "test"}, clist.Channels, "display_name")
tt.AssertMappedSet(t, "channels", []string{"main", "test"}, clist.Channels, "internal_name")
}
@ -1138,7 +1223,7 @@ func TestSendToNewChannel(t *testing.T) {
})
{
clist := tt.RequestAuthGet[chanlist](t, admintok, baseUrl, fmt.Sprintf("/api/users/%d/channels", uid))
clist := tt.RequestAuthGet[chanlist](t, admintok, baseUrl, fmt.Sprintf("/api/users/%s/channels", uid))
tt.AssertMappedSet(t, "channels", []string{"main", "test"}, clist.Channels, "display_name")
tt.AssertMappedSet(t, "channels", []string{"main", "test"}, clist.Channels, "internal_name")
}
@ -1155,7 +1240,7 @@ func TestSendToManualChannel(t *testing.T) {
"fcm_token": "DUMMY_FCM",
})
uid := int(r0["user_id"].(float64))
uid := r0["user_id"].(string)
sendtok := r0["send_key"].(string)
admintok := r0["admin_key"].(string)
@ -1164,7 +1249,7 @@ func TestSendToManualChannel(t *testing.T) {
}
{
clist := tt.RequestAuthGet[chanlist](t, admintok, baseUrl, fmt.Sprintf("/api/users/%d/channels", uid))
clist := tt.RequestAuthGet[chanlist](t, admintok, baseUrl, fmt.Sprintf("/api/users/%s/channels", uid))
tt.AssertMappedSet(t, "channels", []string{}, clist.Channels, "display_name")
tt.AssertMappedSet(t, "channels", []string{}, clist.Channels, "internal_name")
}
@ -1176,7 +1261,7 @@ func TestSendToManualChannel(t *testing.T) {
})
{
clist := tt.RequestAuthGet[chanlist](t, admintok, baseUrl, fmt.Sprintf("/api/users/%d/channels", uid))
clist := tt.RequestAuthGet[chanlist](t, admintok, baseUrl, fmt.Sprintf("/api/users/%s/channels", uid))
tt.AssertMappedSet(t, "channels", []string{"main"}, clist.Channels, "display_name")
tt.AssertMappedSet(t, "channels", []string{"main"}, clist.Channels, "internal_name")
}
@ -1190,18 +1275,18 @@ func TestSendToManualChannel(t *testing.T) {
})
{
clist := tt.RequestAuthGet[chanlist](t, admintok, baseUrl, fmt.Sprintf("/api/users/%d/channels", uid))
clist := tt.RequestAuthGet[chanlist](t, admintok, baseUrl, fmt.Sprintf("/api/users/%s/channels", uid))
tt.AssertEqual(t, "chan.len", 1, len(clist.Channels))
tt.AssertEqual(t, "chan.internal_name", "main", clist.Channels[0]["internal_name"])
tt.AssertEqual(t, "chan.display_name", "main", clist.Channels[0]["display_name"])
}
tt.RequestAuthPost[gin.H](t, admintok, baseUrl, fmt.Sprintf("/api/users/%d/channels", uid), gin.H{
tt.RequestAuthPost[gin.H](t, admintok, baseUrl, fmt.Sprintf("/api/users/%s/channels", uid), gin.H{
"name": "test",
})
{
clist := tt.RequestAuthGet[chanlist](t, admintok, baseUrl, fmt.Sprintf("/api/users/%d/channels", uid))
clist := tt.RequestAuthGet[chanlist](t, admintok, baseUrl, fmt.Sprintf("/api/users/%s/channels", uid))
tt.AssertMappedSet(t, "channels", []string{"main", "test"}, clist.Channels, "display_name")
tt.AssertMappedSet(t, "channels", []string{"main", "test"}, clist.Channels, "internal_name")
}
@ -1215,7 +1300,7 @@ func TestSendToManualChannel(t *testing.T) {
})
{
clist := tt.RequestAuthGet[chanlist](t, admintok, baseUrl, fmt.Sprintf("/api/users/%d/channels", uid))
clist := tt.RequestAuthGet[chanlist](t, admintok, baseUrl, fmt.Sprintf("/api/users/%s/channels", uid))
tt.AssertMappedSet(t, "channels", []string{"main", "test"}, clist.Channels, "display_name")
tt.AssertMappedSet(t, "channels", []string{"main", "test"}, clist.Channels, "internal_name")
}
@ -1228,7 +1313,7 @@ func TestSendToManualChannel(t *testing.T) {
})
{
clist := tt.RequestAuthGet[chanlist](t, admintok, baseUrl, fmt.Sprintf("/api/users/%d/channels", uid))
clist := tt.RequestAuthGet[chanlist](t, admintok, baseUrl, fmt.Sprintf("/api/users/%s/channels", uid))
tt.AssertMappedSet(t, "channels", []string{"main", "test"}, clist.Channels, "display_name")
tt.AssertMappedSet(t, "channels", []string{"main", "test"}, clist.Channels, "internal_name")
}
@ -1245,7 +1330,7 @@ func TestSendToTooLongChannel(t *testing.T) {
"fcm_token": "DUMMY_FCM",
})
uid := int(r0["user_id"].(float64))
uid := r0["user_id"].(string)
sendtok := r0["send_key"].(string)
tt.RequestPost[tt.Void](t, baseUrl, "/", gin.H{
@ -1281,7 +1366,7 @@ func TestQuotaExceededNoPro(t *testing.T) {
"fcm_token": "DUMMY_FCM",
})
uid := int(r0["user_id"].(float64))
uid := r0["user_id"].(string)
admintok := r0["admin_key"].(string)
sendtok := r0["send_key"].(string)
@ -1300,7 +1385,7 @@ func TestQuotaExceededNoPro(t *testing.T) {
}
{
usr := tt.RequestAuthGet[gin.H](t, admintok, baseUrl, fmt.Sprintf("/api/users/%d", uid))
usr := tt.RequestAuthGet[gin.H](t, admintok, baseUrl, fmt.Sprintf("/api/users/%s", uid))
tt.AssertStrRepEqual(t, "quota.1", 1, usr["quota_used"])
tt.AssertStrRepEqual(t, "quota.1", 50, usr["quota_max"])
@ -1317,7 +1402,7 @@ func TestQuotaExceededNoPro(t *testing.T) {
}
{
usr := tt.RequestAuthGet[gin.H](t, admintok, baseUrl, fmt.Sprintf("/api/users/%d", uid))
usr := tt.RequestAuthGet[gin.H](t, admintok, baseUrl, fmt.Sprintf("/api/users/%s", uid))
tt.AssertStrRepEqual(t, "quota.49", 49, usr["quota_used"])
tt.AssertStrRepEqual(t, "quota.49", 50, usr["quota_max"])
@ -1333,7 +1418,7 @@ func TestQuotaExceededNoPro(t *testing.T) {
tt.AssertStrRepEqual(t, "quota.msg.50", 50, msg50["quota_max"])
{
usr := tt.RequestAuthGet[gin.H](t, admintok, baseUrl, fmt.Sprintf("/api/users/%d", uid))
usr := tt.RequestAuthGet[gin.H](t, admintok, baseUrl, fmt.Sprintf("/api/users/%s", uid))
tt.AssertStrRepEqual(t, "quota.50", 50, usr["quota_used"])
tt.AssertStrRepEqual(t, "quota.50", 50, usr["quota_max"])
@ -1359,7 +1444,7 @@ func TestQuotaExceededPro(t *testing.T) {
"pro_token": "ANDROID|v2|PURCHASED:DUMMY_TOK_XX",
})
uid := int(r0["user_id"].(float64))
uid := r0["user_id"].(string)
admintok := r0["admin_key"].(string)
sendtok := r0["send_key"].(string)
@ -1378,7 +1463,7 @@ func TestQuotaExceededPro(t *testing.T) {
}
{
usr := tt.RequestAuthGet[gin.H](t, admintok, baseUrl, fmt.Sprintf("/api/users/%d", uid))
usr := tt.RequestAuthGet[gin.H](t, admintok, baseUrl, fmt.Sprintf("/api/users/%s", uid))
tt.AssertStrRepEqual(t, "quota.1", 1, usr["quota_used"])
tt.AssertStrRepEqual(t, "quota.1", 1000, usr["quota_max"])
@ -1395,7 +1480,7 @@ func TestQuotaExceededPro(t *testing.T) {
}
{
usr := tt.RequestAuthGet[gin.H](t, admintok, baseUrl, fmt.Sprintf("/api/users/%d", uid))
usr := tt.RequestAuthGet[gin.H](t, admintok, baseUrl, fmt.Sprintf("/api/users/%s", uid))
tt.AssertStrRepEqual(t, "quota.999", 999, usr["quota_used"])
tt.AssertStrRepEqual(t, "quota.999", 1000, usr["quota_max"])
@ -1411,7 +1496,7 @@ func TestQuotaExceededPro(t *testing.T) {
tt.AssertStrRepEqual(t, "quota.msg.1000", 1000, msg50["quota_max"])
{
usr := tt.RequestAuthGet[gin.H](t, admintok, baseUrl, fmt.Sprintf("/api/users/%d", uid))
usr := tt.RequestAuthGet[gin.H](t, admintok, baseUrl, fmt.Sprintf("/api/users/%s", uid))
tt.AssertStrRepEqual(t, "quota.1000", 1000, usr["quota_used"])
tt.AssertStrRepEqual(t, "quota.1000", 1000, usr["quota_max"])
@ -1437,7 +1522,7 @@ func TestSendParallel(t *testing.T) {
"pro_token": "ANDROID|v2|PURCHASED:DUMMY_TOK_XX",
})
uid := int(r0["user_id"].(float64))
uid := r0["user_id"].(string)
sendtok := r0["send_key"].(string)
count := 128

View File

@ -59,7 +59,7 @@ type clientex struct {
}
type Userdat struct {
UID int64
UID string
SendKey string
AdminKey string
ReadKey string
@ -319,7 +319,7 @@ func InitDefaultData(t *testing.T, ws *logic.Application) DefData {
}
user0 := RequestPost[gin.H](t, baseUrl, "/api/users", body)
uid0 := int64(user0["user_id"].(float64))
uid0 := user0["user_id"].(string)
readtok0 := user0["read_key"].(string)
sendtok0 := user0["send_key"].(string)
admintok0 := user0["admin_key"].(string)
@ -341,7 +341,7 @@ func InitDefaultData(t *testing.T, ws *logic.Application) DefData {
body["agent_version"] = cex.AgentVersion
body["client_type"] = cex.ClientType
body["fcm_token"] = cex.FCMTok
RequestAuthPost[gin.H](t, users[cex.User].AdminKey, baseUrl, fmt.Sprintf("/api/users/%d/clients", users[cex.User].UID), body)
RequestAuthPost[gin.H](t, users[cex.User].AdminKey, baseUrl, fmt.Sprintf("/api/users/%s/clients", users[cex.User].UID), body)
}
// Create Messages
@ -378,7 +378,7 @@ func InitDefaultData(t *testing.T, ws *logic.Application) DefData {
// create manual channels
{
RequestAuthPost[Void](t, users[9].AdminKey, baseUrl, fmt.Sprintf("/api/users/%d/channels", users[9].UID), gin.H{"name": "manual@chan"})
RequestAuthPost[Void](t, users[9].AdminKey, baseUrl, fmt.Sprintf("/api/users/%s/channels", users[9].UID), gin.H{"name": "manual@chan"})
}
// Sub/Unsub for Users 12+13
@ -399,7 +399,7 @@ func doSubscribe(t *testing.T, baseUrl string, user Userdat, chanOwner Userdat,
if user == chanOwner {
RequestAuthPost[Void](t, user.AdminKey, baseUrl, fmt.Sprintf("/api/users/%d/channels", user.UID), gin.H{
RequestAuthPost[Void](t, user.AdminKey, baseUrl, fmt.Sprintf("/api/users/%s/channels", user.UID), gin.H{
"channel_owner_user_id": chanOwner.UID,
"channel_internal_name": chanInternalName,
})
@ -409,7 +409,7 @@ func doSubscribe(t *testing.T, baseUrl string, user Userdat, chanOwner Userdat,
Channels []gin.H `json:"channels"`
}
clist := RequestAuthGet[chanlist](t, chanOwner.AdminKey, baseUrl, fmt.Sprintf("/api/users/%d/channels?selector=owned", chanOwner.UID))
clist := RequestAuthGet[chanlist](t, chanOwner.AdminKey, baseUrl, fmt.Sprintf("/api/users/%s/channels?selector=owned", chanOwner.UID))
var chandat gin.H
for _, v := range clist.Channels {
@ -419,8 +419,8 @@ func doSubscribe(t *testing.T, baseUrl string, user Userdat, chanOwner Userdat,
}
}
RequestAuthPost[Void](t, user.AdminKey, baseUrl, fmt.Sprintf("/api/users/%d/subscriptions?chan_subscribe_key=%s", user.UID, chandat["subscribe_key"].(string)), gin.H{
"channel_id": chandat["channel_id"].(float64),
RequestAuthPost[Void](t, user.AdminKey, baseUrl, fmt.Sprintf("/api/users/%s/subscriptions?chan_subscribe_key=%s", user.UID, chandat["subscribe_key"].(string)), gin.H{
"channel_id": chandat["channel_id"].(string),
})
}
@ -433,17 +433,17 @@ func doUnsubscribe(t *testing.T, baseUrl string, user Userdat, chanOwner Userdat
Subscriptions []gin.H `json:"subscriptions"`
}
slist := RequestAuthGet[chanlist](t, user.AdminKey, baseUrl, fmt.Sprintf("/api/users/%d/subscriptions?selector=outgoing_confirmed", user.UID))
slist := RequestAuthGet[chanlist](t, user.AdminKey, baseUrl, fmt.Sprintf("/api/users/%s/subscriptions?selector=outgoing_confirmed", user.UID))
var subdat gin.H
for _, v := range slist.Subscriptions {
if v["channel_internal_name"].(string) == chanInternalName && int64(v["channel_owner_user_id"].(float64)) == chanOwner.UID {
if v["channel_internal_name"].(string) == chanInternalName && v["channel_owner_user_id"].(string) == chanOwner.UID {
subdat = v
break
}
}
RequestAuthDelete[Void](t, user.AdminKey, baseUrl, fmt.Sprintf("/api/users/%d/subscriptions/%v", user.UID, subdat["subscription_id"]), gin.H{})
RequestAuthDelete[Void](t, user.AdminKey, baseUrl, fmt.Sprintf("/api/users/%s/subscriptions/%v", user.UID, subdat["subscription_id"]), gin.H{})
}
@ -453,17 +453,17 @@ func doAcceptSub(t *testing.T, baseUrl string, user Userdat, subscriber Userdat,
Subscriptions []gin.H `json:"subscriptions"`
}
slist := RequestAuthGet[chanlist](t, user.AdminKey, baseUrl, fmt.Sprintf("/api/users/%d/subscriptions?selector=incoming_unconfirmed", user.UID))
slist := RequestAuthGet[chanlist](t, user.AdminKey, baseUrl, fmt.Sprintf("/api/users/%s/subscriptions?selector=incoming_unconfirmed", user.UID))
var subdat gin.H
for _, v := range slist.Subscriptions {
if v["channel_internal_name"].(string) == chanInternalName && int64(v["subscriber_user_id"].(float64)) == subscriber.UID {
if v["channel_internal_name"].(string) == chanInternalName && v["subscriber_user_id"].(string) == subscriber.UID {
subdat = v
break
}
}
RequestAuthPatch[Void](t, user.AdminKey, baseUrl, fmt.Sprintf("/api/users/%d/subscriptions/%v", user.UID, subdat["subscription_id"]), gin.H{
RequestAuthPatch[Void](t, user.AdminKey, baseUrl, fmt.Sprintf("/api/users/%s/subscriptions/%v", user.UID, subdat["subscription_id"]), gin.H{
"confirmed": true,
})

View File

@ -0,0 +1,45 @@
package util
import (
"blackforestbytes.com/simplecloudnotifier/logic"
"testing"
"time"
)
func ConvertToCompatID(t *testing.T, ws *logic.Application, newid string) int64 {
ctx := ws.NewSimpleTransactionContext(5 * time.Second)
defer ctx.Cancel()
uidold, _, err := ws.Database.Primary.ConvertToCompatID(ctx, newid)
TestFailIfErr(t, err)
if uidold == nil {
TestFail(t, "faile to convert newid to oldid (compat)")
}
err = ctx.CommitTransaction()
if err != nil {
TestFail(t, "failed to commit")
return 0
}
return *uidold
}
func CreateCompatID(t *testing.T, ws *logic.Application, idtype string, newid string) int64 {
ctx := ws.NewSimpleTransactionContext(5 * time.Second)
defer ctx.Cancel()
uidold, err := ws.Database.Primary.CreateCompatID(ctx, idtype, newid)
TestFailIfErr(t, err)
err = ctx.CommitTransaction()
if err != nil {
TestFail(t, "failed to commit")
return 0
}
return uidold
}

View File

@ -124,14 +124,17 @@ func StartSimpleWebserver(t *testing.T) (*logic.Application, string, func()) {
jobs.NewRequestLogCollectorJob(app),
})
router.Init(ginengine)
err = router.Init(ginengine)
if err != nil {
panic(err)
}
stop := func() {
app.Stop()
_ = app.IsRunning.WaitWithTimeout(5*time.Second, false)
_ = os.Remove(dbfile1)
_ = os.Remove(dbfile2)
_ = os.Remove(dbfile3)
_ = app.IsRunning.WaitWithTimeout(400*time.Millisecond, false)
}
go func() { app.Run() }()