From 0d3526221d30e13af8d921535190d4774eda85ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mike=20Schw=C3=B6rer?= Date: Sun, 20 Nov 2022 03:06:08 +0100 Subject: [PATCH] replace PHP in html with js & bugfixes --- server/README.md | 10 +- server/api/handler/api.go | 62 ++++---- server/api/handler/compat.go | 14 +- server/api/handler/message.go | 188 +++++++++++++++-------- server/api/router.go | 4 +- server/common/ginresp/apiError.go | 3 +- server/common/ginresp/resp.go | 23 ++- server/db/channels.go | 2 +- server/db/database.go | 8 +- server/db/deliveries.go | 4 +- server/db/messages.go | 2 +- server/db/schema/schema_3.ddl | 2 +- server/db/users.go | 6 +- server/logic/application.go | 23 ++- server/logic/context.go | 2 + server/swagger/swagger.json | 242 ++++++++++++++++++++++++------ server/swagger/swagger.yaml | 160 ++++++++++++++++---- server/website/css/style.css | 4 + server/website/index.html | 14 +- server/website/js/logic.js | 27 +++- server/website/js/message_sent.js | 31 ++++ server/website/message_sent.html | 36 ++--- 22 files changed, 634 insertions(+), 233 deletions(-) create mode 100644 server/website/js/message_sent.js diff --git a/server/README.md b/server/README.md index 018d371..1087a8a 100644 --- a/server/README.md +++ b/server/README.md @@ -4,13 +4,13 @@ - background job for re-delivery - - accept/decline subscriptions (PATCH subs) - - (message.go) api routes - - (compat.go) api routes + - POST::/messages - https://firebase.google.com/docs/cloud-messaging/send-message#rest - - List subscriptions on owned channels /RESTful?) + - List subscriptions on all owned channels (RESTful?) - deploy - Dockerfile - php in html - full-text-search: https://www.sqlite.org/fts5.html#contentless_tables - - route to re-create keys \ No newline at end of file + - route to re-create keys + - ack/read deliveries && return ack-count + - compat methods should bind body as form-data \ No newline at end of file diff --git a/server/api/handler/api.go b/server/api/handler/api.go index f200acf..5f94803 100644 --- a/server/api/handler/api.go +++ b/server/api/handler/api.go @@ -40,16 +40,16 @@ func NewAPIHandler(app *logic.Application) APIHandler { // @Router /api-v2/users/ [POST] func (h APIHandler) CreateUser(g *gin.Context) ginresp.HTTPResponse { type body struct { - FCMToken string `json:"fcm_token"` + FCMToken string `json:"fcm_token" binding:"required"` ProToken *string `json:"pro_token"` Username *string `json:"username"` - AgentModel string `json:"agent_model"` - AgentVersion string `json:"agent_version"` - ClientType string `json:"client_type"` + AgentModel string `json:"agent_model" binding:"required"` + AgentVersion string `json:"agent_version" binding:"required"` + ClientType string `json:"client_type" binding:"required"` } var b body - ctx, errResp := h.app.StartRequest(g, nil, nil, &b) + ctx, errResp := h.app.StartRequest(g, nil, nil, &b, nil) if errResp != nil { return *errResp } @@ -129,7 +129,7 @@ func (h APIHandler) GetUser(g *gin.Context) ginresp.HTTPResponse { } var u uri - ctx, errResp := h.app.StartRequest(g, &u, nil, nil) + ctx, errResp := h.app.StartRequest(g, &u, nil, nil, nil) if errResp != nil { return *errResp } @@ -176,7 +176,7 @@ func (h APIHandler) UpdateUser(g *gin.Context) ginresp.HTTPResponse { var u uri var b body - ctx, errResp := h.app.StartRequest(g, &u, nil, &b) + ctx, errResp := h.app.StartRequest(g, &u, nil, &b, nil) if errResp != nil { return *errResp } @@ -250,7 +250,7 @@ func (h APIHandler) ListClients(g *gin.Context) ginresp.HTTPResponse { } var u uri - ctx, errResp := h.app.StartRequest(g, &u, nil, nil) + ctx, errResp := h.app.StartRequest(g, &u, nil, nil, nil) if errResp != nil { return *errResp } @@ -292,7 +292,7 @@ func (h APIHandler) GetClient(g *gin.Context) ginresp.HTTPResponse { } var u uri - ctx, errResp := h.app.StartRequest(g, &u, nil, nil) + ctx, errResp := h.app.StartRequest(g, &u, nil, nil, nil) if errResp != nil { return *errResp } @@ -334,15 +334,15 @@ func (h APIHandler) AddClient(g *gin.Context) ginresp.HTTPResponse { UserID int64 `uri:"uid"` } type body struct { - FCMToken string `json:"fcm_token"` - AgentModel string `json:"agent_model"` - AgentVersion string `json:"agent_version"` - ClientType string `json:"client_type"` + FCMToken string `json:"fcm_token" binding:"required"` + AgentModel string `json:"agent_model" binding:"required"` + AgentVersion string `json:"agent_version" binding:"required"` + ClientType string `json:"client_type" binding:"required"` } var u uri var b body - ctx, errResp := h.app.StartRequest(g, &u, nil, &b) + ctx, errResp := h.app.StartRequest(g, &u, nil, &b, nil) if errResp != nil { return *errResp } @@ -391,7 +391,7 @@ func (h APIHandler) DeleteClient(g *gin.Context) ginresp.HTTPResponse { } var u uri - ctx, errResp := h.app.StartRequest(g, &u, nil, nil) + ctx, errResp := h.app.StartRequest(g, &u, nil, nil, nil) if errResp != nil { return *errResp } @@ -440,7 +440,7 @@ func (h APIHandler) ListChannels(g *gin.Context) ginresp.HTTPResponse { } var u uri - ctx, errResp := h.app.StartRequest(g, &u, nil, nil) + ctx, errResp := h.app.StartRequest(g, &u, nil, nil, nil) if errResp != nil { return *errResp } @@ -482,7 +482,7 @@ func (h APIHandler) GetChannel(g *gin.Context) ginresp.HTTPResponse { } var u uri - ctx, errResp := h.app.StartRequest(g, &u, nil, nil) + ctx, errResp := h.app.StartRequest(g, &u, nil, nil, nil) if errResp != nil { return *errResp } @@ -540,7 +540,7 @@ func (h APIHandler) ListChannelMessages(g *gin.Context) ginresp.HTTPResponse { var u uri var q query - ctx, errResp := h.app.StartRequest(g, &u, &q, nil) + ctx, errResp := h.app.StartRequest(g, &u, &q, nil, nil) if errResp != nil { return *errResp } @@ -620,7 +620,7 @@ func (h APIHandler) ListUserSubscriptions(g *gin.Context) ginresp.HTTPResponse { } var u uri - ctx, errResp := h.app.StartRequest(g, &u, nil, nil) + ctx, errResp := h.app.StartRequest(g, &u, nil, nil, nil) if errResp != nil { return *errResp } @@ -665,7 +665,7 @@ func (h APIHandler) ListChannelSubscriptions(g *gin.Context) ginresp.HTTPRespons } var u uri - ctx, errResp := h.app.StartRequest(g, &u, nil, nil) + ctx, errResp := h.app.StartRequest(g, &u, nil, nil, nil) if errResp != nil { return *errResp } @@ -715,7 +715,7 @@ func (h APIHandler) GetSubscription(g *gin.Context) ginresp.HTTPResponse { } var u uri - ctx, errResp := h.app.StartRequest(g, &u, nil, nil) + ctx, errResp := h.app.StartRequest(g, &u, nil, nil, nil) if errResp != nil { return *errResp } @@ -762,7 +762,7 @@ func (h APIHandler) CancelSubscription(g *gin.Context) ginresp.HTTPResponse { } var u uri - ctx, errResp := h.app.StartRequest(g, &u, nil, nil) + ctx, errResp := h.app.StartRequest(g, &u, nil, nil, nil) if errResp != nil { return *errResp } @@ -813,8 +813,8 @@ func (h APIHandler) CreateSubscription(g *gin.Context) ginresp.HTTPResponse { UserID int64 `uri:"uid"` } type body struct { - ChannelOwnerUserID int64 `form:"channel_owner_user_id"` - Channel string `form:"channel_name"` + ChannelOwnerUserID int64 `form:"channel_owner_user_id" binding:"required"` + Channel string `form:"channel_name" binding:"required"` } type query struct { ChanSubscribeKey *string `form:"chan_subscribe_key"` @@ -823,7 +823,7 @@ func (h APIHandler) CreateSubscription(g *gin.Context) ginresp.HTTPResponse { var u uri var q query var b body - ctx, errResp := h.app.StartRequest(g, &u, &q, &b) + ctx, errResp := h.app.StartRequest(g, &u, &q, &b, nil) if errResp != nil { return *errResp } @@ -841,6 +841,10 @@ func (h APIHandler) CreateSubscription(g *gin.Context) ginresp.HTTPResponse { return ginresp.InternAPIError(400, apierr.CHANNEL_NOT_FOUND, "Channel not found", err) } + if channel.OwnerUserID != u.UserID && (q.ChanSubscribeKey == nil || *q.ChanSubscribeKey != channel.SubscribeKey) { + ginresp.InternAPIError(401, apierr.USER_AUTH_FAILED, "You are not authorized for this action", nil) + } + sub, err := h.database.CreateSubscription(ctx, u.UserID, *channel, channel.OwnerUserID == u.UserID) if err != nil { return ginresp.InternAPIError(500, apierr.DATABASE_ERROR, "Failed to create subscription", err) @@ -875,7 +879,7 @@ func (h APIHandler) UpdateSubscription(g *gin.Context) ginresp.HTTPResponse { var u uri var b body - ctx, errResp := h.app.StartRequest(g, &u, nil, &b) + ctx, errResp := h.app.StartRequest(g, &u, nil, &b, nil) if errResp != nil { return *errResp } @@ -944,7 +948,7 @@ func (h APIHandler) ListMessages(g *gin.Context) ginresp.HTTPResponse { } var q query - ctx, errResp := h.app.StartRequest(g, nil, &q, nil) + ctx, errResp := h.app.StartRequest(g, nil, &q, nil, nil) if errResp != nil { return *errResp } @@ -1010,7 +1014,7 @@ func (h APIHandler) GetMessage(g *gin.Context) ginresp.HTTPResponse { } var u uri - ctx, errResp := h.app.StartRequest(g, &u, nil, nil) + ctx, errResp := h.app.StartRequest(g, &u, nil, nil, nil) if errResp != nil { return *errResp } @@ -1079,7 +1083,7 @@ func (h APIHandler) DeleteMessage(g *gin.Context) ginresp.HTTPResponse { } var u uri - ctx, errResp := h.app.StartRequest(g, &u, nil, nil) + ctx, errResp := h.app.StartRequest(g, &u, nil, nil, nil) if errResp != nil { return *errResp } diff --git a/server/api/handler/compat.go b/server/api/handler/compat.go index 3b60dc5..1bd8d33 100644 --- a/server/api/handler/compat.go +++ b/server/api/handler/compat.go @@ -53,7 +53,7 @@ func (h CompatHandler) Register(g *gin.Context) ginresp.HTTPResponse { var datq query var datb query - ctx, errResp := h.app.StartRequest(g, nil, &datq, &datb) + ctx, errResp := h.app.StartRequest(g, nil, &datq, &datb, nil) if errResp != nil { return *errResp } @@ -152,7 +152,7 @@ func (h CompatHandler) Info(g *gin.Context) ginresp.HTTPResponse { var datq query var datb query - ctx, errResp := h.app.StartRequest(g, nil, &datq, &datb) + ctx, errResp := h.app.StartRequest(g, nil, &datq, &datb, nil) if errResp != nil { return *errResp } @@ -225,7 +225,7 @@ func (h CompatHandler) Ack(g *gin.Context) ginresp.HTTPResponse { var datq query var datb query - ctx, errResp := h.app.StartRequest(g, nil, &datq, &datb) + ctx, errResp := h.app.StartRequest(g, nil, &datq, &datb, nil) if errResp != nil { return *errResp } @@ -287,7 +287,7 @@ func (h CompatHandler) Requery(g *gin.Context) ginresp.HTTPResponse { var datq query var datb query - ctx, errResp := h.app.StartRequest(g, nil, &datq, &datb) + ctx, errResp := h.app.StartRequest(g, nil, &datq, &datb, nil) if errResp != nil { return *errResp } @@ -351,7 +351,7 @@ func (h CompatHandler) Update(g *gin.Context) ginresp.HTTPResponse { var datq query var datb query - ctx, errResp := h.app.StartRequest(g, nil, &datq, &datb) + ctx, errResp := h.app.StartRequest(g, nil, &datq, &datb, nil) if errResp != nil { return *errResp } @@ -448,7 +448,7 @@ func (h CompatHandler) Expand(g *gin.Context) ginresp.HTTPResponse { var datq query var datb query - ctx, errResp := h.app.StartRequest(g, nil, &datq, &datb) + ctx, errResp := h.app.StartRequest(g, nil, &datq, &datb, nil) if errResp != nil { return *errResp } @@ -531,7 +531,7 @@ func (h CompatHandler) Upgrade(g *gin.Context) ginresp.HTTPResponse { var datq query var datb query - ctx, errResp := h.app.StartRequest(g, nil, &datq, &datb) + ctx, errResp := h.app.StartRequest(g, nil, &datq, &datb, nil) if errResp != nil { return *errResp } diff --git a/server/api/handler/message.go b/server/api/handler/message.go index e91fe18..60c242d 100644 --- a/server/api/handler/message.go +++ b/server/api/handler/message.go @@ -30,6 +30,62 @@ 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 +// +// @Param query_data query handler.SendMessageCompat.query false " " +// @Param form_data formData handler.SendMessageCompat.form false " " +// +// @Success 200 {object} handler.sendMessageInternal.response +// @Failure 400 {object} ginresp.apiError +// @Failure 401 {object} ginresp.apiError +// @Failure 403 {object} ginresp.apiError +// @Failure 404 {object} ginresp.apiError +// @Failure 500 {object} ginresp.apiError +// +// @Router /send.php [POST] +func (h MessageHandler) SendMessageCompat(g *gin.Context) ginresp.HTTPResponse { + type query struct { + UserID *int64 `form:"user_id"` + UserKey *string `form:"user_key"` + Channel *string `form:"channel"` + ChanKey *string `form:"chan_key"` + Title *string `form:"title"` + Content *string `form:"content"` + Priority *int `form:"priority"` + UserMessageID *string `form:"msg_id"` + SendTimestamp *float64 `form:"timestamp"` + } + type form struct { + UserID *int64 `form:"user_id"` + UserKey *string `form:"user_key"` + Channel *string `form:"channel"` + ChanKey *string `form:"chan_key"` + Title *string `form:"title"` + Content *string `form:"content"` + Priority *int `form:"priority"` + UserMessageID *string `form:"msg_id"` + SendTimestamp *float64 `form:"timestamp"` + } + + var f form + var q query + 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(ctx, data.UserID, data.UserKey, data.Channel, data.ChanKey, data.Title, data.Content, data.Priority, data.UserMessageID, data.SendTimestamp) + +} + // SendMessage swaggerdoc // // @Summary Send a new message @@ -38,7 +94,7 @@ func NewMessageHandler(app *logic.Application) MessageHandler { // @Param query_data query handler.SendMessage.query false " " // @Param post_body body handler.SendMessage.body false " " // -// @Success 200 {object} handler.SendMessage.response +// @Success 200 {object} handler.sendMessageInternal.response // @Failure 400 {object} ginresp.apiError // @Failure 401 {object} ginresp.apiError // @Failure 403 {object} ginresp.apiError @@ -53,8 +109,8 @@ func (h MessageHandler) SendMessage(g *gin.Context) ginresp.HTTPResponse { UserKey *string `form:"user_key"` Channel *string `form:"channel"` ChanKey *string `form:"chan_key"` - Title *string `form:"message_title"` - Content *string `form:"message_content"` + Title *string `form:"title"` + Content *string `form:"content"` Priority *int `form:"priority"` UserMessageID *string `form:"msg_id"` SendTimestamp *float64 `form:"timestamp"` @@ -64,12 +120,28 @@ func (h MessageHandler) SendMessage(g *gin.Context) ginresp.HTTPResponse { UserKey *string `json:"user_key"` Channel *string `json:"channel"` ChanKey *string `form:"chan_key"` - Title *string `json:"message_title"` - Content *string `json:"message_content"` + Title *string `json:"title"` + Content *string `json:"content"` Priority *int `json:"priority"` UserMessageID *string `json:"msg_id"` SendTimestamp *float64 `json:"timestamp"` } + + var b body + var q query + ctx, errResp := h.app.StartRequest(g, nil, &q, &b, nil) + if errResp != nil { + return *errResp + } + defer ctx.Cancel() + + data := dataext.ObjectMerge(b, q) + + return h.sendMessageInternal(ctx, data.UserID, data.UserKey, data.Channel, data.ChanKey, data.Title, data.Content, data.Priority, data.UserMessageID, data.SendTimestamp) + +} + +func (h MessageHandler) sendMessageInternal(ctx *logic.AppContext, UserID *int64, UserKey *string, Channel *string, ChanKey *string, Title *string, Content *string, Priority *int, UserMessageID *string, SendTimestamp *float64) ginresp.HTTPResponse { type response struct { Success bool `json:"success"` ErrorID apierr.APIError `json:"error"` @@ -83,65 +155,55 @@ func (h MessageHandler) SendMessage(g *gin.Context) ginresp.HTTPResponse { SCNMessageID int64 `json:"scn_msg_id"` } - var b body - var q query - ctx, errResp := h.app.StartRequest(g, nil, &q, &b) - if errResp != nil { - return *errResp + if UserID == nil { + return ginresp.SendAPIError(400, apierr.MISSING_UID, 101, "Missing parameter [[user_id]]", nil) } - defer ctx.Cancel() - - data := dataext.ObjectMerge(b, q) - - if data.UserID == nil { - return ginresp.SendAPIError(400, apierr.MISSING_UID, 101, "Missing parameter [[user_id]]") + if UserKey == nil { + return ginresp.SendAPIError(400, apierr.MISSING_UID, 102, "Missing parameter [[user_token]]", nil) } - if data.UserKey == nil { - return ginresp.SendAPIError(400, apierr.MISSING_UID, 102, "Missing parameter [[user_token]]") + if Title == nil { + return ginresp.SendAPIError(400, apierr.MISSING_UID, 103, "Missing parameter [[title]]", nil) } - if data.Title == nil { - return ginresp.SendAPIError(400, apierr.MISSING_UID, 103, "Missing parameter [[title]]") + if SendTimestamp != nil && mathext.Abs(*SendTimestamp-float64(time.Now().Unix())) > (24*time.Hour).Seconds() { + return ginresp.SendAPIError(400, apierr.TIMESTAMP_OUT_OF_RANGE, -1, "The timestamp mus be within 24 hours of now()", nil) } - if data.SendTimestamp != nil && mathext.Abs(*data.SendTimestamp-float64(time.Now().Unix())) > (24*time.Hour).Seconds() { - return ginresp.SendAPIError(400, apierr.TIMESTAMP_OUT_OF_RANGE, -1, "The timestamp mus be within 24 hours of now()") + if Priority != nil && (*Priority != 0 && *Priority != 1 && *Priority != 2) { + return ginresp.SendAPIError(400, apierr.INVALID_PRIO, 105, "Invalid priority", nil) } - if data.Priority != nil && (*data.Priority != 0 && *data.Priority != 1 && *data.Priority != 2) { - return ginresp.SendAPIError(400, apierr.INVALID_PRIO, 105, "Invalid priority") + if len(strings.TrimSpace(*Title)) == 0 { + return ginresp.SendAPIError(400, apierr.NO_TITLE, 103, "No title specified", nil) } - if len(strings.TrimSpace(*data.Title)) == 0 { - return ginresp.SendAPIError(400, apierr.NO_TITLE, 103, "No title specified") - } - if data.UserMessageID != nil && len(strings.TrimSpace(*data.UserMessageID)) > 64 { - return ginresp.SendAPIError(400, apierr.USR_MSG_ID_TOO_LONG, -1, "MessageID too long (64 characters)") + if UserMessageID != nil && len(strings.TrimSpace(*UserMessageID)) > 64 { + return ginresp.SendAPIError(400, apierr.USR_MSG_ID_TOO_LONG, -1, "MessageID too long (64 characters)", nil) } channelName := "main" - if data.Channel != nil { - channelName = strings.ToLower(strings.TrimSpace(*data.Channel)) + if Channel != nil { + channelName = strings.ToLower(strings.TrimSpace(*Channel)) } - user, err := h.database.GetUser(ctx, *data.UserID) + user, err := h.database.GetUser(ctx, *UserID) if err == sql.ErrNoRows { - return ginresp.SendAPIError(400, apierr.USER_NOT_FOUND, -1, "User not found") + return ginresp.SendAPIError(400, apierr.USER_NOT_FOUND, -1, "User not found", nil) } if err != nil { - return ginresp.SendAPIError(500, apierr.DATABASE_ERROR, -1, "Failed to query user") + return ginresp.SendAPIError(500, apierr.DATABASE_ERROR, -1, "Failed to query user", err) } - if len(strings.TrimSpace(*data.Title)) > 120 { - return ginresp.SendAPIError(400, apierr.TITLE_TOO_LONG, 103, "Title too long (120 characters)") + if len(strings.TrimSpace(*Title)) > 120 { + return ginresp.SendAPIError(400, apierr.TITLE_TOO_LONG, 103, "Title too long (120 characters)", nil) } - if data.Content != nil && len(strings.TrimSpace(*data.Content)) > user.MaxContentLength() { - return ginresp.SendAPIError(400, apierr.CONTENT_TOO_LONG, 104, fmt.Sprintf("Content too long (%d characters; max := %d characters)", len(strings.TrimSpace(*data.Content)), user.MaxContentLength())) + if Content != nil && len(strings.TrimSpace(*Content)) > user.MaxContentLength() { + return ginresp.SendAPIError(400, apierr.CONTENT_TOO_LONG, 104, fmt.Sprintf("Content too long (%d characters; max := %d characters)", len(strings.TrimSpace(*Content)), user.MaxContentLength()), nil) } - if data.UserMessageID != nil { - msg, err := h.database.GetMessageByUserMessageID(ctx, *data.UserMessageID) + if UserMessageID != nil { + msg, err := h.database.GetMessageByUserMessageID(ctx, *UserMessageID) if err != nil { - return ginresp.SendAPIError(500, apierr.DATABASE_ERROR, -1, "Failed to query existing message") + return ginresp.SendAPIError(500, apierr.DATABASE_ERROR, -1, "Failed to query existing message", err) } if msg != nil { - return ginresp.JSON(http.StatusOK, response{ + return ctx.FinishSuccess(ginresp.JSON(http.StatusOK, response{ Success: true, ErrorID: apierr.NO_ERROR, ErrorHighlight: -1, @@ -152,58 +214,58 @@ func (h MessageHandler) SendMessage(g *gin.Context) ginresp.HTTPResponse { IsPro: user.IsPro, QuotaMax: user.QuotaPerDay(), SCNMessageID: msg.SCNMessageID, - }) + })) } } if user.QuotaRemainingToday() <= 0 { - return ginresp.SendAPIError(403, apierr.QUOTA_REACHED, -1, fmt.Sprintf("Daily quota reached (%d)", user.QuotaPerDay())) + return ginresp.SendAPIError(403, apierr.QUOTA_REACHED, -1, fmt.Sprintf("Daily quota reached (%d)", user.QuotaPerDay()), nil) } - channel, err := h.app.GetOrCreateChannel(ctx, *data.UserID, channelName) + channel, err := h.app.GetOrCreateChannel(ctx, *UserID, channelName) if err != nil { - return ginresp.SendAPIError(500, apierr.DATABASE_ERROR, -1, "Failed to query/create channel") + return ginresp.SendAPIError(500, apierr.DATABASE_ERROR, -1, "Failed to query/create channel", err) } - selfChanAdmin := *data.UserID == channel.OwnerUserID && *data.UserKey == user.AdminKey - selfChanSend := *data.UserID == channel.OwnerUserID && *data.UserKey == user.SendKey - forgChanSend := *data.UserID != channel.OwnerUserID && data.ChanKey != nil && *data.ChanKey == channel.SendKey + selfChanAdmin := *UserID == channel.OwnerUserID && *UserKey == user.AdminKey + selfChanSend := *UserID == channel.OwnerUserID && *UserKey == user.SendKey + forgChanSend := *UserID != channel.OwnerUserID && ChanKey != nil && *ChanKey == channel.SendKey if !selfChanAdmin && !selfChanSend && !forgChanSend { - return ginresp.SendAPIError(401, apierr.USER_AUTH_FAILED, 102, fmt.Sprintf("Daily quota reached (%d)", user.QuotaPerDay())) + return ginresp.SendAPIError(401, apierr.USER_AUTH_FAILED, 102, fmt.Sprintf("Daily quota reached (%d)", user.QuotaPerDay()), nil) } var sendTimestamp *time.Time = nil - if data.SendTimestamp != nil { - sendTimestamp = langext.Ptr(timeext.UnixFloatSeconds(*data.SendTimestamp)) + if SendTimestamp != nil { + sendTimestamp = langext.Ptr(timeext.UnixFloatSeconds(*SendTimestamp)) } - priority := langext.Coalesce(data.Priority, 1) + priority := langext.Coalesce(Priority, 1) - msg, err := h.database.CreateMessage(ctx, *data.UserID, channel, sendTimestamp, *data.Title, data.Content, priority, data.UserMessageID) + msg, err := h.database.CreateMessage(ctx, *UserID, channel, sendTimestamp, *Title, Content, priority, UserMessageID) if err != nil { - return ginresp.SendAPIError(500, apierr.DATABASE_ERROR, -1, "Failed to create message in db") + return ginresp.SendAPIError(500, apierr.DATABASE_ERROR, -1, "Failed to create message in db", err) } subscriptions, err := h.database.ListSubscriptionsByChannel(ctx, channel.ChannelID) if err != nil { - return ginresp.SendAPIError(500, apierr.DATABASE_ERROR, -1, "Failed to query subscriptions") + return ginresp.SendAPIError(500, apierr.DATABASE_ERROR, -1, "Failed to query subscriptions", err) } err = h.database.IncUserMessageCounter(ctx, user) if err != nil { - return ginresp.SendAPIError(500, apierr.DATABASE_ERROR, -1, "Failed to inc user msg-counter") + return ginresp.SendAPIError(500, apierr.DATABASE_ERROR, -1, "Failed to inc user msg-counter", err) } err = h.database.IncChannelMessageCounter(ctx, channel) if err != nil { - return ginresp.SendAPIError(500, apierr.DATABASE_ERROR, -1, "Failed to channel msg-counter") + return ginresp.SendAPIError(500, apierr.DATABASE_ERROR, -1, "Failed to inc channel msg-counter", err) } for _, sub := range subscriptions { clients, err := h.database.ListClients(ctx, sub.SubscriberUserID) if err != nil { - return ginresp.SendAPIError(500, apierr.DATABASE_ERROR, -1, "Failed to query clients") + return ginresp.SendAPIError(500, apierr.DATABASE_ERROR, -1, "Failed to query clients", err) } if !sub.Confirmed { @@ -216,19 +278,19 @@ func (h MessageHandler) SendMessage(g *gin.Context) ginresp.HTTPResponse { if err != nil { _, err = h.database.CreateRetryDelivery(ctx, client, msg) if err != nil { - return ginresp.SendAPIError(500, apierr.DATABASE_ERROR, -1, "Failed to create delivery") + return ginresp.SendAPIError(500, apierr.DATABASE_ERROR, -1, "Failed to create delivery", err) } } else { _, err = h.database.CreateSuccessDelivery(ctx, client, msg, *fcmDelivID) if err != nil { - return ginresp.SendAPIError(500, apierr.DATABASE_ERROR, -1, "Failed to create delivery") + return ginresp.SendAPIError(500, apierr.DATABASE_ERROR, -1, "Failed to create delivery", err) } } } } - return ginresp.JSON(http.StatusOK, response{ + return ctx.FinishSuccess(ginresp.JSON(http.StatusOK, response{ Success: true, ErrorID: apierr.NO_ERROR, ErrorHighlight: -1, @@ -239,7 +301,7 @@ func (h MessageHandler) SendMessage(g *gin.Context) ginresp.HTTPResponse { IsPro: user.IsPro, QuotaMax: user.QuotaPerDay(), SCNMessageID: msg.SCNMessageID, - }) + })) } func (h MessageHandler) deliverMessage(ctx *logic.AppContext, client models.Client, msg models.Message) (*string, error) { diff --git a/server/api/router.go b/server/api/router.go index 7376c3a..1b63702 100644 --- a/server/api/router.go +++ b/server/api/router.go @@ -94,7 +94,7 @@ func (r *Router) Init(e *gin.Engine) { apiv2 := e.Group("/api-v2/") { - apiv2.POST("/users/", ginresp.Wrap(r.apiHandler.CreateUser)) + apiv2.POST("/users", ginresp.Wrap(r.apiHandler.CreateUser)) apiv2.GET("/users/:uid", ginresp.Wrap(r.apiHandler.GetUser)) apiv2.PATCH("/users/:uid", ginresp.Wrap(r.apiHandler.UpdateUser)) @@ -127,7 +127,7 @@ func (r *Router) Init(e *gin.Engine) { { sendAPI.POST("/", ginresp.Wrap(r.messageHandler.SendMessage)) sendAPI.POST("/send", ginresp.Wrap(r.messageHandler.SendMessage)) - sendAPI.POST("/send.php", ginresp.Wrap(r.messageHandler.SendMessage)) + sendAPI.POST("/send.php", ginresp.Wrap(r.messageHandler.SendMessageCompat)) } if r.app.Config.ReturnRawErrors { diff --git a/server/common/ginresp/apiError.go b/server/common/ginresp/apiError.go index 51f6356..8856269 100644 --- a/server/common/ginresp/apiError.go +++ b/server/common/ginresp/apiError.go @@ -5,7 +5,8 @@ type apiError struct { Error int `json:"error"` ErrorHighlight int `json:"errhighlight"` Message string `json:"message"` - RawError error `json:"errorObject,omitempty"` + RawError string `json:"errorObj,omitempty"` + Trace string `json:"traceObj,omitempty"` } type compatAPIError struct { diff --git a/server/common/ginresp/resp.go b/server/common/ginresp/resp.go index f54e929..1db84c3 100644 --- a/server/common/ginresp/resp.go +++ b/server/common/ginresp/resp.go @@ -3,8 +3,11 @@ package ginresp import ( scn "blackforestbytes.com/simplecloudnotifier" "blackforestbytes.com/simplecloudnotifier/api/apierr" + "fmt" "github.com/gin-gonic/gin" + "github.com/rs/zerolog/log" "net/http" + "runtime/debug" ) type HTTPResponse interface { @@ -64,25 +67,39 @@ func Text(sc int, data string) HTTPResponse { } func InternalError(e error) HTTPResponse { + log.Error().Err(e).Msg("[InternalError] " + e.Error()) + return &jsonHTTPResponse{statusCode: http.StatusInternalServerError, data: apiError{Success: false, Error: int(apierr.INTERNAL_EXCEPTION), Message: e.Error()}} } func InternAPIError(status int, errorid apierr.APIError, msg string, e error) HTTPResponse { + log.Error().Int("errorid", int(errorid)).Err(e).Msg("[InternAPIError] " + msg) + if scn.Conf.ReturnRawErrors { - return &jsonHTTPResponse{statusCode: status, data: apiError{Success: false, Error: int(errorid), Message: msg, RawError: e}} + return &jsonHTTPResponse{statusCode: status, data: apiError{Success: false, Error: int(errorid), Message: msg, RawError: fmt.Sprintf("%+v", e), Trace: string(debug.Stack())}} } else { return &jsonHTTPResponse{statusCode: status, data: apiError{Success: false, Error: int(errorid), Message: msg}} } } func CompatAPIError(errid int, msg string) HTTPResponse { + log.Error().Int("errid", errid).Msg("[CompatAPIError] " + msg) + return &jsonHTTPResponse{statusCode: 200, data: compatAPIError{Success: false, ErrorID: errid, Message: msg}} } -func SendAPIError(status int, errorid apierr.APIError, highlight int, msg string) HTTPResponse { - return &jsonHTTPResponse{statusCode: status, data: apiError{Success: false, Error: int(errorid), ErrorHighlight: highlight, Message: msg}} +func SendAPIError(status int, errorid apierr.APIError, highlight int, msg string, e error) HTTPResponse { + log.Error().Int("errorid", int(errorid)).Int("highlight", highlight).Err(e).Msg("[SendAPIError] " + msg) + + if scn.Conf.ReturnRawErrors { + return &jsonHTTPResponse{statusCode: status, data: apiError{Success: false, Error: int(errorid), ErrorHighlight: highlight, Message: msg, RawError: fmt.Sprintf("%+v", e), Trace: string(debug.Stack())}} + } else { + return &jsonHTTPResponse{statusCode: status, data: apiError{Success: false, Error: int(errorid), ErrorHighlight: highlight, Message: msg}} + } } func NotImplemented() HTTPResponse { + log.Error().Msg("[NotImplemented]") + return &jsonHTTPResponse{statusCode: http.StatusInternalServerError, data: apiError{Success: false, Error: -1, ErrorHighlight: 0, Message: "Not Implemented"}} } diff --git a/server/db/channels.go b/server/db/channels.go index 1b3c430..903988f 100644 --- a/server/db/channels.go +++ b/server/db/channels.go @@ -129,7 +129,7 @@ func (db *Database) IncChannelMessageCounter(ctx TxContext, channel models.Chann return err } - _, err = tx.ExecContext(ctx, "UPDATE channels SET messages_sent = ? AND timestamp_lastsent = ? WHERE channel_id = ?", + _, err = tx.ExecContext(ctx, "UPDATE channels SET messages_sent = ?, timestamp_lastsent = ? WHERE channel_id = ?", channel.MessagesSent+1, time2DB(time.Now()), channel.ChannelID) diff --git a/server/db/database.go b/server/db/database.go index 6b885b3..8ee359f 100644 --- a/server/db/database.go +++ b/server/db/database.go @@ -69,13 +69,13 @@ func (db *Database) ReadSchema(ctx context.Context) (int, error) { return 0, errors.New("no schema entry in meta table") } - var schema int - err = r2.Scan(&schema) + var dbschema int + err = r2.Scan(&dbschema) if err != nil { return 0, err } - return schema, nil + return dbschema, nil } func (db *Database) Ping() error { @@ -83,5 +83,5 @@ func (db *Database) Ping() error { } func (db *Database) BeginTx(ctx context.Context) (*sql.Tx, error) { - return db.db.BeginTx(ctx, nil) + return db.db.BeginTx(ctx, &sql.TxOptions{Isolation: sql.LevelDefault}) } diff --git a/server/db/deliveries.go b/server/db/deliveries.go index 24373e5..3105ce9 100644 --- a/server/db/deliveries.go +++ b/server/db/deliveries.go @@ -15,7 +15,7 @@ func (db *Database) CreateRetryDelivery(ctx TxContext, client models.Client, msg now := time.Now().UTC() next := now.Add(5 * time.Second) - res, err := tx.ExecContext(ctx, "INSERT INTO deliveries (scn_message_id, receiver_user_id, receiver_client_id, timestamp_created, timestamp_finalized, status, fcm_message_id, next_delivery) VALUES (?, ?, ?, ?, ?, ?, ?)", + res, err := tx.ExecContext(ctx, "INSERT INTO deliveries (scn_message_id, receiver_user_id, receiver_client_id, timestamp_created, timestamp_finalized, status, fcm_message_id, next_delivery) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", msg.SCNMessageID, client.UserID, client.ClientID, @@ -55,7 +55,7 @@ func (db *Database) CreateSuccessDelivery(ctx TxContext, client models.Client, m now := time.Now().UTC() - res, err := tx.ExecContext(ctx, "INSERT INTO deliveries (scn_message_id, receiver_user_id, receiver_client_id, timestamp_created, timestamp_finalized, status, fcm_message_id, next_delivery) VALUES (?, ?, ?, ?, ?, ?, ?)", + res, err := tx.ExecContext(ctx, "INSERT INTO deliveries (scn_message_id, receiver_user_id, receiver_client_id, timestamp_created, timestamp_finalized, status, fcm_message_id, next_delivery) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", msg.SCNMessageID, client.UserID, client.ClientID, diff --git a/server/db/messages.go b/server/db/messages.go index 7d1980c..5aee0f8 100644 --- a/server/db/messages.go +++ b/server/db/messages.go @@ -57,7 +57,7 @@ func (db *Database) CreateMessage(ctx TxContext, senderUserID int64, channel mod now := time.Now().UTC() - res, err := tx.ExecContext(ctx, "INSERT INTO messages (sender_user_id, owner_user_id, channel_name, channel_id, timestamp_real, timestamp_client, title, content, priority, usr_message_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", + res, err := tx.ExecContext(ctx, "INSERT INTO messages (sender_user_id, owner_user_id, channel_name, channel_id, timestamp_real, timestamp_client, title, content, priority, usr_message_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", senderUserID, channel.OwnerUserID, channel.Name, diff --git a/server/db/schema/schema_3.ddl b/server/db/schema/schema_3.ddl index e371e19..3720f5c 100644 --- a/server/db/schema/schema_3.ddl +++ b/server/db/schema/schema_3.ddl @@ -103,7 +103,7 @@ CREATE TABLE deliveries receiver_client_id INTEGER NOT NULL, timestamp_created INTEGER NOT NULL, - timestamp_finalized INTEGER NOT NULL, + timestamp_finalized INTEGER NULL, status TEXT CHECK(status IN ('RETRY','SUCCESS','FAILED')) NOT NULL, diff --git a/server/db/users.go b/server/db/users.go index 157c601..6fc5413 100644 --- a/server/db/users.go +++ b/server/db/users.go @@ -126,7 +126,7 @@ func (db *Database) UpdateUserProToken(ctx TxContext, userid int64, protoken *st return err } - _, err = tx.ExecContext(ctx, "UPDATE users SET pro_token = ? AND is_pro = ? WHERE user_id = ?", + _, err = tx.ExecContext(ctx, "UPDATE users SET pro_token = ?, is_pro = ? WHERE user_id = ?", protoken, bool2DB(protoken != nil), userid) @@ -145,7 +145,7 @@ func (db *Database) IncUserMessageCounter(ctx TxContext, user models.User) error quota := user.QuotaUsedToday() + 1 - _, err = tx.ExecContext(ctx, "UPDATE users SET timestamp_lastsent = ? AND messages_sent = ? AND quota_used = ? AND quota_used_day = ? WHERE user_id = ?", + _, err = tx.ExecContext(ctx, "UPDATE users SET timestamp_lastsent = ?, messages_sent = ?, quota_used = ?, quota_used_day = ? WHERE user_id = ?", time2DB(time.Now()), user.MessagesSent+1, quota, @@ -180,7 +180,7 @@ func (db *Database) UpdateUserKeys(ctx TxContext, userid int64, sendKey string, return err } - _, err = tx.ExecContext(ctx, "UPDATE users SET send_key = ? AND read_key = ? AND admin_key = ? WHERE user_id = ?", + _, err = tx.ExecContext(ctx, "UPDATE users SET send_key = ?, read_key = ?, admin_key = ? WHERE user_id = ?", sendKey, readKey, adminKey, diff --git a/server/logic/application.go b/server/logic/application.go index a54e8a0..e04cafe 100644 --- a/server/logic/application.go +++ b/server/logic/application.go @@ -9,6 +9,7 @@ import ( "blackforestbytes.com/simplecloudnotifier/models" "context" "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" "github.com/rs/zerolog/log" "gogs.mikescher.com/BlackForestBytes/goext/langext" "math/rand" @@ -100,23 +101,29 @@ func (app *Application) Migrate() error { return app.Database.Migrate(ctx) } -func (app *Application) StartRequest(g *gin.Context, uri any, query any, body any) (*AppContext, *ginresp.HTTPResponse) { +func (app *Application) StartRequest(g *gin.Context, uri any, query any, body any, form any) (*AppContext, *ginresp.HTTPResponse) { - if body != nil { - if err := g.ShouldBindJSON(&body); err != nil { - return nil, langext.Ptr(ginresp.InternAPIError(400, apierr.BINDFAIL_BODY_PARAM, "Failed to read body", err)) + if uri != nil { + if err := g.ShouldBindUri(uri); err != nil { + return nil, langext.Ptr(ginresp.InternAPIError(400, apierr.BINDFAIL_URI_PARAM, "Failed to read uri", err)) } } if query != nil { - if err := g.ShouldBindQuery(&query); err != nil { + if err := g.ShouldBindQuery(query); err != nil { return nil, langext.Ptr(ginresp.InternAPIError(400, apierr.BINDFAIL_QUERY_PARAM, "Failed to read query", err)) } } - if uri != nil { - if err := g.ShouldBindUri(&uri); err != nil { - return nil, langext.Ptr(ginresp.InternAPIError(400, apierr.BINDFAIL_URI_PARAM, "Failed to read uri", err)) + if body != nil { + if err := g.ShouldBindJSON(body); err != nil { + return nil, langext.Ptr(ginresp.InternAPIError(400, apierr.BINDFAIL_BODY_PARAM, "Failed to read body", err)) + } + } + + if form != nil { + if err := g.ShouldBindWith(form, binding.Form); err != nil { + return nil, langext.Ptr(ginresp.InternAPIError(400, apierr.BINDFAIL_BODY_PARAM, "Failed to read multipart-form", err)) } } diff --git a/server/logic/context.go b/server/logic/context.go index ae907fc..1ccd06a 100644 --- a/server/logic/context.go +++ b/server/logic/context.go @@ -7,6 +7,7 @@ import ( "context" "database/sql" "errors" + "github.com/rs/zerolog/log" "time" ) @@ -47,6 +48,7 @@ func (ac *AppContext) Value(key any) any { func (ac *AppContext) Cancel() { ac.cancelled = true if ac.transaction != nil { + log.Error().Msg("Rollback transaction") err := ac.transaction.Rollback() if err != nil { panic("failed to rollback transaction: " + err.Error()) diff --git a/server/swagger/swagger.json b/server/swagger/swagger.json index 1ab6fea..ff76729 100644 --- a/server/swagger/swagger.json +++ b/server/swagger/swagger.json @@ -72,7 +72,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/handler.SendMessage.response" + "$ref": "#/definitions/handler.sendMessageInternal.response" } }, "400": { @@ -1404,7 +1404,144 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/handler.SendMessage.response" + "$ref": "#/definitions/handler.sendMessageInternal.response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/ginresp.apiError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/ginresp.apiError" + } + }, + "403": { + "description": "Forbidden", + "schema": { + "$ref": "#/definitions/ginresp.apiError" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/ginresp.apiError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/ginresp.apiError" + } + } + } + } + }, + "/send.php": { + "post": { + "description": "All parameter can be set via query-parameter or form-data body. Only UserID, UserKey and Title are required", + "summary": "Send a new message (compatibility)", + "deprecated": true, + "parameters": [ + { + "type": "string", + "name": "chanKey", + "in": "query" + }, + { + "type": "string", + "name": "channel", + "in": "query" + }, + { + "type": "string", + "name": "content", + "in": "query" + }, + { + "type": "integer", + "name": "priority", + "in": "query" + }, + { + "type": "number", + "name": "sendTimestamp", + "in": "query" + }, + { + "type": "string", + "name": "title", + "in": "query" + }, + { + "type": "integer", + "name": "userID", + "in": "query" + }, + { + "type": "string", + "name": "userKey", + "in": "query" + }, + { + "type": "string", + "name": "userMessageID", + "in": "query" + }, + { + "type": "string", + "name": "chanKey", + "in": "formData" + }, + { + "type": "string", + "name": "channel", + "in": "formData" + }, + { + "type": "string", + "name": "content", + "in": "formData" + }, + { + "type": "integer", + "name": "priority", + "in": "formData" + }, + { + "type": "number", + "name": "sendTimestamp", + "in": "formData" + }, + { + "type": "string", + "name": "title", + "in": "formData" + }, + { + "type": "integer", + "name": "userID", + "in": "formData" + }, + { + "type": "string", + "name": "userKey", + "in": "formData" + }, + { + "type": "string", + "name": "userMessageID", + "in": "formData" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.sendMessageInternal.response" } }, "400": { @@ -1451,12 +1588,17 @@ "error": { "type": "integer" }, - "errorObject": {}, + "errorObj": { + "type": "string" + }, "message": { "type": "string" }, "success": { "type": "boolean" + }, + "traceObj": { + "type": "string" } } }, @@ -1493,6 +1635,12 @@ }, "handler.AddClient.body": { "type": "object", + "required": [ + "agent_model", + "agent_version", + "client_type", + "fcm_token" + ], "properties": { "agent_model": { "type": "string" @@ -1510,6 +1658,10 @@ }, "handler.CreateSubscription.body": { "type": "object", + "required": [ + "channel", + "channelOwnerUserID" + ], "properties": { "channel": { "type": "string" @@ -1521,6 +1673,12 @@ }, "handler.CreateUser.body": { "type": "object", + "required": [ + "agent_model", + "agent_version", + "client_type", + "fcm_token" + ], "properties": { "agent_model": { "type": "string" @@ -1746,10 +1904,7 @@ "channel": { "type": "string" }, - "message_content": { - "type": "string" - }, - "message_title": { + "content": { "type": "string" }, "msg_id": { @@ -1761,6 +1916,9 @@ "timestamp": { "type": "number" }, + "title": { + "type": "string" + }, "user_id": { "type": "integer" }, @@ -1769,41 +1927,6 @@ } } }, - "handler.SendMessage.response": { - "type": "object", - "properties": { - "errhighlight": { - "type": "integer" - }, - "error": { - "type": "integer" - }, - "is_pro": { - "type": "boolean" - }, - "message": { - "type": "string" - }, - "messagecount": { - "type": "integer" - }, - "quota": { - "type": "integer" - }, - "quota_max": { - "type": "integer" - }, - "scn_msg_id": { - "type": "integer" - }, - "success": { - "type": "boolean" - }, - "suppress_send": { - "type": "boolean" - } - } - }, "handler.Update.response": { "type": "object", "properties": { @@ -1901,6 +2024,41 @@ } } }, + "handler.sendMessageInternal.response": { + "type": "object", + "properties": { + "errhighlight": { + "type": "integer" + }, + "error": { + "type": "integer" + }, + "is_pro": { + "type": "boolean" + }, + "message": { + "type": "string" + }, + "messagecount": { + "type": "integer" + }, + "quota": { + "type": "integer" + }, + "quota_max": { + "type": "integer" + }, + "scn_msg_id": { + "type": "integer" + }, + "success": { + "type": "boolean" + }, + "suppress_send": { + "type": "boolean" + } + } + }, "models.ChannelJSON": { "type": "object", "properties": { diff --git a/server/swagger/swagger.yaml b/server/swagger/swagger.yaml index c2b7954..4651dfd 100644 --- a/server/swagger/swagger.yaml +++ b/server/swagger/swagger.yaml @@ -6,11 +6,14 @@ definitions: type: integer error: type: integer - errorObject: {} + errorObj: + type: string message: type: string success: type: boolean + traceObj: + type: string type: object ginresp.compatAPIError: properties: @@ -42,6 +45,11 @@ definitions: type: string fcm_token: type: string + required: + - agent_model + - agent_version + - client_type + - fcm_token type: object handler.CreateSubscription.body: properties: @@ -49,6 +57,9 @@ definitions: type: string channelOwnerUserID: type: integer + required: + - channel + - channelOwnerUserID type: object handler.CreateUser.body: properties: @@ -64,6 +75,11 @@ definitions: type: string username: type: string + required: + - agent_model + - agent_version + - client_type + - fcm_token type: object handler.DatabaseTest.response: properties: @@ -197,9 +213,7 @@ definitions: type: string channel: type: string - message_content: - type: string - message_title: + content: type: string msg_id: type: string @@ -207,34 +221,13 @@ definitions: type: integer timestamp: type: number + title: + type: string user_id: type: integer user_key: type: string type: object - handler.SendMessage.response: - properties: - errhighlight: - type: integer - error: - type: integer - is_pro: - type: boolean - message: - type: string - messagecount: - type: integer - quota: - type: integer - quota_max: - type: integer - scn_msg_id: - type: integer - success: - type: boolean - suppress_send: - type: boolean - type: object handler.Update.response: properties: is_pro: @@ -298,6 +291,29 @@ definitions: uri: type: string type: object + handler.sendMessageInternal.response: + properties: + errhighlight: + type: integer + error: + type: integer + is_pro: + type: boolean + message: + type: string + messagecount: + type: integer + quota: + type: integer + quota_max: + type: integer + scn_msg_id: + type: integer + success: + type: boolean + suppress_send: + type: boolean + type: object models.ChannelJSON: properties: channel_id: @@ -468,7 +484,7 @@ paths: "200": description: OK schema: - $ref: '#/definitions/handler.SendMessage.response' + $ref: '#/definitions/handler.sendMessageInternal.response' "400": description: Bad Request schema: @@ -1355,7 +1371,7 @@ paths: "200": description: OK schema: - $ref: '#/definitions/handler.SendMessage.response' + $ref: '#/definitions/handler.sendMessageInternal.response' "400": description: Bad Request schema: @@ -1377,4 +1393,90 @@ paths: schema: $ref: '#/definitions/ginresp.apiError' summary: Send a new message + /send.php: + post: + deprecated: true + description: All parameter can be set via query-parameter or form-data body. + Only UserID, UserKey and Title are required + parameters: + - in: query + name: chanKey + type: string + - in: query + name: channel + type: string + - in: query + name: content + type: string + - in: query + name: priority + type: integer + - in: query + name: sendTimestamp + type: number + - in: query + name: title + type: string + - in: query + name: userID + type: integer + - in: query + name: userKey + type: string + - in: query + name: userMessageID + type: string + - in: formData + name: chanKey + type: string + - in: formData + name: channel + type: string + - in: formData + name: content + type: string + - in: formData + name: priority + type: integer + - in: formData + name: sendTimestamp + type: number + - in: formData + name: title + type: string + - in: formData + name: userID + type: integer + - in: formData + name: userKey + type: string + - in: formData + name: userMessageID + type: string + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handler.sendMessageInternal.response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/ginresp.apiError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/ginresp.apiError' + "403": + description: Forbidden + schema: + $ref: '#/definitions/ginresp.apiError' + "404": + description: Not Found + schema: + $ref: '#/definitions/ginresp.apiError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/ginresp.apiError' + summary: Send a new message (compatibility) swagger: "2.0" diff --git a/server/website/css/style.css b/server/website/css/style.css index bec9693..3ac9347 100644 --- a/server/website/css/style.css +++ b/server/website/css/style.css @@ -247,4 +247,8 @@ pre, pre span { font-family: Menlo, Consolas, monospace; background: #F9F9F9;; +} + +.display_none { + display: none; } \ No newline at end of file diff --git a/server/website/index.html b/server/website/index.html index cd3c81b..0c553a0 100644 --- a/server/website/index.html +++ b/server/website/index.html @@ -28,33 +28,33 @@
-
type="number">
+
-
type="text" maxlength="64">
+
-
type="text" maxlength="80">
+
-
+
diff --git a/server/website/js/logic.js b/server/website/js/logic.js index 6171928..756a965 100644 --- a/server/website/js/logic.js +++ b/server/website/js/logic.js @@ -28,7 +28,7 @@ function send() data.append('priority', pio.value); let xhr = new XMLHttpRequest(); - xhr.open('POST', '/', true); + xhr.open('POST', '/send.php', true); xhr.onreadystatechange = function () { if (xhr.readyState !== 4) return; @@ -81,10 +81,27 @@ function send() xhr.send(data); } -window.addEventListener("load",function () +window.addEventListener("load", function () { - let btnSend = document.getElementById("btnSend"); + const qp = new URLSearchParams(window.location.search); - if (btnSend !== undefined) btnSend.onclick = function () { send(); return false; }; + const btnSend = document.getElementById("btnSend"); + const selPrio = document.getElementById("prio"); + const txtKey = document.getElementById("ukey"); + const txtUID = document.getElementById("uid"); + const txtTitl = document.getElementById("msg"); + const txtCont = document.getElementById("txt"); -},false); \ No newline at end of file + btnSend.onclick = function () { send(); return false; }; + + if (qp.has('preset_priority')) selPrio.selectedIndex = parseInt(qp.get("preset_priority")); + + if (qp.has('preset_user_key')) txtKey.value = qp.get("preset_user_key"); + + if (qp.has('preset_user_id')) txtUID.value = qp.get("preset_user_id"); + + if (qp.has('preset_title')) txtTitl.value = qp.get("preset_title"); + + if (qp.has('preset_content')) txtCont.value = qp.get("preset_content"); + +}, false); \ No newline at end of file diff --git a/server/website/js/message_sent.js b/server/website/js/message_sent.js new file mode 100644 index 0000000..28dfdc9 --- /dev/null +++ b/server/website/js/message_sent.js @@ -0,0 +1,31 @@ + +window.addEventListener("load", function () +{ + const qp = new URLSearchParams(window.location.search); + + const spanQuota1 = document.getElementById("insQuota1"); + const spanQuota2 = document.getElementById("insQuota2"); + const linkSucc = document.getElementById("succ_link"); + const linkErr = document.getElementById("err_link"); + + spanQuota1.innerText = qp.get('quota_remain') ?? 'ERR'; + spanQuota2.innerText = qp.get('quota_max') ?? 'ERR'; + + const preset_user_id = qp.get('preset_user_id') ?? 'ERR'; + const preset_user_key = qp.get('preset_user_key') ?? 'ERR'; + + linkSucc.setAttribute("href", "/?preset_user_id="+preset_user_id+"&preset_user_key="+preset_user_key); + + if (qp.get("ok") === "1") { + + linkSucc.classList.remove('display_none'); + linkErr.classList.add('display_none'); + + } else { + + linkSucc.classList.add('display_none'); + linkErr.classList.remove('display_none'); + + } + +}, false); \ No newline at end of file diff --git a/server/website/message_sent.html b/server/website/message_sent.html index e17c20d..ca84126 100644 --- a/server/website/message_sent.html +++ b/server/website/message_sent.html @@ -22,35 +22,31 @@ - Send + Send

Simple Cloud Notifier

+ + \ No newline at end of file