Selector param for ListChannels()

This commit is contained in:
Mike Schwörer 2022-11-20 21:15:06 +01:00
parent d46601be5c
commit e8671e8650
Signed by: Mikescher
GPG Key ID: D3C7172E0A70F8CF
6 changed files with 424 additions and 56 deletions

View File

@ -18,6 +18,7 @@ const (
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
INVALID_ENUM_VALUE APIError = 1171
NO_TITLE APIError = 1201 NO_TITLE APIError = 1201
TITLE_TOO_LONG APIError = 1202 TITLE_TOO_LONG APIError = 1202

View File

@ -423,10 +423,17 @@ func (h APIHandler) DeleteClient(g *gin.Context) ginresp.HTTPResponse {
// ListChannels swaggerdoc // ListChannels swaggerdoc
// //
// @Summary List all channels of a user // @Summary List channels of a user (subscribed/owned)
// @Description The possible values for 'selector' are:
// @Description - "owned" Return all channels of the user
// @Description - "subscribed" Return all channels that the user is subscribing to
// @Description - "all" Return channels that the user owns or is subscribing
// @Description - "subscribed_any" Return all channels that the user is subscribing to (even unconfirmed)
// @Description - "all_any" Return channels that the user owns or is subscribing (even unconfirmed)
// @ID api-channels-list // @ID api-channels-list
// //
// @Param uid path int true "UserID" // @Param uid path int true "UserID"
// @Param selector query string true "Filter channels (default: owned)" Enums(owned, subscribed, all, subscribed_any, all_any)
// //
// @Success 200 {object} handler.ListChannels.response // @Success 200 {object} handler.ListChannels.response
// @Failure 400 {object} ginresp.apiError // @Failure 400 {object} ginresp.apiError
@ -439,12 +446,16 @@ func (h APIHandler) ListChannels(g *gin.Context) ginresp.HTTPResponse {
type uri struct { type uri struct {
UserID int64 `uri:"uid"` UserID int64 `uri:"uid"`
} }
type query struct {
Selector *string `form:"selector"`
}
type response struct { type response struct {
Channels []models.ChannelJSON `json:"channels"` Channels []models.ChannelJSON `json:"channels"`
} }
var u uri var u uri
ctx, errResp := h.app.StartRequest(g, &u, nil, nil, nil) var q query
ctx, errResp := h.app.StartRequest(g, &u, &q, nil, nil)
if errResp != nil { if errResp != nil {
return *errResp return *errResp
} }
@ -454,12 +465,43 @@ func (h APIHandler) ListChannels(g *gin.Context) ginresp.HTTPResponse {
return *permResp return *permResp
} }
clients, err := h.database.ListChannels(ctx, u.UserID) sel := strings.ToLower(langext.Coalesce(q.Selector, "owned"))
var res []models.ChannelJSON
if sel == "owned" {
channels, err := h.database.ListChannelsByOwner(ctx, u.UserID)
if err != nil { if err != nil {
return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to query channels", err) return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to query channels", err)
} }
res = langext.ArrMap(channels, func(v models.Channel) models.ChannelJSON { return v.JSON(true) })
res := langext.ArrMap(clients, func(v models.Channel) models.ChannelJSON { return v.JSON() }) } else if sel == "subscribed_any" {
channels, err := h.database.ListChannelsBySubscriber(ctx, u.UserID, false)
if err != nil {
return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to query channels", err)
}
res = langext.ArrMap(channels, func(v models.Channel) models.ChannelJSON { return v.JSON(false) })
} else if sel == "all_any" {
channels, err := h.database.ListChannelsByAccess(ctx, u.UserID, false)
if err != nil {
return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to query channels", err)
}
res = langext.ArrMap(channels, func(v models.Channel) models.ChannelJSON { return v.JSON(false) })
} else if sel == "subscribed" {
channels, err := h.database.ListChannelsBySubscriber(ctx, u.UserID, true)
if err != nil {
return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to query channels", err)
}
res = langext.ArrMap(channels, func(v models.Channel) models.ChannelJSON { return v.JSON(false) })
} else if sel == "all" {
channels, err := h.database.ListChannelsByAccess(ctx, u.UserID, true)
if err != nil {
return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to query channels", err)
}
res = langext.ArrMap(channels, func(v models.Channel) models.ChannelJSON { return v.JSON(false) })
} else {
return ginresp.APIError(g, 400, apierr.INVALID_ENUM_VALUE, "Invalid value for the [selector] parameter", nil)
}
return ctx.FinishSuccess(ginresp.JSON(http.StatusOK, response{Channels: res})) return ctx.FinishSuccess(ginresp.JSON(http.StatusOK, response{Channels: res}))
} }
@ -504,7 +546,7 @@ func (h APIHandler) GetChannel(g *gin.Context) ginresp.HTTPResponse {
return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to query channel", err) return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to query channel", err)
} }
return ctx.FinishSuccess(ginresp.JSON(http.StatusOK, channel.JSON())) return ctx.FinishSuccess(ginresp.JSON(http.StatusOK, channel.JSON(true)))
} }
// ListChannelMessages swaggerdoc // ListChannelMessages swaggerdoc

View File

@ -6,28 +6,6 @@ import (
"time" "time"
) )
func (db *Database) GetChannelByKey(ctx TxContext, key string) (*models.Channel, error) {
tx, err := ctx.GetOrCreateTransaction(db)
if err != nil {
return nil, err
}
rows, err := tx.QueryContext(ctx, "SELECT * FROM channels WHERE subscribe_key = ? OR send_key = ? LIMIT 1", key, key)
if err != nil {
return nil, err
}
channel, err := models.DecodeChannel(rows)
if err == sql.ErrNoRows {
return nil, nil
}
if err != nil {
return nil, err
}
return &channel, nil
}
func (db *Database) GetChannelByName(ctx TxContext, userid int64, chanName string) (*models.Channel, error) { func (db *Database) GetChannelByName(ctx TxContext, userid int64, chanName string) (*models.Channel, error) {
tx, err := ctx.GetOrCreateTransaction(db) tx, err := ctx.GetOrCreateTransaction(db)
if err != nil { if err != nil {
@ -85,7 +63,7 @@ func (db *Database) CreateChannel(ctx TxContext, userid int64, name string, subs
}, nil }, nil
} }
func (db *Database) ListChannels(ctx TxContext, userid int64) ([]models.Channel, error) { func (db *Database) ListChannelsByOwner(ctx TxContext, userid int64) ([]models.Channel, error) {
tx, err := ctx.GetOrCreateTransaction(db) tx, err := ctx.GetOrCreateTransaction(db)
if err != nil { if err != nil {
return nil, err return nil, err
@ -104,6 +82,56 @@ func (db *Database) ListChannels(ctx TxContext, userid int64) ([]models.Channel,
return data, nil return data, nil
} }
func (db *Database) ListChannelsBySubscriber(ctx TxContext, userid int64, confirmed bool) ([]models.Channel, error) {
tx, err := ctx.GetOrCreateTransaction(db)
if err != nil {
return nil, err
}
confCond := ""
if confirmed {
confCond = " AND sub.confirmed = 1"
}
rows, err := tx.QueryContext(ctx, "SELECT * FROM channels LEFT JOIN subscriptions sub on channels.channel_id = sub.channel_id WHERE sub.subscriber_user_id = ? "+confCond,
userid)
if err != nil {
return nil, err
}
data, err := models.DecodeChannels(rows)
if err != nil {
return nil, err
}
return data, nil
}
func (db *Database) ListChannelsByAccess(ctx TxContext, userid int64, confirmed bool) ([]models.Channel, error) {
tx, err := ctx.GetOrCreateTransaction(db)
if err != nil {
return nil, err
}
confCond := "sub.subscriber_user_id = ?"
if confirmed {
confCond = "(sub.subscriber_user_id = ? AND sub.confirmed = 1)"
}
rows, err := tx.QueryContext(ctx, "SELECT * FROM channels LEFT JOIN subscriptions sub on channels.channel_id = sub.channel_id WHERE owner_user_id = ? OR "+confCond,
userid)
if err != nil {
return nil, err
}
data, err := models.DecodeChannels(rows)
if err != nil {
return nil, err
}
return data, nil
}
func (db *Database) GetChannel(ctx TxContext, userid int64, channelid int64) (models.Channel, error) { func (db *Database) GetChannel(ctx TxContext, userid int64, channelid int64) (models.Channel, error) {
tx, err := ctx.GetOrCreateTransaction(db) tx, err := ctx.GetOrCreateTransaction(db)
if err != nil { if err != nil {

View File

@ -18,13 +18,13 @@ type Channel struct {
MessagesSent int MessagesSent int
} }
func (c Channel) JSON() ChannelJSON { func (c Channel) JSON(includeKey bool) ChannelJSON {
return ChannelJSON{ return ChannelJSON{
ChannelID: c.ChannelID, ChannelID: c.ChannelID,
OwnerUserID: c.OwnerUserID, OwnerUserID: c.OwnerUserID,
Name: c.Name, Name: c.Name,
SubscribeKey: c.SubscribeKey, SubscribeKey: langext.Conditional(includeKey, langext.Ptr(c.SubscribeKey), nil),
SendKey: c.SendKey, SendKey: langext.Conditional(includeKey, langext.Ptr(c.SendKey), nil),
TimestampCreated: c.TimestampCreated.Format(time.RFC3339Nano), TimestampCreated: c.TimestampCreated.Format(time.RFC3339Nano),
TimestampLastSent: timeOptFmt(c.TimestampLastSent, time.RFC3339Nano), TimestampLastSent: timeOptFmt(c.TimestampLastSent, time.RFC3339Nano),
MessagesSent: c.MessagesSent, MessagesSent: c.MessagesSent,
@ -35,8 +35,8 @@ type ChannelJSON struct {
ChannelID int64 `json:"channel_id"` ChannelID int64 `json:"channel_id"`
OwnerUserID int64 `json:"owner_user_id"` OwnerUserID int64 `json:"owner_user_id"`
Name string `json:"name"` Name string `json:"name"`
SubscribeKey string `json:"subscribe_key"` SubscribeKey *string `json:"subscribe_key"` // can be nil, depending on endpoint
SendKey string `json:"send_key"` SendKey *string `json:"send_key"` // can be nil, depending on endpoint
TimestampCreated string `json:"timestamp_created"` TimestampCreated string `json:"timestamp_created"`
TimestampLastSent *string `json:"timestamp_last_sent"` TimestampLastSent *string `json:"timestamp_last_sent"`
MessagesSent int `json:"messages_sent"` MessagesSent int `json:"messages_sent"`

View File

@ -66,6 +66,51 @@
"schema": { "schema": {
"$ref": "#/definitions/handler.SendMessage.body" "$ref": "#/definitions/handler.SendMessage.body"
} }
},
{
"type": "string",
"name": "chan_key",
"in": "formData"
},
{
"type": "string",
"name": "channel",
"in": "formData"
},
{
"type": "string",
"name": "content",
"in": "formData"
},
{
"type": "string",
"name": "msg_id",
"in": "formData"
},
{
"type": "integer",
"name": "priority",
"in": "formData"
},
{
"type": "number",
"name": "timestamp",
"in": "formData"
},
{
"type": "string",
"name": "title",
"in": "formData"
},
{
"type": "integer",
"name": "user_id",
"in": "formData"
},
{
"type": "string",
"name": "user_key",
"in": "formData"
} }
], ],
"responses": { "responses": {
@ -167,6 +212,80 @@
} }
} }
} }
},
"post": {
"description": "This is similar to the main route `POST -\u003e https://scn.blackfrestbytes.com/`\nBut this route can change in the future, for long-living scripts etc. it's better to use the normal POST route",
"summary": "Create a new message",
"operationId": "api-messages-create",
"parameters": [
{
"type": "string",
"name": "chan_key",
"in": "query"
},
{
"type": "string",
"name": "channel",
"in": "query"
},
{
"type": "string",
"name": "content",
"in": "query"
},
{
"type": "string",
"name": "msg_id",
"in": "query"
},
{
"type": "integer",
"name": "priority",
"in": "query"
},
{
"type": "number",
"name": "timestamp",
"in": "query"
},
{
"type": "string",
"name": "title",
"in": "query"
}
],
"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"
}
}
}
} }
}, },
"/api-v2/messages/{mid}": { "/api-v2/messages/{mid}": {
@ -349,6 +468,7 @@
}, },
"/api-v2/users/{uid}/channels": { "/api-v2/users/{uid}/channels": {
"get": { "get": {
"description": "The possible values for 'selector' are:\n- \"owned\" Return all channels of the user\n- \"subscribed\" Return all channels that the user is subscribing to\n- \"all\" Return channels that the user owns or is subscribing\n- \"subscribed_any\" Return all channels that the user is subscribing to (even unconfirmed)\n- \"all_any\" Return channels that the user owns or is subscribing (even unconfirmed)",
"summary": "List all channels of a user", "summary": "List all channels of a user",
"operationId": "api-channels-list", "operationId": "api-channels-list",
"parameters": [ "parameters": [
@ -358,6 +478,20 @@
"name": "uid", "name": "uid",
"in": "path", "in": "path",
"required": true "required": true
},
{
"enum": [
"owned",
"subscribed",
"all",
"subscribed_any",
"all_any"
],
"type": "string",
"description": "Filter channels (default: owned)",
"name": "selector",
"in": "query",
"required": true
} }
], ],
"responses": { "responses": {
@ -1569,6 +1703,51 @@
"schema": { "schema": {
"$ref": "#/definitions/handler.SendMessage.body" "$ref": "#/definitions/handler.SendMessage.body"
} }
},
{
"type": "string",
"name": "chan_key",
"in": "formData"
},
{
"type": "string",
"name": "channel",
"in": "formData"
},
{
"type": "string",
"name": "content",
"in": "formData"
},
{
"type": "string",
"name": "msg_id",
"in": "formData"
},
{
"type": "integer",
"name": "priority",
"in": "formData"
},
{
"type": "number",
"name": "timestamp",
"in": "formData"
},
{
"type": "string",
"name": "title",
"in": "formData"
},
{
"type": "integer",
"name": "user_id",
"in": "formData"
},
{
"type": "string",
"name": "user_key",
"in": "formData"
} }
], ],
"responses": { "responses": {
@ -2049,7 +2228,7 @@
"handler.SendMessage.body": { "handler.SendMessage.body": {
"type": "object", "type": "object",
"properties": { "properties": {
"chanKey": { "chan_key": {
"type": "string" "type": "string"
}, },
"channel": { "channel": {
@ -2225,10 +2404,8 @@
"owner_user_id": { "owner_user_id": {
"type": "integer" "type": "integer"
}, },
"send_key": {
"type": "string"
},
"subscribe_key": { "subscribe_key": {
"description": "can be nil, depending on endpoint",
"type": "string" "type": "string"
}, },
"timestamp_created": { "timestamp_created": {

View File

@ -209,7 +209,7 @@ definitions:
type: object type: object
handler.SendMessage.body: handler.SendMessage.body:
properties: properties:
chanKey: chan_key:
type: string type: string
channel: channel:
type: string type: string
@ -324,9 +324,8 @@ definitions:
type: string type: string
owner_user_id: owner_user_id:
type: integer type: integer
send_key:
type: string
subscribe_key: subscribe_key:
description: can be nil, depending on endpoint
type: string type: string
timestamp_created: timestamp_created:
type: string type: string
@ -480,6 +479,33 @@ paths:
name: post_body name: post_body
schema: schema:
$ref: '#/definitions/handler.SendMessage.body' $ref: '#/definitions/handler.SendMessage.body'
- in: formData
name: chan_key
type: string
- in: formData
name: channel
type: string
- in: formData
name: content
type: string
- in: formData
name: msg_id
type: string
- in: formData
name: priority
type: integer
- in: formData
name: timestamp
type: number
- in: formData
name: title
type: string
- in: formData
name: user_id
type: integer
- in: formData
name: user_key
type: string
responses: responses:
"200": "200":
description: OK description: OK
@ -549,6 +575,55 @@ paths:
schema: schema:
$ref: '#/definitions/ginresp.apiError' $ref: '#/definitions/ginresp.apiError'
summary: List all (subscribed) messages summary: List all (subscribed) messages
post:
description: |-
This is similar to the main route `POST -> https://scn.blackfrestbytes.com/`
But this route can change in the future, for long-living scripts etc. it's better to use the normal POST route
operationId: api-messages-create
parameters:
- in: query
name: chan_key
type: string
- in: query
name: channel
type: string
- in: query
name: content
type: string
- in: query
name: msg_id
type: string
- in: query
name: priority
type: integer
- in: query
name: timestamp
type: number
- in: query
name: title
type: string
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: Create a new message
/api-v2/messages/{mid}: /api-v2/messages/{mid}:
patch: patch:
description: The user must own the message and request the resource with the description: The user must own the message and request the resource with the
@ -669,6 +744,13 @@ paths:
summary: (Partially) update a user summary: (Partially) update a user
/api-v2/users/{uid}/channels: /api-v2/users/{uid}/channels:
get: get:
description: |-
The possible values for 'selector' are:
- "owned" Return all channels of the user
- "subscribed" Return all channels that the user is subscribing to
- "all" Return channels that the user owns or is subscribing
- "subscribed_any" Return all channels that the user is subscribing to (even unconfirmed)
- "all_any" Return channels that the user owns or is subscribing (even unconfirmed)
operationId: api-channels-list operationId: api-channels-list
parameters: parameters:
- description: UserID - description: UserID
@ -676,6 +758,17 @@ paths:
name: uid name: uid
required: true required: true
type: integer type: integer
- description: 'Filter channels (default: owned)'
enum:
- owned
- subscribed
- all
- subscribed_any
- all_any
in: query
name: selector
required: true
type: string
responses: responses:
"200": "200":
description: OK description: OK
@ -1489,6 +1582,33 @@ paths:
name: post_body name: post_body
schema: schema:
$ref: '#/definitions/handler.SendMessage.body' $ref: '#/definitions/handler.SendMessage.body'
- in: formData
name: chan_key
type: string
- in: formData
name: channel
type: string
- in: formData
name: content
type: string
- in: formData
name: msg_id
type: string
- in: formData
name: priority
type: integer
- in: formData
name: timestamp
type: number
- in: formData
name: title
type: string
- in: formData
name: user_id
type: integer
- in: formData
name: user_key
type: string
responses: responses:
"200": "200":
description: OK description: OK