replace PHP in html with js & bugfixes
This commit is contained in:
parent
728b12107f
commit
0d3526221d
@ -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
|
||||
- ack/read deliveries && return ack-count
|
||||
- compat methods should bind body as form-data
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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"}}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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})
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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())
|
||||
|
@ -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": {
|
||||
|
@ -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"
|
||||
|
@ -248,3 +248,7 @@ pre, pre span
|
||||
font-family: Menlo, Consolas, monospace;
|
||||
background: #F9F9F9;;
|
||||
}
|
||||
|
||||
.display_none {
|
||||
display: none;
|
||||
}
|
@ -28,33 +28,33 @@
|
||||
|
||||
<div class="row responsive-label">
|
||||
<div class="col-sm-12 col-md-3"><label for="uid" class="doc">UserID</label></div>
|
||||
<div class="col-sm-12 col-md"><input placeholder="UserID" id="uid" class="doc" <?php echo (isset($_GET['preset_user_id']) ? (' value="'.$_GET['preset_user_id'].'" '):(''));?> type="number"></div>
|
||||
<div class="col-sm-12 col-md"><input placeholder="UserID" id="uid" class="doc" type="number"></div>
|
||||
</div>
|
||||
|
||||
<div class="row responsive-label">
|
||||
<div class="col-sm-12 col-md-3"><label for="ukey" class="doc">Authentification Key</label></div>
|
||||
<div class="col-sm-12 col-md"><input placeholder="Key" id="ukey" class="doc" <?php echo (isset($_GET['preset_user_key']) ? (' value="'.$_GET['preset_user_key'].'" '):(''));?> type="text" maxlength="64"></div>
|
||||
<div class="col-sm-12 col-md"><input placeholder="Key" id="ukey" class="doc" type="text" maxlength="64"></div>
|
||||
</div>
|
||||
|
||||
<div class="row responsive-label">
|
||||
<div class="col-sm-12 col-md-3"><label for="prio" class="doc">Priority</label></div>
|
||||
<div class="col-sm-12 col-md">
|
||||
<select id="prio" class="doc" type="text" style="width:100%;">
|
||||
<option value="0" <?php echo (( isset($_GET['preset_priority'])&&$_GET['preset_priority']==='0') ? 'selected':'');?>>Low</option>
|
||||
<option value="1" <?php echo ((!isset($_GET['preset_priority'])||$_GET['preset_priority']==='1') ? 'selected':'');?>>Normal</option>
|
||||
<option value="2" <?php echo (( isset($_GET['preset_priority'])&&$_GET['preset_priority']==='2') ? 'selected':'');?>>High</option>
|
||||
<option value="0" >Low</option>
|
||||
<option value="1" selected>Normal</option>
|
||||
<option value="2" >High</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row responsive-label">
|
||||
<div class="col-sm-12 col-md-3"><label for="msg" class="doc">Message Title</label></div>
|
||||
<div class="col-sm-12 col-md"><input placeholder="Message" id="msg" class="doc" <?php echo (isset($_GET['preset_title']) ? (' value="'.$_GET['preset_title'].'" '):(''));?> type="text" maxlength="80"></div>
|
||||
<div class="col-sm-12 col-md"><input placeholder="Message" id="msg" class="doc" type="text" maxlength="80"></div>
|
||||
</div>
|
||||
|
||||
<div class="row responsive-label">
|
||||
<div class="col-sm-12 col-md-3"><label for="txt" class="doc">Message Content</label></div>
|
||||
<div class="col-sm-12 col-md"><textarea id="txt" class="doc" <?php echo (isset($_GET['preset_content']) ? (' value="'.$_GET['preset_content'].'" '):(''));?> rows="8" maxlength="2048"></textarea></div>
|
||||
<div class="col-sm-12 col-md"><textarea id="txt" class="doc" rows="8" maxlength="2048"></textarea></div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
|
@ -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);
|
||||
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);
|
31
server/website/js/message_sent.js
Normal file
31
server/website/js/message_sent.js
Normal file
@ -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);
|
@ -22,35 +22,31 @@
|
||||
|
||||
<div class="fullcenterflex">
|
||||
|
||||
<?php if (isset($_GET['ok']) && $_GET['ok'] === "1" ): ?>
|
||||
<a id="succ_link" class="display_none card success" href="/?preset_user_id=<?php echo isset($_GET['preset_user_id'])?$_GET['preset_user_id']:'ERR';?>&preset_user_key=<?php echo isset($_GET['preset_user_key'])?$_GET['preset_user_key']:'ERR';?>">
|
||||
<div class="section">
|
||||
<h3 class="doc">Message sent</h3>
|
||||
<p class="doc">Message succesfully sent<br>
|
||||
<span id="insQuota1">ERR</span>/<span id="insQuota2">ERR</span> remaining</p>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<a class="card success" href="/index.php?preset_user_id=<?php echo isset($_GET['preset_user_id'])?$_GET['preset_user_id']:'ERR';?>&preset_user_key=<?php echo isset($_GET['preset_user_key'])?$_GET['preset_user_key']:'ERR';?>">
|
||||
<div class="section">
|
||||
<h3 class="doc">Message sent</h3>
|
||||
<p class="doc">Message succesfully sent<br>
|
||||
<?php echo isset($_GET['quota_remain'])?$_GET['quota_remain']:'ERR';?>/<?php echo isset($_GET['quota_max'])?$_GET['quota_max']:'ERR';?> remaining</p>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<?php else: ?>
|
||||
|
||||
<a class="card error" href="/index.php">
|
||||
<div class="section">
|
||||
<h3 class="doc">Failure</h3>
|
||||
<p class="doc">Unknown error</p>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<?php endif; ?>
|
||||
<a id="err_link" class="card error" href="/">
|
||||
<div class="section">
|
||||
<h3 class="doc">Failure</h3>
|
||||
<p class="doc">Unknown error</p>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
|
||||
<a tabindex="-1" href="https://play.google.com/store/apps/details?id=com.blackforestbytes.simplecloudnotifier" class="button bordered" id="tl_link"><span class="icn-google-play"></span></a>
|
||||
<a tabindex="-1" href="/index.php" class="button bordered" id="tr_link">Send</a>
|
||||
<a tabindex="-1" href="/" class="button bordered" id="tr_link">Send</a>
|
||||
|
||||
<a tabindex="-1" href="/" class="linkcaption"><h1>Simple Cloud Notifier</h1></a>
|
||||
|
||||
</div>
|
||||
|
||||
<script src="/js/message_sent.js" type="text/javascript" ></script>
|
||||
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in New Issue
Block a user