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="processLiterals" value="true" />
<option name="processComments" value="true" /> <option name="processComments" value="true" />
</inspection_tool> </inspection_tool>
<inspection_tool class="SqlRedundantOrderingDirectionInspection" enabled="false" level="WARNING" enabled_by_default="false" />
</profile> </profile>
</component> </component>

View File

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

View File

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

View File

@ -1,6 +1,8 @@
package handler package handler
import ( import (
"blackforestbytes.com/simplecloudnotifier/api/apierr"
hl "blackforestbytes.com/simplecloudnotifier/api/apihighlight"
"blackforestbytes.com/simplecloudnotifier/api/ginresp" "blackforestbytes.com/simplecloudnotifier/api/ginresp"
primarydb "blackforestbytes.com/simplecloudnotifier/db/impl/primary" primarydb "blackforestbytes.com/simplecloudnotifier/db/impl/primary"
"blackforestbytes.com/simplecloudnotifier/logic" "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 // Register swaggerdoc
// //
// @Summary Register a new account // @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") 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{ return ctx.FinishSuccess(ginresp.JSON(http.StatusOK, response{
Success: true, Success: true,
Message: "New user registered", Message: "New user registered",
UserID: user.UserID.IntID(), UserID: oldid,
UserKey: user.AdminKey, UserKey: user.AdminKey,
QuotaUsed: user.QuotaUsedToday(), QuotaUsed: user.QuotaUsedToday(),
QuotaMax: user.QuotaPerDay(), QuotaMax: user.QuotaPerDay(),
@ -184,7 +241,15 @@ func (h CompatHandler) Info(g *gin.Context) ginresp.HTTPResponse {
return ginresp.CompatAPIError(102, "Missing parameter [[user_key]]") 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 { if err == sql.ErrNoRows {
return ginresp.CompatAPIError(201, "User not found") 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{ return ctx.FinishSuccess(ginresp.JSON(http.StatusOK, response{
Success: true, Success: true,
Message: "ok", Message: "ok",
UserID: user.UserID.IntID(), UserID: *data.UserID,
UserKey: user.AdminKey, UserKey: user.AdminKey,
QuotaUsed: user.QuotaUsedToday(), QuotaUsed: user.QuotaUsedToday(),
QuotaMax: user.QuotaPerDay(), 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]]") 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 { if err == sql.ErrNoRows {
return ginresp.CompatAPIError(201, "User not found") 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]]") 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 { if err == sql.ErrNoRows {
return ginresp.CompatAPIError(201, "User not found") 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]]") 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 { if err == sql.ErrNoRows {
return ginresp.CompatAPIError(201, "User not found") 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{ return ctx.FinishSuccess(ginresp.JSON(http.StatusOK, response{
Success: true, Success: true,
Message: "user updated", Message: "user updated",
UserID: user.UserID.IntID(), UserID: *data.UserID,
UserKey: user.AdminKey, UserKey: user.AdminKey,
QuotaUsed: user.QuotaUsedToday(), QuotaUsed: user.QuotaUsedToday(),
QuotaMax: user.QuotaPerDay(), 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]]") 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 { if err == sql.ErrNoRows {
return ginresp.CompatAPIError(201, "User not found") 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") 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 { if err == sql.ErrNoRows {
return ginresp.CompatAPIError(301, "Message not found") return ginresp.CompatAPIError(301, "Message not found")
} }
@ -551,7 +656,7 @@ func (h CompatHandler) Expand(g *gin.Context) ginresp.HTTPResponse {
Priority: msg.Priority, Priority: msg.Priority,
Timestamp: msg.Timestamp().Unix(), Timestamp: msg.Timestamp().Unix(),
UserMessageID: msg.UserMessageID, 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]]") 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 { if err == sql.ErrNoRows {
return ginresp.CompatAPIError(201, "User not found") 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{ return ctx.FinishSuccess(ginresp.JSON(http.StatusOK, response{
Success: true, Success: true,
Message: "user updated", Message: "user updated",
UserID: user.UserID.IntID(), UserID: *data.UserID,
QuotaUsed: user.QuotaUsedToday(), QuotaUsed: user.QuotaUsedToday(),
QuotaMax: user.QuotaPerDay(), QuotaMax: user.QuotaPerDay(),
IsPro: user.IsPro, 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 // SendMessage swaggerdoc
// //
// @Summary Send a new message // @Summary Send a new message
@ -132,7 +90,7 @@ func (h MessageHandler) sendMessageInternal(g *gin.Context, ctx *logic.AppContex
Quota int `json:"quota"` Quota int `json:"quota"`
IsPro bool `json:"is_pro"` IsPro bool `json:"is_pro"`
QuotaMax int `json:"quota_max"` QuotaMax int `json:"quota_max"`
SCNMessageID models.SCNMessageID `json:"scn_msg_id"` SCNMessageID models.MessageID `json:"scn_msg_id"`
} }
if Title != nil { if Title != nil {
@ -212,7 +170,7 @@ func (h MessageHandler) sendMessageInternal(g *gin.Context, ctx *logic.AppContex
Quota: user.QuotaUsedToday(), Quota: user.QuotaUsedToday(),
IsPro: user.IsPro, IsPro: user.IsPro,
QuotaMax: user.QuotaPerDay(), 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, Quota: user.QuotaUsedToday() + 1,
IsPro: user.IsPro, IsPro: user.IsPro,
QuotaMax: user.QuotaPerDay(), 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/ginresp"
"blackforestbytes.com/simplecloudnotifier/api/handler" "blackforestbytes.com/simplecloudnotifier/api/handler"
"blackforestbytes.com/simplecloudnotifier/logic" "blackforestbytes.com/simplecloudnotifier/logic"
"blackforestbytes.com/simplecloudnotifier/models"
"blackforestbytes.com/simplecloudnotifier/swagger" "blackforestbytes.com/simplecloudnotifier/swagger"
"errors"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
"github.com/go-playground/validator/v10"
) )
type Router struct { type Router struct {
@ -44,7 +48,16 @@ func NewRouter(app *logic.Application) *Router {
// @tag.name Common // @tag.name Common
// //
// @BasePath / // @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 ================ // ================ General ================
@ -94,7 +107,7 @@ func (r *Router) Init(e *gin.Engine) {
// ================ Compat (v1) ================ // ================ Compat (v1) ================
compat := e.Group("/api/") compat := e.Group("/api")
{ {
compat.GET("/register.php", r.Wrap(r.compatHandler.Register)) compat.GET("/register.php", r.Wrap(r.compatHandler.Register))
compat.GET("/info.php", r.Wrap(r.compatHandler.Info)) 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)) e.NoRoute(r.Wrap(r.commonHandler.NoRoute))
} }
// ================
return nil
} }
func (r *Router) Wrap(fn ginresp.WHandlerFunc) gin.HandlerFunc { 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}) 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() app.Run()
} }

View File

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

View File

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

View File

@ -24,7 +24,7 @@ type Database struct {
func NewLogsDatabase(cfg server.Config) (*Database, error) { func NewLogsDatabase(cfg server.Config) (*Database, error) {
conf := cfg.DBLogs 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) xdb, err := sqlx.Open("sqlite3", url)
if err != nil { if err != nil {

View File

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

View File

@ -89,7 +89,10 @@ func (db *Database) CreateChannel(ctx TxContext, userid models.UserID, dispName
now := time.Now().UTC() 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, "ouid": userid,
"dnam": dispName, "dnam": dispName,
"inam": intName, "inam": intName,
@ -101,13 +104,8 @@ func (db *Database) CreateChannel(ctx TxContext, userid models.UserID, dispName
return models.Channel{}, err return models.Channel{}, err
} }
liid, err := res.LastInsertId()
if err != nil {
return models.Channel{}, err
}
return models.Channel{ return models.Channel{
ChannelID: models.ChannelID(liid), ChannelID: channelid,
OwnerUserID: userid, OwnerUserID: userid,
DisplayName: dispName, DisplayName: dispName,
InternalName: intName, InternalName: intName,
@ -125,7 +123,9 @@ func (db *Database) ListChannelsByOwner(ctx TxContext, userid models.UserID, sub
return nil, err 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, "ouid": userid,
"subuid": subUserID, "subuid": subUserID,
}) })
@ -154,7 +154,9 @@ func (db *Database) ListChannelsBySubscriber(ctx TxContext, userid models.UserID
confCond = " AND sub.confirmed = 0" 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, "subuid": userid,
}) })
if err != nil { 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)" 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, "ouid": userid,
"subuid": userid, "subuid": userid,
}) })

View File

@ -15,7 +15,10 @@ func (db *Database) CreateClient(ctx TxContext, userid models.UserID, ctype mode
now := time.Now().UTC() 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, "uid": userid,
"typ": string(ctype), "typ": string(ctype),
"fcm": fcmToken, "fcm": fcmToken,
@ -27,13 +30,8 @@ func (db *Database) CreateClient(ctx TxContext, userid models.UserID, ctype mode
return models.Client{}, err return models.Client{}, err
} }
liid, err := res.LastInsertId()
if err != nil {
return models.Client{}, err
}
return models.Client{ return models.Client{
ClientID: models.ClientID(liid), ClientID: clientid,
UserID: userid, UserID: userid,
Type: ctype, Type: ctype,
FCMToken: langext.Ptr(fcmToken), FCMToken: langext.Ptr(fcmToken),
@ -63,7 +61,7 @@ func (db *Database) ListClients(ctx TxContext, userid models.UserID) ([]models.C
return nil, err 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 { if err != nil {
return nil, err 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) { func NewPrimaryDatabase(cfg server.Config) (*Database, error) {
conf := cfg.DBMain 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) xdb, err := sqlx.Open("sqlite3", url)
if err != nil { if err != nil {

View File

@ -17,8 +17,11 @@ func (db *Database) CreateRetryDelivery(ctx TxContext, client models.Client, msg
now := time.Now().UTC() now := time.Now().UTC()
next := scn.NextDeliveryTimestamp(now) 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{ deliveryid := models.NewDeliveryID()
"mid": msg.SCNMessageID,
_, 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, "ruid": client.UserID,
"rcid": client.ClientID, "rcid": client.ClientID,
"tsc": time2DB(now), "tsc": time2DB(now),
@ -31,14 +34,9 @@ func (db *Database) CreateRetryDelivery(ctx TxContext, client models.Client, msg
return models.Delivery{}, err return models.Delivery{}, err
} }
liid, err := res.LastInsertId()
if err != nil {
return models.Delivery{}, err
}
return models.Delivery{ return models.Delivery{
DeliveryID: models.DeliveryID(liid), DeliveryID: deliveryid,
SCNMessageID: msg.SCNMessageID, MessageID: msg.MessageID,
ReceiverUserID: client.UserID, ReceiverUserID: client.UserID,
ReceiverClientID: client.ClientID, ReceiverClientID: client.ClientID,
TimestampCreated: now, TimestampCreated: now,
@ -58,8 +56,11 @@ func (db *Database) CreateSuccessDelivery(ctx TxContext, client models.Client, m
now := time.Now().UTC() 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{ deliveryid := models.NewDeliveryID()
"mid": msg.SCNMessageID,
_, 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, "ruid": client.UserID,
"rcid": client.ClientID, "rcid": client.ClientID,
"tsc": time2DB(now), "tsc": time2DB(now),
@ -72,14 +73,9 @@ func (db *Database) CreateSuccessDelivery(ctx TxContext, client models.Client, m
return models.Delivery{}, err return models.Delivery{}, err
} }
liid, err := res.LastInsertId()
if err != nil {
return models.Delivery{}, err
}
return models.Delivery{ return models.Delivery{
DeliveryID: models.DeliveryID(liid), DeliveryID: deliveryid,
SCNMessageID: msg.SCNMessageID, MessageID: msg.MessageID,
ReceiverUserID: client.UserID, ReceiverUserID: client.UserID,
ReceiverClientID: client.ClientID, ReceiverClientID: client.ClientID,
TimestampCreated: now, TimestampCreated: now,
@ -97,7 +93,7 @@ func (db *Database) ListRetrieableDeliveries(ctx TxContext, pageSize int) ([]mod
return nil, err 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()), "next": time2DB(time.Now()),
"lim": pageSize, "lim": pageSize,
}) })
@ -169,15 +165,15 @@ func (db *Database) SetDeliveryRetry(ctx TxContext, delivery models.Delivery) er
return nil 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) tx, err := ctx.GetOrCreateTransaction(db)
if err != nil { if err != nil {
return err 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(), "ts": time.Now(),
"mid": scnMessageID, "mid": messageID,
}) })
if err != nil { if err != nil {
return err return err

View File

@ -30,7 +30,7 @@ func (db *Database) GetMessageByUserMessageID(ctx TxContext, usrMsgId string) (*
return &msg, nil 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) tx, err := ctx.GetOrCreateTransaction(db)
if err != nil { if err != nil {
return models.Message{}, err return models.Message{}, err
@ -38,9 +38,9 @@ func (db *Database) GetMessage(ctx TxContext, scnMessageID models.SCNMessageID,
var sqlcmd string var sqlcmd string
if allowDeleted { if allowDeleted {
sqlcmd = "SELECT * FROM messages WHERE scn_message_id = :mid LIMIT 1" sqlcmd = "SELECT * FROM messages WHERE message_id = :mid LIMIT 1"
} else { } 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}) 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() 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, "suid": senderUserID,
"ouid": channel.OwnerUserID, "ouid": channel.OwnerUserID,
"cnam": channel.InternalName, "cnam": channel.InternalName,
@ -82,13 +85,8 @@ func (db *Database) CreateMessage(ctx TxContext, senderUserID models.UserID, cha
return models.Message{}, err return models.Message{}, err
} }
liid, err := res.LastInsertId()
if err != nil {
return models.Message{}, err
}
return models.Message{ return models.Message{
SCNMessageID: models.SCNMessageID(liid), MessageID: messageid,
SenderUserID: senderUserID, SenderUserID: senderUserID,
OwnerUserID: channel.OwnerUserID, OwnerUserID: channel.OwnerUserID,
ChannelInternalName: channel.InternalName, ChannelInternalName: channel.InternalName,
@ -104,13 +102,13 @@ func (db *Database) CreateMessage(ctx TxContext, senderUserID models.UserID, cha
}, nil }, 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) tx, err := ctx.GetOrCreateTransaction(db)
if err != nil { if err != nil {
return err 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 { if err != nil {
return err return err
} }
@ -130,12 +128,12 @@ func (db *Database) ListMessages(ctx TxContext, filter models.MessageFilter, pag
pageCond := "1=1" pageCond := "1=1"
if inTok.Mode == cursortoken.CTMNormal { 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() 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 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 { if len(data) <= pageSize {
return data, cursortoken.End(), nil return data, cursortoken.End(), nil
} else { } 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 return data[0:pageSize], outToken, nil
} }
} }

View File

@ -20,7 +20,7 @@ CREATE TABLE `users`
DROP TABLE IF EXISTS `messages`; DROP TABLE IF EXISTS `messages`;
CREATE TABLE `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, `sender_user_id` INT(11) NOT NULL,
`timestamp_real` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, `timestamp_real` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
@ -34,5 +34,5 @@ CREATE TABLE `messages`
`fcm_message_id` VARCHAR(256) NULL, `fcm_message_id` VARCHAR(256) NULL,
`usr_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` CREATE TABLE `messages`
( (
`scn_message_id` INTEGER AUTO_INCREMENT, `message_id` INTEGER AUTO_INCREMENT,
`sender_user_id` INTEGER NOT NULL, `sender_user_id` INTEGER NOT NULL,
`timestamp_real` TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, `timestamp_real` TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
@ -32,7 +32,7 @@ CREATE TABLE `messages`
`fcm_message_id` TEXT NULL, `fcm_message_id` TEXT NULL,
`usr_message_id` TEXT NULL, `usr_message_id` TEXT NULL,
PRIMARY KEY (`scn_message_id`) PRIMARY KEY (`message_id`)
); );
CREATE TABLE `meta` CREATE TABLE `meta`

View File

@ -1,6 +1,6 @@
CREATE TABLE users CREATE TABLE users
( (
user_id INTEGER PRIMARY KEY AUTOINCREMENT, user_id TEXT NOT NULL,
username TEXT NULL DEFAULT NULL, username TEXT NULL DEFAULT NULL,
@ -18,23 +18,27 @@ CREATE TABLE users
quota_used_day TEXT NULL DEFAULT NULL, quota_used_day TEXT NULL DEFAULT NULL,
is_pro INTEGER CHECK(is_pro IN (0, 1)) NOT NULL DEFAULT 0, 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; ) STRICT;
CREATE UNIQUE INDEX "idx_users_protoken" ON users (pro_token) WHERE pro_token IS NOT NULL; CREATE UNIQUE INDEX "idx_users_protoken" ON users (pro_token) WHERE pro_token IS NOT NULL;
CREATE TABLE clients 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, type TEXT CHECK(type IN ('ANDROID', 'IOS')) NOT NULL,
fcm_token TEXT NULL, fcm_token TEXT NULL,
timestamp_created INTEGER NOT NULL, timestamp_created INTEGER NOT NULL,
agent_model TEXT NOT NULL, agent_model TEXT NOT NULL,
agent_version TEXT NOT NULL agent_version TEXT NOT NULL,
PRIMARY KEY (client_id)
) STRICT; ) STRICT;
CREATE INDEX "idx_clients_userid" ON clients (user_id); CREATE INDEX "idx_clients_userid" ON clients (user_id);
CREATE UNIQUE INDEX "idx_clients_fcmtoken" ON clients (fcm_token); 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 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, internal_name TEXT NOT NULL,
display_name TEXT NOT NULL, display_name TEXT NOT NULL,
@ -56,22 +60,26 @@ CREATE TABLE channels
timestamp_created INTEGER NOT NULL, timestamp_created INTEGER NOT NULL,
timestamp_lastsent INTEGER NULL DEFAULT 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; ) STRICT;
CREATE UNIQUE INDEX "idx_channels_identity" ON channels (owner_user_id, internal_name); CREATE UNIQUE INDEX "idx_channels_identity" ON channels (owner_user_id, internal_name);
CREATE TABLE subscriptions CREATE TABLE subscriptions
( (
subscription_id INTEGER PRIMARY KEY AUTOINCREMENT, subscription_id TEXT NOT NULL,
subscriber_user_id INTEGER NOT NULL, subscriber_user_id TEXT NOT NULL,
channel_owner_user_id INTEGER NOT NULL, channel_owner_user_id TEXT NOT NULL,
channel_internal_name TEXT NOT NULL, channel_internal_name TEXT NOT NULL,
channel_id INTEGER NOT NULL, channel_id TEXT NOT NULL,
timestamp_created INTEGER 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; ) STRICT;
CREATE UNIQUE INDEX "idx_subscriptions_ref" ON subscriptions (subscriber_user_id, channel_owner_user_id, channel_internal_name); 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); CREATE INDEX "idx_subscriptions_chan" ON subscriptions (channel_id);
@ -83,11 +91,11 @@ CREATE INDEX "idx_subscriptions_conf" ON subscriptions (confirmed);
CREATE TABLE messages CREATE TABLE messages
( (
scn_message_id INTEGER PRIMARY KEY AUTOINCREMENT, message_id TEXT NOT NULL,
sender_user_id INTEGER NOT NULL, sender_user_id TEXT NOT NULL,
owner_user_id INTEGER NOT NULL, owner_user_id TEXT NOT NULL,
channel_internal_name 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_ip TEXT NOT NULL,
sender_name TEXT NULL, sender_name TEXT NULL,
@ -99,7 +107,9 @@ CREATE TABLE messages
priority INTEGER CHECK(priority IN (0, 1, 2)) NOT NULL, priority INTEGER CHECK(priority IN (0, 1, 2)) NOT NULL,
usr_message_id TEXT 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; ) STRICT;
CREATE INDEX "idx_messages_owner_channel" ON messages (owner_user_id, channel_internal_name COLLATE BINARY); 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); 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, tokenize = unicode61,
content = 'messages', content = 'messages',
content_rowid = 'scn_message_id' content_rowid = 'rowid'
); );
CREATE TRIGGER fts_insert AFTER INSERT ON messages BEGIN 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; END;
CREATE TRIGGER fts_update AFTER UPDATE ON messages BEGIN 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 (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.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; END;
CREATE TRIGGER fts_delete AFTER DELETE ON messages BEGIN 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; END;
CREATE TABLE deliveries CREATE TABLE deliveries
( (
delivery_id INTEGER PRIMARY KEY AUTOINCREMENT, delivery_id TEXT NOT NULL,
scn_message_id INTEGER NOT NULL, message_id TEXT NOT NULL,
receiver_user_id INTEGER NOT NULL, receiver_user_id TEXT NOT NULL,
receiver_client_id INTEGER NOT NULL, receiver_client_id TEXT NOT NULL,
timestamp_created INTEGER NOT NULL, timestamp_created INTEGER NOT NULL,
timestamp_finalized INTEGER NULL, timestamp_finalized INTEGER NULL,
@ -157,9 +166,21 @@ CREATE TABLE deliveries
retry_count INTEGER NOT NULL DEFAULT 0, retry_count INTEGER NOT NULL DEFAULT 0,
next_delivery INTEGER NULL DEFAULT NULL, next_delivery INTEGER NULL DEFAULT NULL,
fcm_message_id TEXT NULL fcm_message_id TEXT NULL,
PRIMARY KEY (delivery_id)
) STRICT; ) 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` CREATE TABLE `meta`

View File

@ -15,7 +15,10 @@ func (db *Database) CreateSubscription(ctx TxContext, subscriberUID models.UserI
now := time.Now().UTC() 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, "suid": subscriberUID,
"ouid": channel.OwnerUserID, "ouid": channel.OwnerUserID,
"cnam": channel.InternalName, "cnam": channel.InternalName,
@ -27,13 +30,8 @@ func (db *Database) CreateSubscription(ctx TxContext, subscriberUID models.UserI
return models.Subscription{}, err return models.Subscription{}, err
} }
liid, err := res.LastInsertId()
if err != nil {
return models.Subscription{}, err
}
return models.Subscription{ return models.Subscription{
SubscriptionID: models.SubscriptionID(liid), SubscriptionID: subscriptionid,
SubscriberUserID: subscriberUID, SubscriberUserID: subscriberUID,
ChannelOwnerUserID: channel.OwnerUserID, ChannelOwnerUserID: channel.OwnerUserID,
ChannelID: channel.ChannelID, ChannelID: channel.ChannelID,
@ -49,7 +47,9 @@ func (db *Database) ListSubscriptionsByChannel(ctx TxContext, channelID models.C
return nil, err 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 { if err != nil {
return nil, err return nil, err
} }
@ -75,7 +75,9 @@ func (db *Database) ListSubscriptionsByChannelOwner(ctx TxContext, ownerUserID m
cond = " AND confirmed = 0" 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 { if err != nil {
return nil, err return nil, err
} }
@ -101,7 +103,9 @@ func (db *Database) ListSubscriptionsBySubscriber(ctx TxContext, subscriberUserI
cond = " AND confirmed = 0" 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 { if err != nil {
return nil, err return nil, err
} }

View File

@ -16,7 +16,10 @@ func (db *Database) CreateUser(ctx TxContext, readKey string, sendKey string, ad
now := time.Now().UTC() 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, "un": username,
"rk": readKey, "rk": readKey,
"sk": sendKey, "sk": sendKey,
@ -29,13 +32,8 @@ func (db *Database) CreateUser(ctx TxContext, readKey string, sendKey string, ad
return models.User{}, err return models.User{}, err
} }
liid, err := res.LastInsertId()
if err != nil {
return models.User{}, err
}
return models.User{ return models.User{
UserID: models.UserID(liid), UserID: userid,
Username: username, Username: username,
ReadKey: readKey, ReadKey: readKey,
SendKey: sendKey, SendKey: sendKey,

View File

@ -24,7 +24,7 @@ type Database struct {
func NewRequestsDatabase(cfg server.Config) (*Database, error) { func NewRequestsDatabase(cfg server.Config) (*Database, error) {
conf := cfg.DBRequests 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) xdb, err := sqlx.Open("sqlite3", url)
if err != nil { if err != nil {

View File

@ -7,11 +7,12 @@ import (
"time" "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() 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, "method": data.Method,
"uri": data.URI, "uri": data.URI,
"user_agent": data.UserAgent, "user_agent": data.UserAgent,
@ -38,13 +39,8 @@ func (db *Database) InsertRequestLog(ctx context.Context, data models.RequestLog
return models.RequestLogDB{}, err return models.RequestLogDB{}, err
} }
liid, err := res.LastInsertId()
if err != nil {
return models.RequestLogDB{}, err
}
return models.RequestLogDB{ return models.RequestLogDB{
RequestID: models.RequestID(liid), RequestID: requestid,
Method: data.Method, Method: data.Method,
URI: data.URI, URI: data.URI,
UserAgent: data.UserAgent, UserAgent: data.UserAgent,

View File

@ -1,7 +1,7 @@
CREATE TABLE `requests` CREATE TABLE `requests`
( (
request_id INTEGER PRIMARY KEY AUTOINCREMENT, request_id TEXT NOT NULL,
method TEXT NOT NULL, method TEXT NOT NULL,
uri TEXT NOT NULL, uri TEXT NOT NULL,
@ -15,8 +15,8 @@ CREATE TABLE `requests`
userid TEXT NULL, userid TEXT NULL,
permissions TEXT NULL, permissions TEXT NULL,
response_statuscode INTEGER NOT NULL, response_statuscode INTEGER NULL,
response_body_size INTEGER NOT NULL, response_body_size INTEGER NULL,
response_body TEXT NULL, response_body TEXT NULL,
response_content_type TEXT NOT NULL, response_content_type TEXT NOT NULL,
processing_time INTEGER NOT NULL, processing_time INTEGER NOT NULL,
@ -26,8 +26,9 @@ CREATE TABLE `requests`
timestamp_created INTEGER NOT NULL, timestamp_created INTEGER NOT NULL,
timestamp_start INTEGER NOT NULL, timestamp_start INTEGER NOT NULL,
timestamp_finish INTEGER NOT NULL timestamp_finish INTEGER NOT NULL,
PRIMARY KEY (request_id)
) STRICT; ) 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) client, err := j.app.Database.Primary.GetClient(ctx, delivery.ReceiverUserID, delivery.ReceiverClientID)
if err != nil { 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() ctx.RollbackTransaction()
return 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 { 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() ctx.RollbackTransaction()
return return
} }
@ -149,7 +149,7 @@ func (j *DeliveryRetryJob) redeliver(ctx *logic.SimpleContext, delivery models.D
if msg.Deleted { if msg.Deleted {
err = j.app.Database.Primary.SetDeliveryFailed(ctx, delivery) err = j.app.Database.Primary.SetDeliveryFailed(ctx, delivery)
if err != nil { 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() ctx.RollbackTransaction()
return return
} }
@ -159,22 +159,22 @@ func (j *DeliveryRetryJob) redeliver(ctx *logic.SimpleContext, delivery models.D
if err == nil { if err == nil {
err = j.app.Database.Primary.SetDeliverySuccess(ctx, delivery, *fcmDelivID) err = j.app.Database.Primary.SetDeliverySuccess(ctx, delivery, *fcmDelivID)
if err != nil { if err != nil {
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() ctx.RollbackTransaction()
return return
} }
} else if delivery.RetryCount+1 > delivery.MaxRetryCount() { } else if delivery.RetryCount+1 > delivery.MaxRetryCount() {
err = j.app.Database.Primary.SetDeliveryFailed(ctx, delivery) err = j.app.Database.Primary.SetDeliveryFailed(ctx, delivery)
if err != nil { 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() ctx.RollbackTransaction()
return 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 { } else {
err = j.app.Database.Primary.SetDeliveryRetry(ctx, delivery) err = j.app.Database.Primary.SetDeliveryRetry(ctx, delivery)
if err != nil { 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() ctx.RollbackTransaction()
return return
} }

View File

@ -72,11 +72,12 @@ mainLoop:
log.Error().Msg(fmt.Sprintf("Received unknown job signal: <%s> in job [%s]", signal, j.name)) log.Error().Msg(fmt.Sprintf("Received unknown job signal: <%s> in job [%s]", signal, j.name))
} }
case obj := <-j.app.RequestLogQueue: case obj := <-j.app.RequestLogQueue:
err := j.insertLog(obj) requestid := models.NewRequestID()
err := j.insertLog(requestid, obj)
if err != nil { 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 { } 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) 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) ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel() defer cancel()
_, err := j.app.Database.Requests.InsertRequestLog(ctx, rl.DB()) _, err := j.app.Database.Requests.InsertRequestLog(ctx, requestid, rl.DB())
if err != nil { if err != nil {
return err return err
} }

View File

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

View File

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

View File

@ -1,78 +1,376 @@
package models 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 { type EntityID interface {
IntID() int64
String() string String() string
Valid() error
Prefix() string
Raw() string
CheckString() string
Regex() *regexp.Regexp
} }
type UserID int64 const idlen = 24
func (id UserID) IntID() int64 { const checklen = 1
return int64(id)
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 { 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 { func (id UserID) Raw() string {
return int64(id) 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 { 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 { func (id ChannelID) Raw() string {
return int64(id) 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 { func (id DeliveryID) String() string {
return strconv.FormatInt(int64(id), 10) return string(id)
} }
type SCNMessageID int64 func (id DeliveryID) Prefix() string {
return prefixDeliveryID
func (id SCNMessageID) IntID() int64 {
return int64(id)
} }
func (id SCNMessageID) String() string { func (id DeliveryID) Raw() string {
return strconv.FormatInt(int64(id), 10) return getRawData(prefixDeliveryID, string(id))
} }
type SubscriptionID int64 func (id DeliveryID) CheckString() string {
return getCheckString(prefixDeliveryID, string(id))
}
func (id SubscriptionID) IntID() int64 { func (id DeliveryID) Regex() *regexp.Regexp {
return int64(id) 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 { 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 { func (id SubscriptionID) Raw() string {
return int64(id) 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 { 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 { func (id ClientID) Raw() string {
return int64(id) 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 { 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 { type Message struct {
SCNMessageID SCNMessageID MessageID MessageID
SenderUserID UserID SenderUserID UserID
OwnerUserID UserID OwnerUserID UserID
ChannelInternalName string ChannelInternalName string
@ -31,7 +31,7 @@ type Message struct {
func (m Message) FullJSON() MessageJSON { func (m Message) FullJSON() MessageJSON {
return MessageJSON{ return MessageJSON{
SCNMessageID: m.SCNMessageID, MessageID: m.MessageID,
SenderUserID: m.SenderUserID, SenderUserID: m.SenderUserID,
OwnerUserID: m.OwnerUserID, OwnerUserID: m.OwnerUserID,
ChannelInternalName: m.ChannelInternalName, ChannelInternalName: m.ChannelInternalName,
@ -49,7 +49,7 @@ func (m Message) FullJSON() MessageJSON {
func (m Message) TrimmedJSON() MessageJSON { func (m Message) TrimmedJSON() MessageJSON {
return MessageJSON{ return MessageJSON{
SCNMessageID: m.SCNMessageID, MessageID: m.MessageID,
SenderUserID: m.SenderUserID, SenderUserID: m.SenderUserID,
OwnerUserID: m.OwnerUserID, OwnerUserID: m.OwnerUserID,
ChannelInternalName: m.ChannelInternalName, ChannelInternalName: m.ChannelInternalName,
@ -94,7 +94,7 @@ func (m Message) ShortContent() string {
} }
type MessageJSON struct { type MessageJSON struct {
SCNMessageID SCNMessageID `json:"scn_message_id"` MessageID MessageID `json:"message_id"`
SenderUserID UserID `json:"sender_user_id"` SenderUserID UserID `json:"sender_user_id"`
OwnerUserID UserID `json:"owner_user_id"` OwnerUserID UserID `json:"owner_user_id"`
ChannelInternalName string `json:"channel_internal_name"` ChannelInternalName string `json:"channel_internal_name"`
@ -110,7 +110,7 @@ type MessageJSON struct {
} }
type MessageDB struct { type MessageDB struct {
SCNMessageID SCNMessageID `db:"scn_message_id"` MessageID MessageID `db:"message_id"`
SenderUserID UserID `db:"sender_user_id"` SenderUserID UserID `db:"sender_user_id"`
OwnerUserID UserID `db:"owner_user_id"` OwnerUserID UserID `db:"owner_user_id"`
ChannelInternalName string `db:"channel_internal_name"` ChannelInternalName string `db:"channel_internal_name"`
@ -128,7 +128,7 @@ type MessageDB struct {
func (m MessageDB) Model() Message { func (m MessageDB) Model() Message {
return Message{ return Message{
SCNMessageID: m.SCNMessageID, MessageID: m.MessageID,
SenderUserID: m.SenderUserID, SenderUserID: m.SenderUserID,
OwnerUserID: m.OwnerUserID, OwnerUserID: m.OwnerUserID,
ChannelInternalName: m.ChannelInternalName, 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 " joinClause += " LEFT JOIN subscriptions AS subs on messages.channel_id = subs.channel_id "
} }
if f.SearchString != nil { 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) sqlClauses := make([]string, 0)

View File

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

View File

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

View File

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

View File

@ -21,7 +21,7 @@ func TestCreateChannel(t *testing.T) {
"fcm_token": "DUMMY_FCM", "fcm_token": "DUMMY_FCM",
}) })
uid := int(r0["user_id"].(float64)) uid := r0["user_id"].(string)
admintok := r0["admin_key"].(string) admintok := r0["admin_key"].(string)
type chanlist struct { 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, "display_name")
tt.AssertMappedSet(t, "channels", []string{}, clist.Channels, "internal_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", "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.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, "display_name")
tt.AssertMappedSet(t, "channels", []string{"test"}, clist.Channels, "internal_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", "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, "display_name")
tt.AssertMappedSet(t, "channels", []string{"asdf", "test"}, clist.Channels, "internal_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", "fcm_token": "DUMMY_FCM",
}) })
uid := int(r0["user_id"].(float64)) uid := r0["user_id"].(string)
admintok := r0["admin_key"].(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), "name": langext.StrRepeat("X", 121),
}, 400, apierr.CHANNEL_TOO_LONG) }, 400, apierr.CHANNEL_TOO_LONG)
} }
@ -86,7 +86,7 @@ func TestChannelNameNormalization(t *testing.T) {
"fcm_token": "DUMMY_FCM", "fcm_token": "DUMMY_FCM",
}) })
uid := int(r0["user_id"].(float64)) uid := r0["user_id"].(string)
admintok := r0["admin_key"].(string) admintok := r0["admin_key"].(string)
type chanlist struct { 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, "display_name")
tt.AssertMappedSet(t, "channels", []string{}, clist.Channels, "internal_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", "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, "display_name")
tt.AssertMappedSet(t, "channels", []string{"test"}, clist.Channels, "internal_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", "name": "test",
}, 409, apierr.CHANNEL_ALREADY_EXISTS) }, 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", "name": "TEST",
}, 409, apierr.CHANNEL_ALREADY_EXISTS) }, 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", "name": "Test",
}, 409, apierr.CHANNEL_ALREADY_EXISTS) }, 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 ", "name": "Test ",
}, 409, apierr.CHANNEL_ALREADY_EXISTS) }, 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", "name": " Test",
}, 409, apierr.CHANNEL_ALREADY_EXISTS) }, 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", "name": "\rTeSt\n",
}, 409, apierr.CHANNEL_ALREADY_EXISTS) }, 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, "display_name")
tt.AssertMappedSet(t, "channels", []string{"test"}, clist.Channels, "internal_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 ", "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, "display_name")
tt.AssertMappedSet(t, "channels", []string{"test", "weird_[\uF5FF]\\stuff"}, clist.Channels, "internal_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 { 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") 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 { 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") 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 { 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") 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 { 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") 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 { 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") 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 { 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") 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", "fcm_token": "DUMMY_FCM",
}) })
uid := int(r0["user_id"].(float64)) uid := r0["user_id"].(string)
admintok := r0["admin_key"].(string) admintok := r0["admin_key"].(string)
type chanlist struct { 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, "display_name")
tt.AssertMappedSet(t, "channels", []string{}, clist.Channels, "internal_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", "name": "server-alerts",
}) })
chanid := fmt.Sprintf("%v", chan0["channel_id"]) 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, "display_name")
tt.AssertMappedSet(t, "channels", []string{"server-alerts"}, clist.Channels, "internal_name") tt.AssertMappedSet(t, "channels", []string{"server-alerts"}, clist.Channels, "internal_name")
tt.AssertEqual(t, "channels.descr", nil, clist.Channels[0]["description_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.display_name", "server-alerts", chan1["display_name"])
tt.AssertEqual(t, "channels.internal_name", "server-alerts", chan1["internal_name"]) tt.AssertEqual(t, "channels.internal_name", "server-alerts", chan1["internal_name"])
tt.AssertEqual(t, "channels.description_name", nil, chan1["description_name"]) tt.AssertEqual(t, "channels.description_name", nil, chan1["description_name"])
@ -408,12 +408,12 @@ func TestChannelUpdate(t *testing.T) {
// [1] update display_name // [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", "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.display_name", "SERVER-ALERTS", chan1["display_name"])
tt.AssertEqual(t, "channels.internal_name", "server-alerts", chan1["internal_name"]) tt.AssertEqual(t, "channels.internal_name", "server-alerts", chan1["internal_name"])
tt.AssertEqual(t, "channels.description_name", nil, chan1["description_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 // [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", "display_name": "SERVER-ALERTS2",
}, 400, apierr.CHANNEL_NAME_WOULD_CHANGE) }, 400, apierr.CHANNEL_NAME_WOULD_CHANGE)
// [3] renew subscribe_key // [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, "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.AssertNotEqual(t, "channels.subscribe_key", chan0["subscribe_key"], chan1["subscribe_key"])
tt.AssertEqual(t, "channels.send_key", chan0["send_key"], chan1["send_key"]) tt.AssertEqual(t, "channels.send_key", chan0["send_key"], chan1["send_key"])
} }
// [4] renew 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, "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.subscribe_key", chan0["subscribe_key"], chan1["subscribe_key"])
tt.AssertNotEqual(t, "channels.send_key", chan0["send_key"], chan1["send_key"]) tt.AssertNotEqual(t, "channels.send_key", chan0["send_key"], chan1["send_key"])
} }
// [5] update description_name // [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", "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"]) tt.AssertEqual(t, "channels.description_name", "hello World", chan1["description_name"])
} }
// [6] update 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 ", "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"]) tt.AssertEqual(t, "channels.description_name", "AXXhello World9", chan1["description_name"])
} }
// [7] clear 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": "", "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"]) tt.AssertEqual(t, "channels.description_name", nil, chan1["description_name"])
} }
// [8] fail to update 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), "description_name": strings.Repeat("0123456789", 48),
}, 400, apierr.CHANNEL_DESCRIPTION_TOO_LONG) }, 400, apierr.CHANNEL_DESCRIPTION_TOO_LONG)

View File

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

View File

@ -2,6 +2,7 @@ package test
import ( import (
"blackforestbytes.com/simplecloudnotifier/api/apierr" "blackforestbytes.com/simplecloudnotifier/api/apierr"
"blackforestbytes.com/simplecloudnotifier/models"
tt "blackforestbytes.com/simplecloudnotifier/test/util" tt "blackforestbytes.com/simplecloudnotifier/test/util"
"fmt" "fmt"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@ -48,7 +49,7 @@ func TestDeleteMessage(t *testing.T) {
"fcm_token": "DUMMY_FCM", "fcm_token": "DUMMY_FCM",
}) })
uid := int(r0["user_id"].(float64)) uid := r0["user_id"].(string)
sendtok := r0["send_key"].(string) sendtok := r0["send_key"].(string)
admintok := r0["admin_key"].(string) admintok := r0["admin_key"].(string)
@ -76,7 +77,7 @@ func TestDeleteMessageAndResendUsrMsgId(t *testing.T) {
"fcm_token": "DUMMY_FCM", "fcm_token": "DUMMY_FCM",
}) })
uid := int(r0["user_id"].(float64)) uid := r0["user_id"].(string)
sendtok := r0["send_key"].(string) sendtok := r0["send_key"].(string)
admintok := r0["admin_key"].(string) admintok := r0["admin_key"].(string)
@ -138,7 +139,18 @@ func TestGetMessageNotFound(t *testing.T) {
data := tt.InitDefaultData(t, ws) 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) { func TestGetMessageFull(t *testing.T) {

View File

@ -2,6 +2,7 @@ package test
import ( import (
"blackforestbytes.com/simplecloudnotifier/api/apierr" "blackforestbytes.com/simplecloudnotifier/api/apierr"
"blackforestbytes.com/simplecloudnotifier/models"
"blackforestbytes.com/simplecloudnotifier/push" "blackforestbytes.com/simplecloudnotifier/push"
tt "blackforestbytes.com/simplecloudnotifier/test/util" tt "blackforestbytes.com/simplecloudnotifier/test/util"
"fmt" "fmt"
@ -24,7 +25,7 @@ func TestSendSimpleMessageJSON(t *testing.T) {
"fcm_token": "DUMMY_FCM", "fcm_token": "DUMMY_FCM",
}) })
uid := int(r0["user_id"].(float64)) uid := r0["user_id"].(string)
admintok := r0["admin_key"].(string) admintok := r0["admin_key"].(string)
readtok := r0["read_key"].(string) readtok := r0["read_key"].(string)
sendtok := r0["send_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.AssertEqual(t, "messageCount", 1, len(pusher.Data))
tt.AssertStrRepEqual(t, "msg.title", "HelloWorld_001", pusher.Last().Message.Title) 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.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 { type mglist struct {
Messages []gin.H `json:"messages"` Messages []gin.H `json:"messages"`
@ -77,16 +78,16 @@ func TestSendSimpleMessageQuery(t *testing.T) {
"fcm_token": "DUMMY_FCM", "fcm_token": "DUMMY_FCM",
}) })
uid := int(r0["user_id"].(float64)) uid := r0["user_id"].(string)
admintok := r0["admin_key"].(string) admintok := r0["admin_key"].(string)
sendtok := r0["send_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.AssertEqual(t, "messageCount", 1, len(pusher.Data))
tt.AssertStrRepEqual(t, "msg.title", "Hello World 2134", pusher.Last().Message.Title) 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.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 { type mglist struct {
Messages []gin.H `json:"messages"` Messages []gin.H `json:"messages"`
@ -113,20 +114,20 @@ func TestSendSimpleMessageForm(t *testing.T) {
"fcm_token": "DUMMY_FCM", "fcm_token": "DUMMY_FCM",
}) })
uid := int(r0["user_id"].(float64)) uid := r0["user_id"].(string)
admintok := r0["admin_key"].(string) admintok := r0["admin_key"].(string)
sendtok := r0["send_key"].(string) sendtok := r0["send_key"].(string)
msg1 := tt.RequestPost[gin.H](t, baseUrl, "/", tt.FormData{ msg1 := tt.RequestPost[gin.H](t, baseUrl, "/", tt.FormData{
"user_key": sendtok, "user_key": sendtok,
"user_id": fmt.Sprintf("%d", uid), "user_id": uid,
"title": "Hello World 9999 [$$$]", "title": "Hello World 9999 [$$$]",
}) })
tt.AssertEqual(t, "messageCount", 1, len(pusher.Data)) tt.AssertEqual(t, "messageCount", 1, len(pusher.Data))
tt.AssertStrRepEqual(t, "msg.title", "Hello World 9999 [$$$]", pusher.Last().Message.Title) 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.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 { type mglist struct {
Messages []gin.H `json:"messages"` Messages []gin.H `json:"messages"`
@ -153,10 +154,10 @@ func TestSendSimpleMessageFormAndQuery(t *testing.T) {
"fcm_token": "DUMMY_FCM", "fcm_token": "DUMMY_FCM",
}) })
uid := int(r0["user_id"].(float64)) uid := r0["user_id"].(string)
sendtok := r0["send_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("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_key": "ERR",
"user_id": "999999", "user_id": "999999",
"title": "2222222", "title": "2222222",
@ -164,7 +165,7 @@ func TestSendSimpleMessageFormAndQuery(t *testing.T) {
tt.AssertEqual(t, "messageCount", 1, len(pusher.Data)) tt.AssertEqual(t, "messageCount", 1, len(pusher.Data))
tt.AssertStrRepEqual(t, "msg.title", "1111111", pusher.Last().Message.Title) 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) { func TestSendSimpleMessageJSONAndQuery(t *testing.T) {
@ -180,18 +181,19 @@ func TestSendSimpleMessageJSONAndQuery(t *testing.T) {
"fcm_token": "DUMMY_FCM", "fcm_token": "DUMMY_FCM",
}) })
uid := int(r0["user_id"].(float64)) uid := r0["user_id"].(string)
sendtok := r0["send_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("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_key": "ERR",
"user_id": 999999, "user_id": models.NewUserID(),
"title": "2222222", "title": "2222222",
}) })
tt.AssertEqual(t, "messageCount", 1, len(pusher.Data)) tt.AssertEqual(t, "messageCount", 1, len(pusher.Data))
tt.AssertStrRepEqual(t, "msg.title", "1111111", pusher.Last().Message.Title) 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) { func TestSendSimpleMessageAlt1(t *testing.T) {
@ -207,7 +209,7 @@ func TestSendSimpleMessageAlt1(t *testing.T) {
"fcm_token": "DUMMY_FCM", "fcm_token": "DUMMY_FCM",
}) })
uid := int(r0["user_id"].(float64)) uid := r0["user_id"].(string)
admintok := r0["admin_key"].(string) admintok := r0["admin_key"].(string)
readtok := r0["read_key"].(string) readtok := r0["read_key"].(string)
sendtok := r0["send_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.AssertEqual(t, "messageCount", 1, len(pusher.Data))
tt.AssertStrRepEqual(t, "msg.title", "HelloWorld_001", pusher.Last().Message.Title) 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.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 { type mglist struct {
Messages []gin.H `json:"messages"` Messages []gin.H `json:"messages"`
@ -254,7 +256,7 @@ func TestSendContentMessage(t *testing.T) {
"fcm_token": "DUMMY_FCM", "fcm_token": "DUMMY_FCM",
}) })
uid := int(r0["user_id"].(float64)) uid := r0["user_id"].(string)
admintok := r0["admin_key"].(string) admintok := r0["admin_key"].(string)
sendtok := r0["send_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.AssertEqual(t, "messageCount", 1, len(pusher.Data))
tt.AssertStrRepEqual(t, "msg.title", "HelloWorld_042", pusher.Last().Message.Title) 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.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 { type mglist struct {
Messages []gin.H `json:"messages"` Messages []gin.H `json:"messages"`
@ -299,7 +301,7 @@ func TestSendWithSendername(t *testing.T) {
"fcm_token": "DUMMY_FCM", "fcm_token": "DUMMY_FCM",
}) })
uid := int(r0["user_id"].(float64)) uid := r0["user_id"].(string)
sendtok := r0["send_key"].(string) sendtok := r0["send_key"].(string)
admintok := r0["admin_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.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.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.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 { type mglist struct {
Messages []gin.H `json:"messages"` Messages []gin.H `json:"messages"`
@ -348,7 +350,7 @@ func TestSendLongContent(t *testing.T) {
"fcm_token": "DUMMY_FCM", "fcm_token": "DUMMY_FCM",
}) })
uid := int(r0["user_id"].(float64)) uid := r0["user_id"].(string)
admintok := r0["admin_key"].(string) admintok := r0["admin_key"].(string)
sendtok := r0["send_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.AssertEqual(t, "messageCount", 1, len(pusher.Data))
tt.AssertStrRepEqual(t, "msg.title", "HelloWorld_042", pusher.Last().Message.Title) 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.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 { type mglist struct {
Messages []gin.H `json:"messages"` Messages []gin.H `json:"messages"`
@ -405,7 +407,7 @@ func TestSendTooLongContent(t *testing.T) {
"fcm_token": "DUMMY_FCM", "fcm_token": "DUMMY_FCM",
}) })
uid := int(r0["user_id"].(float64)) uid := r0["user_id"].(string)
sendtok := r0["send_key"].(string) sendtok := r0["send_key"].(string)
longContent := "" longContent := ""
@ -433,7 +435,7 @@ func TestSendLongContentPro(t *testing.T) {
"pro_token": "ANDROID|v2|PURCHASED:DUMMY_TOK_XX", "pro_token": "ANDROID|v2|PURCHASED:DUMMY_TOK_XX",
}) })
uid := int(r0["user_id"].(float64)) uid := r0["user_id"].(string)
sendtok := r0["send_key"].(string) sendtok := r0["send_key"].(string)
{ {
@ -519,7 +521,7 @@ func TestSendTooLongTitle(t *testing.T) {
"fcm_token": "DUMMY_FCM", "fcm_token": "DUMMY_FCM",
}) })
uid := int(r0["user_id"].(float64)) uid := r0["user_id"].(string)
sendtok := r0["send_key"].(string) sendtok := r0["send_key"].(string)
tt.RequestPostShouldFail(t, baseUrl, "/", gin.H{ tt.RequestPostShouldFail(t, baseUrl, "/", gin.H{
@ -542,7 +544,7 @@ func TestSendIdempotent(t *testing.T) {
"fcm_token": "DUMMY_FCM", "fcm_token": "DUMMY_FCM",
}) })
uid := int(r0["user_id"].(float64)) uid := r0["user_id"].(string)
readtok := r0["admin_key"].(string) readtok := r0["admin_key"].(string)
sendtok := r0["send_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.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.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.msg_id", "c0235a49-dabc-4cdc-a0ce-453966e0c2d5", pusher.Last().Message.UserMessageID)
tt.AssertStrRepEqual(t, "msg.title", "Hello SCN", pusher.Last().Message.Title) 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.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", 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.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.msg_id", "c0235a49-dabc-4cdc-a0ce-453966e0c2d5", pusher.Last().Message.UserMessageID)
tt.AssertStrRepEqual(t, "msg.title", "Hello SCN", pusher.Last().Message.Title) tt.AssertStrRepEqual(t, "msg.title", "Hello SCN", pusher.Last().Message.Title)
@ -620,7 +622,7 @@ func TestSendWithPriority(t *testing.T) {
"fcm_token": "DUMMY_FCM", "fcm_token": "DUMMY_FCM",
}) })
uid := int(r0["user_id"].(float64)) uid := r0["user_id"].(string)
sendtok := r0["send_key"].(string) sendtok := r0["send_key"].(string)
admintok := r0["admin_key"].(string) admintok := r0["admin_key"].(string)
@ -713,7 +715,7 @@ func TestSendInvalidPriority(t *testing.T) {
"fcm_token": "DUMMY_FCM", "fcm_token": "DUMMY_FCM",
}) })
uid := int(r0["user_id"].(float64)) uid := r0["user_id"].(string)
sendtok := r0["send_key"].(string) sendtok := r0["send_key"].(string)
admintok := r0["admin_key"].(string) admintok := r0["admin_key"].(string)
@ -765,11 +767,9 @@ func TestSendInvalidPriority(t *testing.T) {
"priority": 9999, "priority": 9999,
}, 400, apierr.INVALID_PRIO) }, 400, apierr.INVALID_PRIO)
struid := fmt.Sprintf("%d", uid)
tt.RequestPostShouldFail(t, baseUrl, "/", tt.FormData{ tt.RequestPostShouldFail(t, baseUrl, "/", tt.FormData{
"user_key": sendtok, "user_key": sendtok,
"user_id": struid, "user_id": uid,
"title": "(title)", "title": "(title)",
"content": "(content)", "content": "(content)",
"priority": "-1", "priority": "-1",
@ -777,7 +777,7 @@ func TestSendInvalidPriority(t *testing.T) {
tt.RequestPostShouldFail(t, baseUrl, "/", tt.FormData{ tt.RequestPostShouldFail(t, baseUrl, "/", tt.FormData{
"user_key": sendtok, "user_key": sendtok,
"user_id": struid, "user_id": uid,
"title": "(title)", "title": "(title)",
"content": "(content)", "content": "(content)",
"priority": "4", "priority": "4",
@ -785,7 +785,7 @@ func TestSendInvalidPriority(t *testing.T) {
tt.RequestPostShouldFail(t, baseUrl, "/", tt.FormData{ tt.RequestPostShouldFail(t, baseUrl, "/", tt.FormData{
"user_key": sendtok, "user_key": sendtok,
"user_id": struid, "user_id": uid,
"title": "(title)", "title": "(title)",
"content": "(content)", "content": "(content)",
"priority": "9999", "priority": "9999",
@ -793,7 +793,7 @@ func TestSendInvalidPriority(t *testing.T) {
tt.RequestPostShouldFail(t, baseUrl, "/", tt.FormData{ tt.RequestPostShouldFail(t, baseUrl, "/", tt.FormData{
"user_key": admintok, "user_key": admintok,
"user_id": struid, "user_id": uid,
"title": "(title)", "title": "(title)",
"content": "(content)", "content": "(content)",
"priority": "-1", "priority": "-1",
@ -801,7 +801,7 @@ func TestSendInvalidPriority(t *testing.T) {
tt.RequestPostShouldFail(t, baseUrl, "/", tt.FormData{ tt.RequestPostShouldFail(t, baseUrl, "/", tt.FormData{
"user_key": admintok, "user_key": admintok,
"user_id": struid, "user_id": uid,
"title": "(title)", "title": "(title)",
"content": "(content)", "content": "(content)",
"priority": "4", "priority": "4",
@ -809,7 +809,7 @@ func TestSendInvalidPriority(t *testing.T) {
tt.RequestPostShouldFail(t, baseUrl, "/", tt.FormData{ tt.RequestPostShouldFail(t, baseUrl, "/", tt.FormData{
"user_key": admintok, "user_key": admintok,
"user_id": struid, "user_id": uid,
"title": "(title)", "title": "(title)",
"content": "(content)", "content": "(content)",
"priority": "9999", "priority": "9999",
@ -831,7 +831,7 @@ func TestSendWithTimestamp(t *testing.T) {
"fcm_token": "DUMMY_FCM", "fcm_token": "DUMMY_FCM",
}) })
uid := int(r0["user_id"].(float64)) uid := r0["user_id"].(string)
sendtok := r0["send_key"].(string) sendtok := r0["send_key"].(string)
admintok := r0["admin_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{ msg1 := tt.RequestPost[gin.H](t, baseUrl, "/", tt.FormData{
"user_key": sendtok, "user_key": sendtok,
"user_id": fmt.Sprintf("%d", uid), "user_id": fmt.Sprintf("%s", uid),
"title": "TTT", "title": "TTT",
"timestamp": fmt.Sprintf("%d", ts), "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.TimestampClient", ts, pusher.Last().Message.TimestampClient.Unix())
tt.AssertStrRepEqual(t, "msg.Timestamp", ts, pusher.Last().Message.Timestamp().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.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 { type mglist struct {
Messages []gin.H `json:"messages"` Messages []gin.H `json:"messages"`
@ -888,33 +888,33 @@ func TestSendInvalidTimestamp(t *testing.T) {
"fcm_token": "DUMMY_FCM", "fcm_token": "DUMMY_FCM",
}) })
uid := int(r0["user_id"].(float64)) uid := r0["user_id"].(string)
sendtok := r0["send_key"].(string) sendtok := r0["send_key"].(string)
tt.RequestPostShouldFail(t, baseUrl, "/", tt.FormData{ tt.RequestPostShouldFail(t, baseUrl, "/", tt.FormData{
"user_key": sendtok, "user_key": sendtok,
"user_id": fmt.Sprintf("%d", uid), "user_id": fmt.Sprintf("%s", uid),
"title": "TTT", "title": "TTT",
"timestamp": "-10000", "timestamp": "-10000",
}, 400, apierr.TIMESTAMP_OUT_OF_RANGE) }, 400, apierr.TIMESTAMP_OUT_OF_RANGE)
tt.RequestPostShouldFail(t, baseUrl, "/", tt.FormData{ tt.RequestPostShouldFail(t, baseUrl, "/", tt.FormData{
"user_key": sendtok, "user_key": sendtok,
"user_id": fmt.Sprintf("%d", uid), "user_id": fmt.Sprintf("%s", uid),
"title": "TTT", "title": "TTT",
"timestamp": "0", "timestamp": "0",
}, 400, apierr.TIMESTAMP_OUT_OF_RANGE) }, 400, apierr.TIMESTAMP_OUT_OF_RANGE)
tt.RequestPostShouldFail(t, baseUrl, "/", tt.FormData{ tt.RequestPostShouldFail(t, baseUrl, "/", tt.FormData{
"user_key": sendtok, "user_key": sendtok,
"user_id": fmt.Sprintf("%d", uid), "user_id": fmt.Sprintf("%s", uid),
"title": "TTT", "title": "TTT",
"timestamp": fmt.Sprintf("%d", time.Now().Unix()-int64(25*time.Hour.Seconds())), "timestamp": fmt.Sprintf("%d", time.Now().Unix()-int64(25*time.Hour.Seconds())),
}, 400, apierr.TIMESTAMP_OUT_OF_RANGE) }, 400, apierr.TIMESTAMP_OUT_OF_RANGE)
tt.RequestPostShouldFail(t, baseUrl, "/", tt.FormData{ tt.RequestPostShouldFail(t, baseUrl, "/", tt.FormData{
"user_key": sendtok, "user_key": sendtok,
"user_id": fmt.Sprintf("%d", uid), "user_id": fmt.Sprintf("%s", uid),
"title": "TTT", "title": "TTT",
"timestamp": fmt.Sprintf("%d", time.Now().Unix()+int64(25*time.Hour.Seconds())), "timestamp": fmt.Sprintf("%d", time.Now().Unix()+int64(25*time.Hour.Seconds())),
}, 400, apierr.TIMESTAMP_OUT_OF_RANGE) }, 400, apierr.TIMESTAMP_OUT_OF_RANGE)
@ -947,28 +947,28 @@ func TestSendInvalidTimestamp(t *testing.T) {
"timestamp": time.Now().Unix() + int64(25*time.Hour.Seconds()), "timestamp": time.Now().Unix() + int64(25*time.Hour.Seconds()),
}, 400, apierr.TIMESTAMP_OUT_OF_RANGE) }, 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, sendtok,
uid, uid,
"TTT", "TTT",
-10000, -10000,
), nil, 400, apierr.TIMESTAMP_OUT_OF_RANGE) ), 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, sendtok,
uid, uid,
"TTT", "TTT",
0, 0,
), nil, 400, apierr.TIMESTAMP_OUT_OF_RANGE) ), 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, sendtok,
uid, uid,
"TTT", "TTT",
time.Now().Unix()-int64(25*time.Hour.Seconds()), time.Now().Unix()-int64(25*time.Hour.Seconds()),
), nil, 400, apierr.TIMESTAMP_OUT_OF_RANGE) ), 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, sendtok,
uid, uid,
"TTT", "TTT",
@ -978,40 +978,34 @@ func TestSendInvalidTimestamp(t *testing.T) {
tt.AssertEqual(t, "messageCount", 0, len(pusher.Data)) tt.AssertEqual(t, "messageCount", 0, len(pusher.Data))
} }
func TestSendCompat(t *testing.T) { func TestSendCompatWithOldUser(t *testing.T) {
ws, baseUrl, stop := tt.StartSimpleWebserver(t) ws, baseUrl, stop := tt.StartSimpleWebserver(t)
defer stop() defer stop()
pusher := ws.Pusher.(*push.TestSink) pusher := ws.Pusher.(*push.TestSink)
r0 := tt.RequestPost[gin.H](t, baseUrl, "/api/users", gin.H{ r0 := tt.RequestGet[gin.H](t, baseUrl, "/api/register.php?fcm_token=DUMMY_FCM&pro=0&pro_token=")
"agent_model": "DUMMY_PHONE",
"agent_version": "4X",
"client_type": "ANDROID",
"fcm_token": "DUMMY_FCM",
})
uid := int(r0["user_id"].(float64)) uidold := int64(r0["user_id"].(float64))
admintok := r0["admin_key"].(string) admintok := r0["user_key"].(string)
readtok := r0["read_key"].(string)
sendtok := r0["send_key"].(string)
msg1 := tt.RequestPost[gin.H](t, baseUrl, "/send.php", tt.FormData{ msg1 := tt.RequestPost[gin.H](t, baseUrl, "/send.php", tt.FormData{
"user_key": sendtok, "user_key": admintok,
"user_id": fmt.Sprintf("%d", uid), "user_id": fmt.Sprintf("%d", uidold),
"title": "HelloWorld_001", "title": "HelloWorld_001",
}) })
// does not allow json - only form & query
tt.RequestPostShouldFail(t, baseUrl, "/send.php", gin.H{ tt.RequestPostShouldFail(t, baseUrl, "/send.php", gin.H{
"user_key": readtok, "user_key": admintok,
"user_id": uid, "user_id": uidold,
"title": "HelloWorld_001", "title": "HelloWorld_001",
}, 0, 0) }, 400, 0)
tt.AssertEqual(t, "messageCount", 1, len(pusher.Data)) tt.AssertEqual(t, "messageCount", 1, len(pusher.Data))
tt.AssertStrRepEqual(t, "msg.title", "HelloWorld_001", pusher.Last().Message.Title) 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.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 { type mglist struct {
Messages []gin.H `json:"messages"` 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.title", "HelloWorld_001", msg1Get["title"])
tt.AssertStrRepEqual(t, "msg.channel_internal_name", "main", msg1Get["channel_internal_name"]) 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.AssertEqual(t, "messageCount", 2, len(pusher.Data))
tt.AssertStrRepEqual(t, "msg.title", "HelloWorld_002", pusher.Last().Message.Title) 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.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"])) 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{ msg3 := tt.RequestPost[gin.H](t, baseUrl, "/send.php", tt.FormData{
"user_key": sendtok, "user_key": sendtok,
"user_id": fmt.Sprintf("%d", uid), "user_id": fmt.Sprintf("%d", uidold),
"title": "HelloWorld_003", "title": "HelloWorld_003",
"content": content3, "content": content3,
"priority": "2", "priority": "2",
@ -1059,7 +1144,7 @@ func TestSendCompat(t *testing.T) {
tt.AssertEqual(t, "messageCount", 3, len(pusher.Data)) tt.AssertEqual(t, "messageCount", 3, len(pusher.Data))
tt.AssertStrRepEqual(t, "msg.Title", "HelloWorld_003", pusher.Last().Message.Title) 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.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.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", "8a2c7e92-86f3-4d69-897a-571286954030", pusher.Last().Message.UserMessageID)
tt.AssertStrRepEqual(t, "msg.UserMessageID", ts3, pusher.Last().Message.Timestamp().Unix()) tt.AssertStrRepEqual(t, "msg.UserMessageID", ts3, pusher.Last().Message.Timestamp().Unix())
@ -1077,7 +1162,7 @@ func TestSendToNewChannel(t *testing.T) {
"fcm_token": "DUMMY_FCM", "fcm_token": "DUMMY_FCM",
}) })
uid := int(r0["user_id"].(float64)) uid := r0["user_id"].(string)
sendtok := r0["send_key"].(string) sendtok := r0["send_key"].(string)
admintok := r0["admin_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)) 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, "display_name")
tt.AssertMappedSet(t, "channels", []string{"main"}, clist.Channels, "internal_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, "display_name")
tt.AssertMappedSet(t, "channels", []string{"main"}, clist.Channels, "internal_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, "display_name")
tt.AssertMappedSet(t, "channels", []string{"main", "test"}, clist.Channels, "internal_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, "display_name")
tt.AssertMappedSet(t, "channels", []string{"main", "test"}, clist.Channels, "internal_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", "fcm_token": "DUMMY_FCM",
}) })
uid := int(r0["user_id"].(float64)) uid := r0["user_id"].(string)
sendtok := r0["send_key"].(string) sendtok := r0["send_key"].(string)
admintok := r0["admin_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, "display_name")
tt.AssertMappedSet(t, "channels", []string{}, clist.Channels, "internal_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, "display_name")
tt.AssertMappedSet(t, "channels", []string{"main"}, clist.Channels, "internal_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.len", 1, len(clist.Channels))
tt.AssertEqual(t, "chan.internal_name", "main", clist.Channels[0]["internal_name"]) 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.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", "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, "display_name")
tt.AssertMappedSet(t, "channels", []string{"main", "test"}, clist.Channels, "internal_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, "display_name")
tt.AssertMappedSet(t, "channels", []string{"main", "test"}, clist.Channels, "internal_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, "display_name")
tt.AssertMappedSet(t, "channels", []string{"main", "test"}, clist.Channels, "internal_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", "fcm_token": "DUMMY_FCM",
}) })
uid := int(r0["user_id"].(float64)) uid := r0["user_id"].(string)
sendtok := r0["send_key"].(string) sendtok := r0["send_key"].(string)
tt.RequestPost[tt.Void](t, baseUrl, "/", gin.H{ tt.RequestPost[tt.Void](t, baseUrl, "/", gin.H{
@ -1281,7 +1366,7 @@ func TestQuotaExceededNoPro(t *testing.T) {
"fcm_token": "DUMMY_FCM", "fcm_token": "DUMMY_FCM",
}) })
uid := int(r0["user_id"].(float64)) uid := r0["user_id"].(string)
admintok := r0["admin_key"].(string) admintok := r0["admin_key"].(string)
sendtok := r0["send_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", 1, usr["quota_used"])
tt.AssertStrRepEqual(t, "quota.1", 50, usr["quota_max"]) 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", 49, usr["quota_used"])
tt.AssertStrRepEqual(t, "quota.49", 50, usr["quota_max"]) 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"]) 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_used"])
tt.AssertStrRepEqual(t, "quota.50", 50, usr["quota_max"]) 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", "pro_token": "ANDROID|v2|PURCHASED:DUMMY_TOK_XX",
}) })
uid := int(r0["user_id"].(float64)) uid := r0["user_id"].(string)
admintok := r0["admin_key"].(string) admintok := r0["admin_key"].(string)
sendtok := r0["send_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", 1, usr["quota_used"])
tt.AssertStrRepEqual(t, "quota.1", 1000, usr["quota_max"]) 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", 999, usr["quota_used"])
tt.AssertStrRepEqual(t, "quota.999", 1000, usr["quota_max"]) 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"]) 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_used"])
tt.AssertStrRepEqual(t, "quota.1000", 1000, usr["quota_max"]) 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", "pro_token": "ANDROID|v2|PURCHASED:DUMMY_TOK_XX",
}) })
uid := int(r0["user_id"].(float64)) uid := r0["user_id"].(string)
sendtok := r0["send_key"].(string) sendtok := r0["send_key"].(string)
count := 128 count := 128

View File

@ -59,7 +59,7 @@ type clientex struct {
} }
type Userdat struct { type Userdat struct {
UID int64 UID string
SendKey string SendKey string
AdminKey string AdminKey string
ReadKey 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) 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) readtok0 := user0["read_key"].(string)
sendtok0 := user0["send_key"].(string) sendtok0 := user0["send_key"].(string)
admintok0 := user0["admin_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["agent_version"] = cex.AgentVersion
body["client_type"] = cex.ClientType body["client_type"] = cex.ClientType
body["fcm_token"] = cex.FCMTok 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 // Create Messages
@ -378,7 +378,7 @@ func InitDefaultData(t *testing.T, ws *logic.Application) DefData {
// create manual channels // 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 // Sub/Unsub for Users 12+13
@ -399,7 +399,7 @@ func doSubscribe(t *testing.T, baseUrl string, user Userdat, chanOwner Userdat,
if user == chanOwner { 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_owner_user_id": chanOwner.UID,
"channel_internal_name": chanInternalName, "channel_internal_name": chanInternalName,
}) })
@ -409,7 +409,7 @@ func doSubscribe(t *testing.T, baseUrl string, user Userdat, chanOwner Userdat,
Channels []gin.H `json:"channels"` 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 var chandat gin.H
for _, v := range clist.Channels { 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{ 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"].(float64), "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"` 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 var subdat gin.H
for _, v := range slist.Subscriptions { 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 subdat = v
break 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"` 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 var subdat gin.H
for _, v := range slist.Subscriptions { 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 subdat = v
break 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, "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), jobs.NewRequestLogCollectorJob(app),
}) })
router.Init(ginengine) err = router.Init(ginengine)
if err != nil {
panic(err)
}
stop := func() { stop := func() {
app.Stop() app.Stop()
_ = app.IsRunning.WaitWithTimeout(5*time.Second, false)
_ = os.Remove(dbfile1) _ = os.Remove(dbfile1)
_ = os.Remove(dbfile2) _ = os.Remove(dbfile2)
_ = os.Remove(dbfile3) _ = os.Remove(dbfile3)
_ = app.IsRunning.WaitWithTimeout(400*time.Millisecond, false)
} }
go func() { app.Run() }() go func() { app.Run() }()