ListMessages()

This commit is contained in:
Mike Schwörer 2022-11-20 00:19:41 +01:00
parent 0d641b727f
commit 80f3b982d2
Signed by: Mikescher
GPG Key ID: D3C7172E0A70F8CF
15 changed files with 1635 additions and 115 deletions

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="SqlDialectMappings"> <component name="SqlDialectMappings">
<file url="file://$PROJECT_DIR$/db/schema_3.ddl" dialect="SQLite" /> <file url="file://$PROJECT_DIR$/db/schema/schema_3.ddl" dialect="SQLite" />
<file url="PROJECT" dialect="SQLite" /> <file url="PROJECT" dialect="SQLite" />
</component> </component>
<component name="SqlResolveMappings"> <component name="SqlResolveMappings">

View File

@ -12,3 +12,4 @@
- deploy - deploy
- Dockerfile - Dockerfile
- php in html - php in html
- full-text-search: https://www.sqlite.org/fts5.html#contentless_tables

View File

@ -12,6 +12,7 @@ const (
INVALID_PRIO APIError = 1104 INVALID_PRIO APIError = 1104
REQ_METHOD APIError = 1105 REQ_METHOD APIError = 1105
INVALID_CLIENTTYPE APIError = 1106 INVALID_CLIENTTYPE APIError = 1106
PAGETOKEN_ERROR APIError = 1120
BINDFAIL_QUERY_PARAM APIError = 1151 BINDFAIL_QUERY_PARAM APIError = 1151
BINDFAIL_BODY_PARAM APIError = 1152 BINDFAIL_BODY_PARAM APIError = 1152
BINDFAIL_URI_PARAM APIError = 1153 BINDFAIL_URI_PARAM APIError = 1153

View File

@ -4,11 +4,13 @@ import (
"blackforestbytes.com/simplecloudnotifier/api/apierr" "blackforestbytes.com/simplecloudnotifier/api/apierr"
"blackforestbytes.com/simplecloudnotifier/common/ginresp" "blackforestbytes.com/simplecloudnotifier/common/ginresp"
"blackforestbytes.com/simplecloudnotifier/db" "blackforestbytes.com/simplecloudnotifier/db"
"blackforestbytes.com/simplecloudnotifier/db/cursortoken"
"blackforestbytes.com/simplecloudnotifier/logic" "blackforestbytes.com/simplecloudnotifier/logic"
"blackforestbytes.com/simplecloudnotifier/models" "blackforestbytes.com/simplecloudnotifier/models"
"database/sql" "database/sql"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"gogs.mikescher.com/BlackForestBytes/goext/langext" "gogs.mikescher.com/BlackForestBytes/goext/langext"
"gogs.mikescher.com/BlackForestBytes/goext/mathext"
"net/http" "net/http"
) )
@ -820,9 +822,79 @@ func (h APIHandler) UpdateSubscription(g *gin.Context) ginresp.HTTPResponse {
return ctx.FinishSuccess(ginresp.JSON(http.StatusOK, subscription.JSON())) return ctx.FinishSuccess(ginresp.JSON(http.StatusOK, subscription.JSON()))
} }
// ListMessages swaggerdoc
//
// @Summary List all (subscribed) messages
// @Description The next_page_token is an opaque token, the special value "@start" (or empty-string) is the beginning and "@end" is the end
// @Description Simply start the pagination without a next_page_token and get the next page by calling this endpoint with the returned next_page_token of the last query
// @Description If there are no more entries the token "@end" will be returned
// @Description By default we return long messages with a trimmed body, if trimmed=false is supplied we return full messages (this reduces the max page_size)
// @ID api-messages-list
//
// @Param query_data query handler.ListMessages.query false " "
//
// @Success 200 {object} handler.ListMessages.response
// @Failure 400 {object} ginresp.apiError
// @Failure 401 {object} ginresp.apiError
// @Failure 404 {object} ginresp.apiError
// @Failure 500 {object} ginresp.apiError
//
// @Router /api-v2/messages [GET]
func (h APIHandler) ListMessages(g *gin.Context) ginresp.HTTPResponse { func (h APIHandler) ListMessages(g *gin.Context) ginresp.HTTPResponse {
//also update last_read type query struct {
return ginresp.NotImplemented() //TODO PageSize *int `form:"page_size"`
NextPageToken *string `form:"next_page_token"`
Filter *string `form:"filter"`
Trimmed *bool `form:"trimmed"`
}
type response struct {
Messages []models.MessageJSON `json:"messages"`
NextPageToken string `json:"next_page_token"`
PageSize int `json:"page_size"`
}
var q query
ctx, errResp := h.app.StartRequest(g, nil, &q, nil)
if errResp != nil {
return *errResp
}
defer ctx.Cancel()
trimmed := langext.Coalesce(q.Trimmed, true)
maxPageSize := langext.Conditional(trimmed, 16, 256)
pageSize := mathext.Clamp(langext.Coalesce(q.PageSize, 64), 1, maxPageSize)
if permResp := ctx.CheckPermissionRead(); permResp != nil {
return *permResp
}
userid := *ctx.GetPermissionUserID()
tok, err := cursortoken.Decode(langext.Coalesce(q.NextPageToken, ""))
if err != nil {
return ginresp.InternAPIError(500, apierr.PAGETOKEN_ERROR, "Failed to decode next_page_token", err)
}
err = h.database.UpdateUserLastRead(ctx, userid)
if err != nil {
return ginresp.InternAPIError(500, apierr.DATABASE_ERROR, "Failed to update last-read", err)
}
messages, npt, err := h.database.ListMessages(ctx, userid, pageSize, tok)
if err != nil {
return ginresp.InternAPIError(500, apierr.DATABASE_ERROR, "Failed to query messages", err)
}
var res []models.MessageJSON
if trimmed {
res = langext.ArrMap(messages, func(v models.Message) models.MessageJSON { return v.TrimmedJSON() })
} else {
res = langext.ArrMap(messages, func(v models.Message) models.MessageJSON { return v.FullJSON() })
}
return ctx.FinishSuccess(ginresp.JSON(http.StatusOK, response{Messages: res, NextPageToken: npt.Token(), PageSize: pageSize}))
} }
// GetMessage swaggerdoc // GetMessage swaggerdoc
@ -831,7 +903,7 @@ func (h APIHandler) ListMessages(g *gin.Context) ginresp.HTTPResponse {
// @Description The user must either own the message and request the resource with the READ or ADMIN Key // @Description The user must either own the message and request the resource with the READ or ADMIN Key
// @Description Or the user must subscribe to the corresponding channel (and be confirmed) and request the resource with the READ or ADMIN Key // @Description Or the user must subscribe to the corresponding channel (and be confirmed) and request the resource with the READ or ADMIN Key
// @Description The returned message is never trimmed // @Description The returned message is never trimmed
// @ID api-message-get // @ID api-messages-get
// //
// @Param mid path int true "SCNMessageID" // @Param mid path int true "SCNMessageID"
// //
@ -900,7 +972,7 @@ func (h APIHandler) GetMessage(g *gin.Context) ginresp.HTTPResponse {
// //
// @Summary Delete a single message // @Summary Delete a single message
// @Description The user must own the message and request the resource with the ADMIN Key // @Description The user must own the message and request the resource with the ADMIN Key
// @ID api-message-delete // @ID api-messages-delete
// //
// @Param mid path int true "SCNMessageID" // @Param mid path int true "SCNMessageID"
// //

View File

@ -0,0 +1,133 @@
package cursortoken
import (
"encoding/base32"
"encoding/json"
"errors"
"strings"
"time"
)
type Mode string
const (
CTMStart = "START"
CTMNormal = "NORMAL"
CTMEnd = "END"
)
type CursorToken struct {
Mode Mode
Timestamp int64
Id int64
Direction string
}
type cursorTokenSerialize struct {
Timestamp *int64 `json:"ts,omitempty"`
Id *int64 `json:"id,omitempty"`
Direction *string `json:"dir,omitempty"`
}
func Start() CursorToken {
return CursorToken{
Mode: CTMStart,
Timestamp: 0,
Id: 0,
Direction: "",
}
}
func End() CursorToken {
return CursorToken{
Mode: CTMEnd,
Timestamp: 0,
Id: 0,
Direction: "",
}
}
func Normal(ts time.Time, id int64, dir string) CursorToken {
return CursorToken{
Mode: CTMNormal,
Timestamp: ts.UnixMilli(),
Id: id,
Direction: dir,
}
}
func (c *CursorToken) Token() string {
if c.Mode == CTMStart {
return "@start"
}
if c.Mode == CTMEnd {
return "@end"
}
// We kinda manually implement omitempty for the CursorToken here
// because omitempty does not work for time.Time and otherwise we would always
// get weird time values when decoding a token that initially didn't have an Timestamp set
// For this usecase we treat Unix=0 as an empty timestamp
sertok := cursorTokenSerialize{}
if c.Id != 0 {
sertok.Id = &c.Id
}
if c.Timestamp != 0 {
sertok.Timestamp = &c.Timestamp
}
if c.Direction != "" {
sertok.Direction = &c.Direction
}
body, err := json.Marshal(sertok)
if err != nil {
panic(err)
}
return "tok_" + base32.StdEncoding.EncodeToString(body)
}
func Decode(tok string) (CursorToken, error) {
if tok == "" {
return Start(), nil
}
if strings.ToLower(tok) == "@start" {
return Start(), nil
}
if strings.ToLower(tok) == "@end" {
return End(), nil
}
if !strings.HasPrefix(tok, "tok_") {
return CursorToken{}, errors.New("could not decode token, missing prefix")
}
body, err := base32.StdEncoding.DecodeString(tok[len("tok_"):])
if err != nil {
return CursorToken{}, err
}
var tokenDeserialize cursorTokenSerialize
err = json.Unmarshal(body, &tokenDeserialize)
if err != nil {
return CursorToken{}, err
}
token := CursorToken{Mode: CTMNormal}
if tokenDeserialize.Timestamp != nil {
token.Timestamp = *tokenDeserialize.Timestamp
}
if tokenDeserialize.Id != nil {
token.Id = *tokenDeserialize.Id
}
if tokenDeserialize.Direction != nil {
token.Direction = *tokenDeserialize.Direction
}
return token, nil
}

View File

@ -2,24 +2,15 @@ package db
import ( import (
scn "blackforestbytes.com/simplecloudnotifier" scn "blackforestbytes.com/simplecloudnotifier"
"blackforestbytes.com/simplecloudnotifier/db/schema"
"context" "context"
"database/sql" "database/sql"
_ "embed"
"errors" "errors"
"fmt" "fmt"
_ "github.com/mattn/go-sqlite3" _ "github.com/mattn/go-sqlite3"
"time" "time"
) )
//go:embed schema_1.ddl
var schema1 string
//go:embed schema_2.ddl
var schema2 string
//go:embed schema_3.ddl
var schema3 string
type Database struct { type Database struct {
db *sql.DB db *sql.DB
} }
@ -37,24 +28,24 @@ func (db *Database) Migrate(ctx context.Context) error {
ctx, cancel := context.WithTimeout(context.Background(), 24*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 24*time.Second)
defer cancel() defer cancel()
schema, err := db.ReadSchema(ctx) currschema, err := db.ReadSchema(ctx)
if schema == 0 { if currschema == 0 {
_, err = db.db.ExecContext(ctx, schema3) _, err = db.db.ExecContext(ctx, schema.Schema3)
if err != nil { if err != nil {
return err return err
} }
return nil return nil
} else if schema == 1 { } else if currschema == 1 {
return errors.New("cannot autom. upgrade schema 1") return errors.New("cannot autom. upgrade schema 1")
} else if schema == 2 { } else if currschema == 2 {
return errors.New("cannot autom. upgrade schema 2") //TODO return errors.New("cannot autom. upgrade schema 2") //TODO
} else if schema == 3 { } else if currschema == 3 {
return nil // current return nil // current
} else { } else {
return errors.New(fmt.Sprintf("Unknown DB schema: %d", schema)) return errors.New(fmt.Sprintf("Unknown DB schema: %d", currschema))
} }
} }

View File

@ -1,8 +1,10 @@
package db package db
import ( import (
"blackforestbytes.com/simplecloudnotifier/db/cursortoken"
"blackforestbytes.com/simplecloudnotifier/models" "blackforestbytes.com/simplecloudnotifier/models"
"database/sql" "database/sql"
"fmt"
"time" "time"
) )
@ -103,3 +105,38 @@ func (db *Database) DeleteMessage(ctx TxContext, scnMessageID int64) error {
return nil return nil
} }
func (db *Database) ListMessages(ctx TxContext, userid int64, pageSize int, inTok cursortoken.CursorToken) ([]models.Message, cursortoken.CursorToken, error) {
tx, err := ctx.GetOrCreateTransaction(db)
if err != nil {
return nil, cursortoken.CursorToken{}, err
}
if inTok.Mode == cursortoken.CTMEnd {
return make([]models.Message, 0), cursortoken.End(), nil
}
pageCond := ""
if inTok.Mode == cursortoken.CTMNormal {
pageCond = fmt.Sprintf("AND ( timestamp_real < %d OR (timestamp_real = %d AND scn_message_id < %d ) )", inTok.Timestamp, inTok.Timestamp, inTok.Id)
}
rows, err := tx.QueryContext(ctx, "SELECT messages.* FROM messages LEFT JOIN subscriptions subs on messages.channel_id = subs.channel_id WHERE subs.subscriber_user_id = ? AND subs.confirmed = 1 "+pageCond+" ORDER BY timestamp_real DESC LIMIT ?",
userid,
pageSize+1)
if err != nil {
return nil, cursortoken.CursorToken{}, err
}
data, err := models.DecodeMessages(rows)
if err != nil {
return nil, cursortoken.CursorToken{}, err
}
if len(data) <= pageSize {
return data, cursortoken.End(), nil
} else {
outToken := cursortoken.Normal(data[pageSize-1].TimestampReal, data[pageSize-1].SCNMessageID, "DESC")
return data[0:pageSize], outToken, nil
}
}

View File

@ -0,0 +1,12 @@
package schema
import _ "embed"
//go:embed schema_1.ddl
var Schema1 string
//go:embed schema_2.ddl
var Schema2 string
//go:embed schema_3.ddl
var Schema3 string

View File

@ -42,6 +42,18 @@ func (ac *AppContext) CheckPermissionUserRead(userid int64) *ginresp.HTTPRespons
return langext.Ptr(respoNotAuthorized) return langext.Ptr(respoNotAuthorized)
} }
func (ac *AppContext) CheckPermissionRead() *ginresp.HTTPResponse {
p := ac.permissions
if p.UserID != nil && p.KeyType == PermKeyTypeUserRead {
return nil
}
if p.UserID != nil && p.KeyType == PermKeyTypeUserAdmin {
return nil
}
return langext.Ptr(respoNotAuthorized)
}
func (ac *AppContext) CheckPermissionUserAdmin(userid int64) *ginresp.HTTPResponse { func (ac *AppContext) CheckPermissionUserAdmin(userid int64) *ginresp.HTTPResponse {
p := ac.permissions p := ac.permissions
if p.UserID != nil && *p.UserID == userid && p.KeyType == PermKeyTypeUserAdmin { if p.UserID != nil && *p.UserID == userid && p.KeyType == PermKeyTypeUserAdmin {

View File

@ -11,16 +11,22 @@
"paths": { "paths": {
"/": { "/": {
"post": { "post": {
"description": "All parameter can be set via query-parameter or the json body. Only UserID, UserKey and Title are required",
"summary": "Send a new message", "summary": "Send a new message",
"parameters": [ "parameters": [
{ {
"type": "string", "type": "string",
"name": "message_content", "name": "chanKey",
"in": "query" "in": "query"
}, },
{ {
"type": "string", "type": "string",
"name": "message_title", "name": "channel",
"in": "query"
},
{
"type": "string",
"name": "content",
"in": "query" "in": "query"
}, },
{ {
@ -29,25 +35,30 @@
"in": "query" "in": "query"
}, },
{ {
"type": "integer", "type": "number",
"name": "sendTimestamp", "name": "sendTimestamp",
"in": "query" "in": "query"
}, },
{
"type": "string",
"name": "title",
"in": "query"
},
{
"type": "integer",
"name": "userID",
"in": "query"
},
{
"type": "string",
"name": "userKey",
"in": "query"
},
{ {
"type": "string", "type": "string",
"name": "userMessageID", "name": "userMessageID",
"in": "query" "in": "query"
}, },
{
"type": "string",
"name": "user_id",
"in": "query"
},
{
"type": "string",
"name": "user_key",
"in": "query"
},
{ {
"description": " ", "description": " ",
"name": "post_body", "name": "post_body",
@ -61,7 +72,122 @@
"200": { "200": {
"description": "OK", "description": "OK",
"schema": { "schema": {
"$ref": "#/definitions/models.ClientJSON" "$ref": "#/definitions/handler.SendMessage.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"
}
}
}
}
},
"/api-v2/messages": {
"get": {
"description": "The next_page_token is an opaque token, the special value \"@start\" (or empty-string) is the beginning and \"@end\" is the end\nSimply start the pagination without a next_page_token and get the next page by calling this endpoint with the returned next_page_token of the last query\nIf there are no more entries the token \"@end\" will be returned\nBy default we return long messages with a trimmed body, if trimmed=false is supplied we return full messages (this reduces the max page_size)",
"summary": "List all (subscribed) messages",
"operationId": "api-messages-list",
"parameters": [
{
"type": "string",
"name": "filter",
"in": "query"
},
{
"type": "string",
"name": "nextPageToken",
"in": "query"
},
{
"type": "integer",
"name": "pageSize",
"in": "query"
},
{
"type": "boolean",
"name": "trimmed",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/handler.ListMessages.response"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/ginresp.apiError"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/ginresp.apiError"
}
},
"404": {
"description": "Not Found",
"schema": {
"$ref": "#/definitions/ginresp.apiError"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/ginresp.apiError"
}
}
}
}
},
"/api-v2/messages/{mid}": {
"patch": {
"description": "The user must own the message and request the resource with the ADMIN Key",
"summary": "Delete a single message",
"operationId": "api-messages-delete",
"parameters": [
{
"type": "integer",
"description": "SCNMessageID",
"name": "mid",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/models.MessageJSON"
} }
}, },
"400": { "400": {
@ -221,6 +347,161 @@
} }
} }
}, },
"/api-v2/users/{uid}/channels": {
"get": {
"summary": "List all channels of a user",
"operationId": "api-channels-list",
"parameters": [
{
"type": "integer",
"description": "UserID",
"name": "uid",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/handler.ListChannels.response"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/ginresp.apiError"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/ginresp.apiError"
}
},
"404": {
"description": "Not Found",
"schema": {
"$ref": "#/definitions/ginresp.apiError"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/ginresp.apiError"
}
}
}
}
},
"/api-v2/users/{uid}/channels/{cid}": {
"get": {
"summary": "List all channels of a user",
"operationId": "api-channels-get",
"parameters": [
{
"type": "integer",
"description": "UserID",
"name": "uid",
"in": "path",
"required": true
},
{
"type": "integer",
"description": "ChannelID",
"name": "cid",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/models.ChannelJSON"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/ginresp.apiError"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/ginresp.apiError"
}
},
"404": {
"description": "Not Found",
"schema": {
"$ref": "#/definitions/ginresp.apiError"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/ginresp.apiError"
}
}
}
}
},
"/api-v2/users/{uid}/channels/{cid}/subscriptions": {
"get": {
"summary": "List all subscriptions of a channel",
"operationId": "api-chan-subscriptions-list",
"parameters": [
{
"type": "integer",
"description": "UserID",
"name": "uid",
"in": "path",
"required": true
},
{
"type": "integer",
"description": "ChannelID",
"name": "cid",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/handler.ListChannelSubscriptions.response"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/ginresp.apiError"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/ginresp.apiError"
}
},
"404": {
"description": "Not Found",
"schema": {
"$ref": "#/definitions/ginresp.apiError"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/ginresp.apiError"
}
}
}
}
},
"/api-v2/users/{uid}/clients": { "/api-v2/users/{uid}/clients": {
"get": { "get": {
"summary": "List all clients", "summary": "List all clients",
@ -238,7 +519,7 @@
"200": { "200": {
"description": "OK", "description": "OK",
"schema": { "schema": {
"$ref": "#/definitions/handler.ListClients.result" "$ref": "#/definitions/handler.ListClients.response"
} }
}, },
"400": { "400": {
@ -374,6 +655,269 @@
} }
} }
}, },
"/api-v2/users/{uid}/subscriptions": {
"get": {
"summary": "List all channels of a user",
"operationId": "api-user-subscriptions-list",
"parameters": [
{
"type": "integer",
"description": "UserID",
"name": "uid",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/handler.ListUserSubscriptions.response"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/ginresp.apiError"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/ginresp.apiError"
}
},
"404": {
"description": "Not Found",
"schema": {
"$ref": "#/definitions/ginresp.apiError"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/ginresp.apiError"
}
}
}
},
"post": {
"summary": "Creare/Request a subscription",
"operationId": "api-subscriptions-create",
"parameters": [
{
"type": "integer",
"description": "UserID",
"name": "uid",
"in": "path",
"required": true
},
{
"type": "string",
"name": "chanSubscribeKey",
"in": "query"
},
{
"description": " ",
"name": "post_data",
"in": "body",
"schema": {
"$ref": "#/definitions/handler.CreateSubscription.body"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/models.SubscriptionJSON"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/ginresp.apiError"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/ginresp.apiError"
}
},
"404": {
"description": "Not Found",
"schema": {
"$ref": "#/definitions/ginresp.apiError"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/ginresp.apiError"
}
}
}
}
},
"/api-v2/users/{uid}/subscriptions/{sid}": {
"get": {
"summary": "Get a single subscription",
"operationId": "api-subscriptions-get",
"parameters": [
{
"type": "integer",
"description": "UserID",
"name": "uid",
"in": "path",
"required": true
},
{
"type": "integer",
"description": "SubscriptionID",
"name": "sid",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/models.SubscriptionJSON"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/ginresp.apiError"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/ginresp.apiError"
}
},
"404": {
"description": "Not Found",
"schema": {
"$ref": "#/definitions/ginresp.apiError"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/ginresp.apiError"
}
}
}
},
"delete": {
"summary": "Cancel (delete) subscription",
"operationId": "api-subscriptions-delete",
"parameters": [
{
"type": "integer",
"description": "UserID",
"name": "uid",
"in": "path",
"required": true
},
{
"type": "integer",
"description": "SubscriptionID",
"name": "sid",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/models.SubscriptionJSON"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/ginresp.apiError"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/ginresp.apiError"
}
},
"404": {
"description": "Not Found",
"schema": {
"$ref": "#/definitions/ginresp.apiError"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/ginresp.apiError"
}
}
}
},
"patch": {
"summary": "Update a subscription (e.g. confirm)",
"operationId": "api-subscriptions-update",
"parameters": [
{
"type": "integer",
"description": "UserID",
"name": "uid",
"in": "path",
"required": true
},
{
"type": "integer",
"description": "SubscriptionID",
"name": "sid",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/models.SubscriptionJSON"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/ginresp.apiError"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/ginresp.apiError"
}
},
"404": {
"description": "Not Found",
"schema": {
"$ref": "#/definitions/ginresp.apiError"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/ginresp.apiError"
}
}
}
}
},
"/api/ack.php": { "/api/ack.php": {
"get": { "get": {
"summary": "Acknowledge that a message was received", "summary": "Acknowledge that a message was received",
@ -773,16 +1317,22 @@
}, },
"/send": { "/send": {
"post": { "post": {
"description": "All parameter can be set via query-parameter or the json body. Only UserID, UserKey and Title are required",
"summary": "Send a new message", "summary": "Send a new message",
"parameters": [ "parameters": [
{ {
"type": "string", "type": "string",
"name": "message_content", "name": "chanKey",
"in": "query" "in": "query"
}, },
{ {
"type": "string", "type": "string",
"name": "message_title", "name": "channel",
"in": "query"
},
{
"type": "string",
"name": "content",
"in": "query" "in": "query"
}, },
{ {
@ -791,25 +1341,30 @@
"in": "query" "in": "query"
}, },
{ {
"type": "integer", "type": "number",
"name": "sendTimestamp", "name": "sendTimestamp",
"in": "query" "in": "query"
}, },
{
"type": "string",
"name": "title",
"in": "query"
},
{
"type": "integer",
"name": "userID",
"in": "query"
},
{
"type": "string",
"name": "userKey",
"in": "query"
},
{ {
"type": "string", "type": "string",
"name": "userMessageID", "name": "userMessageID",
"in": "query" "in": "query"
}, },
{
"type": "string",
"name": "user_id",
"in": "query"
},
{
"type": "string",
"name": "user_key",
"in": "query"
},
{ {
"description": " ", "description": " ",
"name": "post_body", "name": "post_body",
@ -823,7 +1378,7 @@
"200": { "200": {
"description": "OK", "description": "OK",
"schema": { "schema": {
"$ref": "#/definitions/models.ClientJSON" "$ref": "#/definitions/handler.SendMessage.response"
} }
}, },
"400": { "400": {
@ -838,6 +1393,12 @@
"$ref": "#/definitions/ginresp.apiError" "$ref": "#/definitions/ginresp.apiError"
} }
}, },
"403": {
"description": "Forbidden",
"schema": {
"$ref": "#/definitions/ginresp.apiError"
}
},
"404": { "404": {
"description": "Not Found", "description": "Not Found",
"schema": { "schema": {
@ -907,6 +1468,17 @@
} }
} }
}, },
"handler.CreateSubscription.body": {
"type": "object",
"properties": {
"channel": {
"type": "string"
},
"channelOwnerUserID": {
"type": "integer"
}
}
},
"handler.CreateUser.body": { "handler.CreateUser.body": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -1001,7 +1573,29 @@
} }
} }
}, },
"handler.ListClients.result": { "handler.ListChannelSubscriptions.response": {
"type": "object",
"properties": {
"subscriptions": {
"type": "array",
"items": {
"$ref": "#/definitions/models.SubscriptionJSON"
}
}
}
},
"handler.ListChannels.response": {
"type": "object",
"properties": {
"channels": {
"type": "array",
"items": {
"$ref": "#/definitions/models.ChannelJSON"
}
}
}
},
"handler.ListClients.response": {
"type": "object", "type": "object",
"properties": { "properties": {
"clients": { "clients": {
@ -1012,6 +1606,34 @@
} }
} }
}, },
"handler.ListMessages.response": {
"type": "object",
"properties": {
"messages": {
"type": "array",
"items": {
"$ref": "#/definitions/models.MessageJSON"
}
},
"next_page_token": {
"type": "string"
},
"page_size": {
"type": "integer"
}
}
},
"handler.ListUserSubscriptions.response": {
"type": "object",
"properties": {
"subscriptions": {
"type": "array",
"items": {
"$ref": "#/definitions/models.SubscriptionJSON"
}
}
}
},
"handler.Register.response": { "handler.Register.response": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -1061,29 +1683,70 @@
"handler.SendMessage.body": { "handler.SendMessage.body": {
"type": "object", "type": "object",
"properties": { "properties": {
"chanKey": {
"type": "string"
},
"channel": {
"type": "string"
},
"message_content": { "message_content": {
"type": "string" "type": "string"
}, },
"message_title": { "message_title": {
"type": "string" "type": "string"
}, },
"msg_id": {
"type": "string"
},
"priority": { "priority": {
"type": "integer" "type": "integer"
}, },
"sendTimestamp": { "timestamp": {
"type": "integer" "type": "number"
},
"userMessageID": {
"type": "string"
}, },
"user_id": { "user_id": {
"type": "string" "type": "integer"
}, },
"user_key": { "user_key": {
"type": "string" "type": "string"
} }
} }
}, },
"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": { "handler.Update.response": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -1172,6 +1835,35 @@
} }
} }
}, },
"models.ChannelJSON": {
"type": "object",
"properties": {
"channel_id": {
"type": "integer"
},
"messages_sent": {
"type": "integer"
},
"name": {
"type": "string"
},
"owner_user_id": {
"type": "integer"
},
"send_key": {
"type": "string"
},
"subscribe_key": {
"type": "string"
},
"timestamp_created": {
"type": "string"
},
"timestamp_last_sent": {
"type": "string"
}
}
},
"models.ClientJSON": { "models.ClientJSON": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -1221,6 +1913,44 @@
} }
} }
}, },
"models.MessageJSON": {
"type": "object",
"properties": {
"body": {
"type": "string"
},
"channel_id": {
"type": "integer"
},
"channel_name": {
"type": "string"
},
"owner_user_id": {
"type": "integer"
},
"priority": {
"type": "integer"
},
"scn_message_id": {
"type": "integer"
},
"sender_user_id": {
"type": "integer"
},
"timestamp": {
"type": "string"
},
"title": {
"type": "string"
},
"trimmed": {
"type": "boolean"
},
"usr_message_id": {
"type": "string"
}
}
},
"models.ShortCompatMessage": { "models.ShortCompatMessage": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -1247,6 +1977,32 @@
} }
} }
}, },
"models.SubscriptionJSON": {
"type": "object",
"properties": {
"channel_id": {
"type": "integer"
},
"channel_name": {
"type": "string"
},
"channel_owner_user_id": {
"type": "integer"
},
"confirmed": {
"type": "boolean"
},
"subscriber_user_id": {
"type": "integer"
},
"subscription_id": {
"type": "integer"
},
"timestamp_created": {
"type": "string"
}
}
},
"models.UserJSON": { "models.UserJSON": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -1259,12 +2015,12 @@
"messages_sent": { "messages_sent": {
"type": "integer" "type": "integer"
}, },
"quota_day": { "quota_used": {
"type": "string"
},
"quota_today": {
"type": "integer" "type": "integer"
}, },
"quota_used_day": {
"type": "string"
},
"read_key": { "read_key": {
"type": "string" "type": "string"
}, },

View File

@ -34,6 +34,13 @@ definitions:
fcm_token: fcm_token:
type: string type: string
type: object type: object
handler.CreateSubscription.body:
properties:
channel:
type: string
channelOwnerUserID:
type: integer
type: object
handler.CreateUser.body: handler.CreateUser.body:
properties: properties:
agent_model: agent_model:
@ -95,13 +102,45 @@ definitions:
user_key: user_key:
type: string type: string
type: object type: object
handler.ListClients.result: handler.ListChannelSubscriptions.response:
properties:
subscriptions:
items:
$ref: '#/definitions/models.SubscriptionJSON'
type: array
type: object
handler.ListChannels.response:
properties:
channels:
items:
$ref: '#/definitions/models.ChannelJSON'
type: array
type: object
handler.ListClients.response:
properties: properties:
clients: clients:
items: items:
$ref: '#/definitions/models.ClientJSON' $ref: '#/definitions/models.ClientJSON'
type: array type: array
type: object type: object
handler.ListMessages.response:
properties:
messages:
items:
$ref: '#/definitions/models.MessageJSON'
type: array
next_page_token:
type: string
page_size:
type: integer
type: object
handler.ListUserSubscriptions.response:
properties:
subscriptions:
items:
$ref: '#/definitions/models.SubscriptionJSON'
type: array
type: object
handler.Register.response: handler.Register.response:
properties: properties:
is_pro: is_pro:
@ -134,20 +173,47 @@ definitions:
type: object type: object
handler.SendMessage.body: handler.SendMessage.body:
properties: properties:
chanKey:
type: string
channel:
type: string
message_content: message_content:
type: string type: string
message_title: message_title:
type: string type: string
msg_id:
type: string
priority: priority:
type: integer type: integer
sendTimestamp: timestamp:
type: integer type: number
user_id: user_id:
type: string type: integer
user_key: user_key:
type: string type: string
userMessageID: type: object
handler.SendMessage.response:
properties:
errhighlight:
type: integer
error:
type: integer
is_pro:
type: boolean
message:
type: string 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 type: object
handler.Update.response: handler.Update.response:
properties: properties:
@ -206,6 +272,25 @@ definitions:
uri: uri:
type: string type: string
type: object type: object
models.ChannelJSON:
properties:
channel_id:
type: integer
messages_sent:
type: integer
name:
type: string
owner_user_id:
type: integer
send_key:
type: string
subscribe_key:
type: string
timestamp_created:
type: string
timestamp_last_sent:
type: string
type: object
models.ClientJSON: models.ClientJSON:
properties: properties:
agent_model: agent_model:
@ -238,6 +323,31 @@ definitions:
usr_msg_id: usr_msg_id:
type: string type: string
type: object type: object
models.MessageJSON:
properties:
body:
type: string
channel_id:
type: integer
channel_name:
type: string
owner_user_id:
type: integer
priority:
type: integer
scn_message_id:
type: integer
sender_user_id:
type: integer
timestamp:
type: string
title:
type: string
trimmed:
type: boolean
usr_message_id:
type: string
type: object
models.ShortCompatMessage: models.ShortCompatMessage:
properties: properties:
body: body:
@ -255,6 +365,23 @@ definitions:
usr_msg_id: usr_msg_id:
type: string type: string
type: object type: object
models.SubscriptionJSON:
properties:
channel_id:
type: integer
channel_name:
type: string
channel_owner_user_id:
type: integer
confirmed:
type: boolean
subscriber_user_id:
type: integer
subscription_id:
type: integer
timestamp_created:
type: string
type: object
models.UserJSON: models.UserJSON:
properties: properties:
admin_key: admin_key:
@ -263,10 +390,10 @@ definitions:
type: boolean type: boolean
messages_sent: messages_sent:
type: integer type: integer
quota_day: quota_used:
type: string
quota_today:
type: integer type: integer
quota_used_day:
type: string
read_key: read_key:
type: string type: string
send_key: send_key:
@ -291,28 +418,36 @@ info:
paths: paths:
/: /:
post: post:
description: All parameter can be set via query-parameter or the json body.
Only UserID, UserKey and Title are required
parameters: parameters:
- in: query - in: query
name: message_content name: chanKey
type: string type: string
- in: query - in: query
name: message_title name: channel
type: string
- in: query
name: content
type: string type: string
- in: query - in: query
name: priority name: priority
type: integer type: integer
- in: query - in: query
name: sendTimestamp name: sendTimestamp
type: number
- in: query
name: title
type: string
- in: query
name: userID
type: integer type: integer
- in: query
name: userKey
type: string
- in: query - in: query
name: userMessageID name: userMessageID
type: string type: string
- in: query
name: user_id
type: string
- in: query
name: user_key
type: string
- description: ' ' - description: ' '
in: body in: body
name: post_body name: post_body
@ -322,7 +457,54 @@ paths:
"200": "200":
description: OK description: OK
schema: schema:
$ref: '#/definitions/models.ClientJSON' $ref: '#/definitions/handler.SendMessage.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
/api-v2/messages:
get:
description: |-
The next_page_token is an opaque token, the special value "@start" (or empty-string) is the beginning and "@end" is the end
Simply start the pagination without a next_page_token and get the next page by calling this endpoint with the returned next_page_token of the last query
If there are no more entries the token "@end" will be returned
By default we return long messages with a trimmed body, if trimmed=false is supplied we return full messages (this reduces the max page_size)
operationId: api-messages-list
parameters:
- in: query
name: filter
type: string
- in: query
name: nextPageToken
type: string
- in: query
name: pageSize
type: integer
- in: query
name: trimmed
type: boolean
responses:
"200":
description: OK
schema:
$ref: '#/definitions/handler.ListMessages.response'
"400": "400":
description: Bad Request description: Bad Request
schema: schema:
@ -339,7 +521,40 @@ paths:
description: Internal Server Error description: Internal Server Error
schema: schema:
$ref: '#/definitions/ginresp.apiError' $ref: '#/definitions/ginresp.apiError'
summary: Send a new message summary: List all (subscribed) messages
/api-v2/messages/{mid}:
patch:
description: The user must own the message and request the resource with the
ADMIN Key
operationId: api-messages-delete
parameters:
- description: SCNMessageID
in: path
name: mid
required: true
type: integer
responses:
"200":
description: OK
schema:
$ref: '#/definitions/models.MessageJSON'
"400":
description: Bad Request
schema:
$ref: '#/definitions/ginresp.apiError'
"401":
description: Unauthorized
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: Delete a single message
/api-v2/users/: /api-v2/users/:
post: post:
operationId: api-user-create operationId: api-user-create
@ -425,6 +640,109 @@ paths:
schema: schema:
$ref: '#/definitions/ginresp.apiError' $ref: '#/definitions/ginresp.apiError'
summary: (Partially) update a user summary: (Partially) update a user
/api-v2/users/{uid}/channels:
get:
operationId: api-channels-list
parameters:
- description: UserID
in: path
name: uid
required: true
type: integer
responses:
"200":
description: OK
schema:
$ref: '#/definitions/handler.ListChannels.response'
"400":
description: Bad Request
schema:
$ref: '#/definitions/ginresp.apiError'
"401":
description: Unauthorized
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: List all channels of a user
/api-v2/users/{uid}/channels/{cid}:
get:
operationId: api-channels-get
parameters:
- description: UserID
in: path
name: uid
required: true
type: integer
- description: ChannelID
in: path
name: cid
required: true
type: integer
responses:
"200":
description: OK
schema:
$ref: '#/definitions/models.ChannelJSON'
"400":
description: Bad Request
schema:
$ref: '#/definitions/ginresp.apiError'
"401":
description: Unauthorized
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: List all channels of a user
/api-v2/users/{uid}/channels/{cid}/subscriptions:
get:
operationId: api-chan-subscriptions-list
parameters:
- description: UserID
in: path
name: uid
required: true
type: integer
- description: ChannelID
in: path
name: cid
required: true
type: integer
responses:
"200":
description: OK
schema:
$ref: '#/definitions/handler.ListChannelSubscriptions.response'
"400":
description: Bad Request
schema:
$ref: '#/definitions/ginresp.apiError'
"401":
description: Unauthorized
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: List all subscriptions of a channel
/api-v2/users/{uid}/clients: /api-v2/users/{uid}/clients:
get: get:
operationId: api-clients-list operationId: api-clients-list
@ -438,7 +756,7 @@ paths:
"200": "200":
description: OK description: OK
schema: schema:
$ref: '#/definitions/handler.ListClients.result' $ref: '#/definitions/handler.ListClients.response'
"400": "400":
description: Bad Request description: Bad Request
schema: schema:
@ -527,6 +845,181 @@ paths:
schema: schema:
$ref: '#/definitions/ginresp.apiError' $ref: '#/definitions/ginresp.apiError'
summary: Get a single clients summary: Get a single clients
/api-v2/users/{uid}/subscriptions:
get:
operationId: api-user-subscriptions-list
parameters:
- description: UserID
in: path
name: uid
required: true
type: integer
responses:
"200":
description: OK
schema:
$ref: '#/definitions/handler.ListUserSubscriptions.response'
"400":
description: Bad Request
schema:
$ref: '#/definitions/ginresp.apiError'
"401":
description: Unauthorized
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: List all channels of a user
post:
operationId: api-subscriptions-create
parameters:
- description: UserID
in: path
name: uid
required: true
type: integer
- in: query
name: chanSubscribeKey
type: string
- description: ' '
in: body
name: post_data
schema:
$ref: '#/definitions/handler.CreateSubscription.body'
responses:
"200":
description: OK
schema:
$ref: '#/definitions/models.SubscriptionJSON'
"400":
description: Bad Request
schema:
$ref: '#/definitions/ginresp.apiError'
"401":
description: Unauthorized
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: Creare/Request a subscription
/api-v2/users/{uid}/subscriptions/{sid}:
delete:
operationId: api-subscriptions-delete
parameters:
- description: UserID
in: path
name: uid
required: true
type: integer
- description: SubscriptionID
in: path
name: sid
required: true
type: integer
responses:
"200":
description: OK
schema:
$ref: '#/definitions/models.SubscriptionJSON'
"400":
description: Bad Request
schema:
$ref: '#/definitions/ginresp.apiError'
"401":
description: Unauthorized
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: Cancel (delete) subscription
get:
operationId: api-subscriptions-get
parameters:
- description: UserID
in: path
name: uid
required: true
type: integer
- description: SubscriptionID
in: path
name: sid
required: true
type: integer
responses:
"200":
description: OK
schema:
$ref: '#/definitions/models.SubscriptionJSON'
"400":
description: Bad Request
schema:
$ref: '#/definitions/ginresp.apiError'
"401":
description: Unauthorized
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: Get a single subscription
patch:
operationId: api-subscriptions-update
parameters:
- description: UserID
in: path
name: uid
required: true
type: integer
- description: SubscriptionID
in: path
name: sid
required: true
type: integer
responses:
"200":
description: OK
schema:
$ref: '#/definitions/models.SubscriptionJSON'
"400":
description: Bad Request
schema:
$ref: '#/definitions/ginresp.apiError'
"401":
description: Unauthorized
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: Update a subscription (e.g. confirm)
/api/ack.php: /api/ack.php:
get: get:
operationId: compat-ack operationId: compat-ack
@ -790,28 +1283,36 @@ paths:
$ref: '#/definitions/ginresp.apiError' $ref: '#/definitions/ginresp.apiError'
/send: /send:
post: post:
description: All parameter can be set via query-parameter or the json body.
Only UserID, UserKey and Title are required
parameters: parameters:
- in: query - in: query
name: message_content name: chanKey
type: string type: string
- in: query - in: query
name: message_title name: channel
type: string
- in: query
name: content
type: string type: string
- in: query - in: query
name: priority name: priority
type: integer type: integer
- in: query - in: query
name: sendTimestamp name: sendTimestamp
type: number
- in: query
name: title
type: string
- in: query
name: userID
type: integer type: integer
- in: query
name: userKey
type: string
- in: query - in: query
name: userMessageID name: userMessageID
type: string type: string
- in: query
name: user_id
type: string
- in: query
name: user_key
type: string
- description: ' ' - description: ' '
in: body in: body
name: post_body name: post_body
@ -821,7 +1322,7 @@ paths:
"200": "200":
description: OK description: OK
schema: schema:
$ref: '#/definitions/models.ClientJSON' $ref: '#/definitions/handler.SendMessage.response'
"400": "400":
description: Bad Request description: Bad Request
schema: schema:
@ -830,6 +1331,10 @@ paths:
description: Unauthorized description: Unauthorized
schema: schema:
$ref: '#/definitions/ginresp.apiError' $ref: '#/definitions/ginresp.apiError'
"403":
description: Forbidden
schema:
$ref: '#/definitions/ginresp.apiError'
"404": "404":
description: Not Found description: Not Found
schema: schema: