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_BODY_PARAM APIError = 1152
BINDFAIL_URI_PARAM APIError = 1153
INVALID_ENUM_VALUE APIError = 1171
NO_TITLE APIError = 1201
TITLE_TOO_LONG APIError = 1202

View File

@ -423,28 +423,39 @@ func (h APIHandler) DeleteClient(g *gin.Context) ginresp.HTTPResponse {
// ListChannels swaggerdoc
//
// @Summary List all channels of a user
// @ID api-channels-list
// @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
//
// @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
// @Failure 400 {object} ginresp.apiError
// @Failure 401 {object} ginresp.apiError
// @Failure 404 {object} ginresp.apiError
// @Failure 500 {object} ginresp.apiError
// @Success 200 {object} handler.ListChannels.response
// @Failure 400 {object} ginresp.apiError
// @Failure 401 {object} ginresp.apiError
// @Failure 404 {object} ginresp.apiError
// @Failure 500 {object} ginresp.apiError
//
// @Router /api-v2/users/{uid}/channels [GET]
// @Router /api-v2/users/{uid}/channels [GET]
func (h APIHandler) ListChannels(g *gin.Context) ginresp.HTTPResponse {
type uri struct {
UserID int64 `uri:"uid"`
}
type query struct {
Selector *string `form:"selector"`
}
type response struct {
Channels []models.ChannelJSON `json:"channels"`
}
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 {
return *errResp
}
@ -454,12 +465,43 @@ func (h APIHandler) ListChannels(g *gin.Context) ginresp.HTTPResponse {
return *permResp
}
clients, err := h.database.ListChannels(ctx, u.UserID)
if err != nil {
return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to query channels", err)
}
sel := strings.ToLower(langext.Coalesce(q.Selector, "owned"))
res := langext.ArrMap(clients, func(v models.Channel) models.ChannelJSON { return v.JSON() })
var res []models.ChannelJSON
if sel == "owned" {
channels, err := h.database.ListChannelsByOwner(ctx, u.UserID)
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(true) })
} 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}))
}
@ -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 ctx.FinishSuccess(ginresp.JSON(http.StatusOK, channel.JSON()))
return ctx.FinishSuccess(ginresp.JSON(http.StatusOK, channel.JSON(true)))
}
// ListChannelMessages swaggerdoc
@ -1131,11 +1173,11 @@ func (h APIHandler) DeleteMessage(g *gin.Context) ginresp.HTTPResponse {
//
// @Param post_data query handler.CreateMessage.body false " "
//
// @Success 200 {object} models.MessageJSON
// @Failure 400 {object} ginresp.apiError
// @Failure 401 {object} ginresp.apiError
// @Failure 404 {object} ginresp.apiError
// @Failure 500 {object} ginresp.apiError
// @Success 200 {object} models.MessageJSON
// @Failure 400 {object} ginresp.apiError
// @Failure 401 {object} ginresp.apiError
// @Failure 404 {object} ginresp.apiError
// @Failure 500 {object} ginresp.apiError
//
// @Router /api-v2/messages [POST]
func (h APIHandler) CreateMessage(g *gin.Context) ginresp.HTTPResponse {

View File

@ -6,28 +6,6 @@ import (
"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) {
tx, err := ctx.GetOrCreateTransaction(db)
if err != nil {
@ -85,7 +63,7 @@ func (db *Database) CreateChannel(ctx TxContext, userid int64, name string, subs
}, 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)
if err != nil {
return nil, err
@ -104,6 +82,56 @@ func (db *Database) ListChannels(ctx TxContext, userid int64) ([]models.Channel,
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) {
tx, err := ctx.GetOrCreateTransaction(db)
if err != nil {

View File

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

View File

@ -66,6 +66,51 @@
"schema": {
"$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": {
@ -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}": {
@ -349,6 +468,7 @@
},
"/api-v2/users/{uid}/channels": {
"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",
"operationId": "api-channels-list",
"parameters": [
@ -358,6 +478,20 @@
"name": "uid",
"in": "path",
"required": true
},
{
"enum": [
"owned",
"subscribed",
"all",
"subscribed_any",
"all_any"
],
"type": "string",
"description": "Filter channels (default: owned)",
"name": "selector",
"in": "query",
"required": true
}
],
"responses": {
@ -1569,6 +1703,51 @@
"schema": {
"$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": {
@ -2049,7 +2228,7 @@
"handler.SendMessage.body": {
"type": "object",
"properties": {
"chanKey": {
"chan_key": {
"type": "string"
},
"channel": {
@ -2225,10 +2404,8 @@
"owner_user_id": {
"type": "integer"
},
"send_key": {
"type": "string"
},
"subscribe_key": {
"description": "can be nil, depending on endpoint",
"type": "string"
},
"timestamp_created": {

View File

@ -209,7 +209,7 @@ definitions:
type: object
handler.SendMessage.body:
properties:
chanKey:
chan_key:
type: string
channel:
type: string
@ -324,9 +324,8 @@ definitions:
type: string
owner_user_id:
type: integer
send_key:
type: string
subscribe_key:
description: can be nil, depending on endpoint
type: string
timestamp_created:
type: string
@ -480,6 +479,33 @@ paths:
name: post_body
schema:
$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:
"200":
description: OK
@ -549,6 +575,55 @@ paths:
schema:
$ref: '#/definitions/ginresp.apiError'
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}:
patch:
description: The user must own the message and request the resource with the
@ -669,6 +744,13 @@ paths:
summary: (Partially) update a user
/api-v2/users/{uid}/channels:
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
parameters:
- description: UserID
@ -676,6 +758,17 @@ paths:
name: uid
required: true
type: integer
- description: 'Filter channels (default: owned)'
enum:
- owned
- subscribed
- all
- subscribed_any
- all_any
in: query
name: selector
required: true
type: string
responses:
"200":
description: OK
@ -1489,6 +1582,33 @@ paths:
name: post_body
schema:
$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:
"200":
description: OK