Implement /deliveries route
This commit is contained in:
@@ -271,6 +271,65 @@ func (h APIHandler) GetMessage(pctx ginext.PreContext) ginext.HTTPResponse {
|
||||
})
|
||||
}
|
||||
|
||||
// ListMessageDeliveries swaggerdoc
|
||||
//
|
||||
// @Summary List deliveries for a message
|
||||
// @Description The user must own the channel and request the resource with the ADMIN Key
|
||||
// @ID api-messages-deliveries
|
||||
// @Tags API-v2
|
||||
//
|
||||
// @Param mid path string true "MessageID"
|
||||
//
|
||||
// @Success 200 {object} handler.ListMessageDeliveries.response
|
||||
// @Failure 400 {object} ginresp.apiError "supplied values/parameters cannot be parsed / are invalid"
|
||||
// @Failure 401 {object} ginresp.apiError "user is not authorized / has missing permissions"
|
||||
// @Failure 404 {object} ginresp.apiError "message not found"
|
||||
// @Failure 500 {object} ginresp.apiError "internal server error"
|
||||
//
|
||||
// @Router /api/v2/messages/{mid}/deliveries [GET]
|
||||
func (h APIHandler) ListMessageDeliveries(pctx ginext.PreContext) ginext.HTTPResponse {
|
||||
type uri struct {
|
||||
MessageID models.MessageID `uri:"mid" binding:"entityid"`
|
||||
}
|
||||
type response struct {
|
||||
Deliveries []models.Delivery `json:"deliveries"`
|
||||
}
|
||||
|
||||
var u uri
|
||||
ctx, g, errResp := pctx.URI(&u).Start()
|
||||
if errResp != nil {
|
||||
return *errResp
|
||||
}
|
||||
defer ctx.Cancel()
|
||||
|
||||
return h.app.DoRequest(ctx, g, models.TLockRead, func(ctx *logic.AppContext, finishSuccess func(r ginext.HTTPResponse) ginext.HTTPResponse) ginext.HTTPResponse {
|
||||
|
||||
if permResp := ctx.CheckPermissionAny(); permResp != nil {
|
||||
return *permResp
|
||||
}
|
||||
|
||||
msg, err := h.database.GetMessage(ctx, u.MessageID, false)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return ginresp.APIError(g, 404, apierr.MESSAGE_NOT_FOUND, "message not found", err)
|
||||
}
|
||||
if err != nil {
|
||||
return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to query message", err)
|
||||
}
|
||||
|
||||
// User must own the channel and have admin key
|
||||
if permResp := ctx.CheckPermissionUserAdmin(msg.ChannelOwnerUserID); permResp != nil {
|
||||
return *permResp
|
||||
}
|
||||
|
||||
deliveries, err := h.database.ListDeliveriesOfMessage(ctx, msg.MessageID)
|
||||
if err != nil {
|
||||
return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to query deliveries", err)
|
||||
}
|
||||
|
||||
return finishSuccess(ginext.JSON(http.StatusOK, response{Deliveries: deliveries}))
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteMessage swaggerdoc
|
||||
//
|
||||
// @Summary Delete a single message
|
||||
|
||||
@@ -164,6 +164,7 @@ func (r *Router) Init(e *ginext.GinWrapper) error {
|
||||
apiv2.GET("/messages").Handle(r.apiHandler.ListMessages)
|
||||
apiv2.GET("/messages/:mid").Handle(r.apiHandler.GetMessage)
|
||||
apiv2.DELETE("/messages/:mid").Handle(r.apiHandler.DeleteMessage)
|
||||
apiv2.GET("/messages/:mid/deliveries").Handle(r.apiHandler.ListMessageDeliveries)
|
||||
|
||||
apiv2.GET("/sender-names").Handle(r.apiHandler.ListSenderNames)
|
||||
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
package primary
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
scn "blackforestbytes.com/simplecloudnotifier"
|
||||
"blackforestbytes.com/simplecloudnotifier/db"
|
||||
"blackforestbytes.com/simplecloudnotifier/models"
|
||||
"git.blackforestbytes.com/BlackForestBytes/goext/langext"
|
||||
"git.blackforestbytes.com/BlackForestBytes/goext/sq"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (db *Database) CreateRetryDelivery(ctx db.TxContext, client models.Client, msg models.Message) (models.Delivery, error) {
|
||||
@@ -182,3 +183,12 @@ func (db *Database) DeleteDeliveriesOfChannel(ctx db.TxContext, channelid models
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *Database) ListDeliveriesOfMessage(ctx db.TxContext, messageID models.MessageID) ([]models.Delivery, error) {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return sq.QueryAll[models.Delivery](ctx, tx, "SELECT * FROM deliveries WHERE message_id = :mid AND deleted=0 ORDER BY timestamp_created ASC", sq.PP{"mid": messageID}, sq.SModeExtended, sq.Safe)
|
||||
}
|
||||
|
||||
@@ -1552,3 +1552,111 @@ func TestListMessagesPaginatedDirectInvalidToken(t *testing.T) {
|
||||
// Test invalid paginated token (float)
|
||||
tt.RequestAuthGetShouldFail(t, data.User[16].AdminKey, baseUrl, fmt.Sprintf("/api/v2/messages?page_size=%d&next_page_token=%s", 10, "$1.5"), 400, apierr.PAGETOKEN_ERROR)
|
||||
}
|
||||
|
||||
func TestListMessageDeliveries(t *testing.T) {
|
||||
_, baseUrl, stop := tt.StartSimpleWebserver(t)
|
||||
defer stop()
|
||||
|
||||
r0 := tt.RequestPost[gin.H](t, baseUrl, "/api/v2/users", gin.H{
|
||||
"agent_model": "DUMMY_PHONE",
|
||||
"agent_version": "4X",
|
||||
"client_type": "ANDROID",
|
||||
"fcm_token": "DUMMY_FCM",
|
||||
})
|
||||
|
||||
sendtok := r0["send_key"].(string)
|
||||
admintok := r0["admin_key"].(string)
|
||||
|
||||
msg1 := tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{
|
||||
"key": sendtok,
|
||||
"title": "Message_1",
|
||||
})
|
||||
|
||||
type delivery struct {
|
||||
DeliveryID string `json:"delivery_id"`
|
||||
MessageID string `json:"message_id"`
|
||||
ReceiverUserID string `json:"receiver_user_id"`
|
||||
ReceiverClientID string `json:"receiver_client_id"`
|
||||
Status string `json:"status"`
|
||||
RetryCount int `json:"retry_count"`
|
||||
TimestampCreated string `json:"timestamp_created"`
|
||||
FCMMessageID *string `json:"fcm_message_id"`
|
||||
}
|
||||
type deliveryList struct {
|
||||
Deliveries []delivery `json:"deliveries"`
|
||||
}
|
||||
|
||||
deliveries := tt.RequestAuthGet[deliveryList](t, admintok, baseUrl, "/api/v2/messages/"+fmt.Sprintf("%v", msg1["scn_msg_id"])+"/deliveries")
|
||||
|
||||
tt.AssertTrue(t, "deliveries.len >= 1", len(deliveries.Deliveries) >= 1)
|
||||
tt.AssertEqual(t, "deliveries[0].message_id", fmt.Sprintf("%v", msg1["scn_msg_id"]), deliveries.Deliveries[0].MessageID)
|
||||
}
|
||||
|
||||
func TestListMessageDeliveriesNotFound(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/v2/messages/"+models.NewMessageID().String()+"/deliveries", 404, apierr.MESSAGE_NOT_FOUND)
|
||||
}
|
||||
|
||||
func TestListMessageDeliveriesNoAuth(t *testing.T) {
|
||||
_, baseUrl, stop := tt.StartSimpleWebserver(t)
|
||||
defer stop()
|
||||
|
||||
r0 := tt.RequestPost[gin.H](t, baseUrl, "/api/v2/users", gin.H{
|
||||
"agent_model": "DUMMY_PHONE",
|
||||
"agent_version": "4X",
|
||||
"client_type": "ANDROID",
|
||||
"fcm_token": "DUMMY_FCM",
|
||||
})
|
||||
|
||||
sendtok := r0["send_key"].(string)
|
||||
|
||||
msg1 := tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{
|
||||
"key": sendtok,
|
||||
"title": "Message_1",
|
||||
})
|
||||
|
||||
tt.RequestGetShouldFail(t, baseUrl, "/api/v2/messages/"+fmt.Sprintf("%v", msg1["scn_msg_id"])+"/deliveries", 401, apierr.USER_AUTH_FAILED)
|
||||
}
|
||||
|
||||
func TestListMessageDeliveriesNonAdminKey(t *testing.T) {
|
||||
_, baseUrl, stop := tt.StartSimpleWebserver(t)
|
||||
defer stop()
|
||||
|
||||
r0 := tt.RequestPost[gin.H](t, baseUrl, "/api/v2/users", gin.H{
|
||||
"agent_model": "DUMMY_PHONE",
|
||||
"agent_version": "4X",
|
||||
"client_type": "ANDROID",
|
||||
"fcm_token": "DUMMY_FCM",
|
||||
})
|
||||
|
||||
sendtok := r0["send_key"].(string)
|
||||
readtok := r0["read_key"].(string)
|
||||
|
||||
msg1 := tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{
|
||||
"key": sendtok,
|
||||
"title": "Message_1",
|
||||
})
|
||||
|
||||
// read key should fail (not admin)
|
||||
tt.RequestAuthGetShouldFail(t, readtok, baseUrl, "/api/v2/messages/"+fmt.Sprintf("%v", msg1["scn_msg_id"])+"/deliveries", 401, apierr.USER_AUTH_FAILED)
|
||||
}
|
||||
|
||||
func TestListMessageDeliveriesDifferentUserChannel(t *testing.T) {
|
||||
ws, baseUrl, stop := tt.StartSimpleWebserver(t)
|
||||
defer stop()
|
||||
|
||||
data := tt.InitDefaultData(t, ws)
|
||||
|
||||
// User 0 sends a message
|
||||
msg1 := tt.RequestPost[gin.H](t, baseUrl, "/", gin.H{
|
||||
"key": data.User[0].SendKey,
|
||||
"title": "Message_from_user_0",
|
||||
})
|
||||
|
||||
// User 1 tries to access deliveries of User 0's message - should fail
|
||||
tt.RequestAuthGetShouldFail(t, data.User[1].AdminKey, baseUrl, "/api/v2/messages/"+fmt.Sprintf("%v", msg1["scn_msg_id"])+"/deliveries", 401, apierr.USER_AUTH_FAILED)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user