ListClients()

This commit is contained in:
Mike Schwörer 2022-11-19 12:47:23 +01:00
parent 35ef2175bc
commit f555f0f1cf
Signed by: Mikescher
GPG Key ID: D3C7172E0A70F8CF
12 changed files with 307 additions and 19 deletions

View File

@ -28,7 +28,8 @@ build-docker:
-t "$(DOCKER_REPO)/$(DOCKER_NAME):latest" \
.
build-swagger:
.PHONY: swagger
swagger:
which swag || go install github.com/swaggo/swag/cmd/swag@latest
swag init -generalInfo api/router.go --output ./swagger/ --outputTypes "json,yaml"

View File

@ -96,7 +96,7 @@ func (h APIHandler) CreateUser(g *gin.Context) ginresp.HTTPResponse {
// GetUser swaggerdoc
//
// @Summary Get a user (only self is allowed)
// @Summary Get a user
// @ID api-user-get
//
// @Param uid path int true "UserID"
@ -137,7 +137,7 @@ func (h APIHandler) GetUser(g *gin.Context) ginresp.HTTPResponse {
// UpdateUser swaggerdoc
//
// @Summary (Partially) update a user (only self allowed)
// @Summary (Partially) update a user
// @Description The body-values are optional, only send the ones you want to update
// @ID api-user-update
//
@ -212,8 +212,47 @@ func (h APIHandler) UpdateUser(g *gin.Context) ginresp.HTTPResponse {
return ctx.FinishSuccess(ginresp.JSON(http.StatusOK, user.JSON()))
}
// ListClients swaggerdoc
//
// @Summary List all clients
// @ID api-clients-list
//
// @Param uid path int true "UserID"
//
// @Success 200 {object} handler.ListClients.result
// @Failure 400 {object} ginresp.apiError
// @Failure 401 {object} ginresp.apiError
// @Failure 404 {object} ginresp.apiError
// @Failure 500 {object} ginresp.apiError
//
// @Router /api-v2/user/{uid}/clients [GET]
func (h APIHandler) ListClients(g *gin.Context) ginresp.HTTPResponse {
return ginresp.NotImplemented()
type uri struct {
UserID int64 `uri:"uid"`
}
type result struct {
Clients []models.ClientJSON `json:"clients"`
}
var u uri
ctx, errResp := h.app.StartRequest(g, &u, nil, nil)
if errResp != nil {
return *errResp
}
defer ctx.Cancel()
if permResp := ctx.CheckPermissionUserRead(u.UserID); permResp != nil {
return *permResp
}
clients, err := h.app.Database.ListClients(ctx, u.UserID)
if err != nil {
return ginresp.InternAPIError(500, apierr.DATABASE_ERROR, "Failed to query user", err)
}
res := langext.ArrMap(clients, func(v models.Client) models.ClientJSON { return v.JSON() })
return ctx.FinishSuccess(ginresp.JSON(http.StatusOK, result{Clients: res}))
}
func (h APIHandler) GetClient(g *gin.Context) ginresp.HTTPResponse {

View File

@ -3,6 +3,7 @@ package models
import (
"database/sql"
"github.com/blockloop/scan"
"gogs.mikescher.com/BlackForestBytes/goext/langext"
"time"
)
@ -71,10 +72,19 @@ func (c ChannelDB) Model() Channel {
}
func DecodeChannel(r *sql.Rows) (Channel, error) {
var udb ChannelDB
err := scan.RowStrict(&udb, r)
var data ChannelDB
err := scan.RowStrict(&data, r)
if err != nil {
return Channel{}, err
}
return udb.Model(), nil
return data.Model(), nil
}
func DecodeChannels(r *sql.Rows) ([]Channel, error) {
var data []ChannelDB
err := scan.RowsStrict(&data, r)
if err != nil {
return nil, err
}
return langext.ArrMap(data, func(v ChannelDB) Channel { return v.Model() }), nil
}

View File

@ -1,6 +1,11 @@
package models
import "time"
import (
"database/sql"
"github.com/blockloop/scan"
"gogs.mikescher.com/BlackForestBytes/goext/langext"
"time"
)
type ClientType string
@ -19,5 +24,64 @@ type Client struct {
AgentVersion string
}
type ClientJSON struct {
func (c Client) JSON() ClientJSON {
return ClientJSON{
ClientID: c.ClientID,
UserID: c.UserID,
Type: c.Type,
FCMToken: c.FCMToken,
TimestampCreated: c.TimestampCreated.Format(time.RFC3339Nano),
AgentModel: c.AgentModel,
AgentVersion: c.AgentVersion,
}
}
type ClientJSON struct {
ClientID int64 `json:"client_id"`
UserID int64 `json:"user_id"`
Type ClientType `json:"type"`
FCMToken *string `json:"fcm_token"`
TimestampCreated string `json:"timestamp_created"`
AgentModel string `json:"agent_model"`
AgentVersion string `json:"agent_version"`
}
type ClientDB struct {
ClientID int64 `db:"client_id"`
UserID int64 `db:"user_id"`
Type ClientType `db:"type"`
FCMToken *string `db:"fcm_token"`
TimestampCreated int64 `db:"timestamp_created"`
AgentModel string `db:"agent_model"`
AgentVersion string `db:"agent_version"`
}
func (c ClientDB) Model() Client {
return Client{
ClientID: c.ClientID,
UserID: c.UserID,
Type: c.Type,
FCMToken: c.FCMToken,
TimestampCreated: time.UnixMilli(c.TimestampCreated),
AgentModel: c.AgentModel,
AgentVersion: c.AgentVersion,
}
}
func DecodeClient(r *sql.Rows) (Client, error) {
var data ClientDB
err := scan.RowStrict(&data, r)
if err != nil {
return Client{}, err
}
return data.Model(), nil
}
func DecodeClients(r *sql.Rows) ([]Client, error) {
var data []ClientDB
err := scan.RowsStrict(&data, r)
if err != nil {
return nil, err
}
return langext.ArrMap(data, func(v ClientDB) Client { return v.Model() }), nil
}

View File

@ -3,6 +3,7 @@ package models
import (
"database/sql"
"github.com/blockloop/scan"
"gogs.mikescher.com/BlackForestBytes/goext/langext"
"time"
)
@ -88,10 +89,19 @@ func (u UserDB) Model() User {
}
func DecodeUser(r *sql.Rows) (User, error) {
var udb UserDB
err := scan.RowStrict(&udb, r)
var data UserDB
err := scan.RowStrict(&data, r)
if err != nil {
return User{}, err
}
return udb.Model(), nil
return data.Model(), nil
}
func DecodeUsers(r *sql.Rows) ([]User, error) {
var data []UserDB
err := scan.RowsStrict(&data, r)
if err != nil {
return nil, err
}
return langext.ArrMap(data, func(v UserDB) User { return v.Model() }), nil
}

View File

@ -8,13 +8,17 @@ func Wrap(fn WHandlerFunc) gin.HandlerFunc {
return func(g *gin.Context) {
reqctx := g.Request.Context()
wrap := fn(g)
if g.Writer.Written() {
panic("Writing in WrapperFunc is not supported")
}
wrap.Write(g)
if reqctx.Err() == nil {
wrap.Write(g)
}
}

View File

@ -202,3 +202,22 @@ func (db *Database) UpdateUserProToken(ctx TxContext, userid int64, protoken *st
return nil
}
func (db *Database) ListClients(ctx TxContext, userid int64) ([]models.Client, error) {
tx, err := ctx.GetOrCreateTransaction(db)
if err != nil {
return nil, err
}
rows, err := tx.QueryContext(ctx, "SELECT * FROM clients WHERE user_id = ?", userid)
if err != nil {
return nil, err
}
data, err := models.DecodeClients(rows)
if err != nil {
return nil, err
}
return data, nil
}

View File

@ -6,6 +6,7 @@ require (
github.com/gin-gonic/gin v1.8.1
github.com/rs/zerolog v1.28.0
github.com/swaggo/swag v1.8.7
gogs.mikescher.com/BlackForestBytes/goext v0.0.18
)
require (
@ -33,7 +34,6 @@ require (
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.0.1 // indirect
github.com/ugorji/go/codec v1.2.7 // indirect
gogs.mikescher.com/BlackForestBytes/goext v0.0.17 // indirect
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect
golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect
golang.org/x/sys v0.1.0 // indirect

View File

@ -95,6 +95,8 @@ github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
gogs.mikescher.com/BlackForestBytes/goext v0.0.17 h1:jsfbvII7aa0SH9qY0fnXBdtNnQe1YY3DgXDThEwLICc=
gogs.mikescher.com/BlackForestBytes/goext v0.0.17/go.mod h1:TMBOjo3FRFh/GiTT0z3nwLmgcFJB87oSF2VMs4XUCTQ=
gogs.mikescher.com/BlackForestBytes/goext v0.0.18 h1:fprrLoAPGdI4ObveHR1DjiP9WhlTJppWtjqMA6ZkyS8=
gogs.mikescher.com/BlackForestBytes/goext v0.0.18/go.mod h1:TMBOjo3FRFh/GiTT0z3nwLmgcFJB87oSF2VMs4XUCTQ=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=

View File

@ -43,7 +43,7 @@ func (app *Application) Run() {
errChan := make(chan error)
go func() {
log.Info().Str("address", httpserver.Addr).Msg("HTTP-Server started")
log.Info().Str("address", httpserver.Addr).Msg("HTTP-Server started on http://localhost:" + app.Config.ServerPort)
errChan <- httpserver.ListenAndServe()
}()

View File

@ -47,7 +47,7 @@
},
"/api-v2/user/{uid}": {
"get": {
"summary": "Get a user (only self is allowed)",
"summary": "Get a user",
"operationId": "api-user-get",
"parameters": [
{
@ -93,7 +93,7 @@
},
"patch": {
"description": "The body-values are optional, only send the ones you want to update",
"summary": "(Partially) update a user (only self allowed)",
"summary": "(Partially) update a user",
"operationId": "api-user-update",
"parameters": [
{
@ -139,6 +139,53 @@
}
}
},
"/api-v2/user/{uid}/clients": {
"get": {
"summary": "List all clients",
"operationId": "api-clients-list",
"parameters": [
{
"type": "integer",
"description": "UserID",
"name": "uid",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/handler.ListClients.result"
}
},
"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": {
"get": {
"summary": "Acknowledge that a message was received",
@ -667,6 +714,17 @@
}
}
},
"handler.ListClients.result": {
"type": "object",
"properties": {
"clients": {
"type": "array",
"items": {
"$ref": "#/definitions/models.ClientJSON"
}
}
}
},
"handler.Register.response": {
"type": "object",
"properties": {
@ -801,6 +859,32 @@
}
}
},
"models.ClientJSON": {
"type": "object",
"properties": {
"agent_model": {
"type": "string"
},
"agent_version": {
"type": "string"
},
"client_id": {
"type": "integer"
},
"fcm_token": {
"type": "string"
},
"timestamp_created": {
"type": "string"
},
"type": {
"type": "string"
},
"user_id": {
"type": "integer"
}
}
},
"models.CompatMessage": {
"type": "object",
"properties": {

View File

@ -84,6 +84,13 @@ definitions:
user_key:
type: string
type: object
handler.ListClients.result:
properties:
clients:
items:
$ref: '#/definitions/models.ClientJSON'
type: array
type: object
handler.Register.response:
properties:
is_pro:
@ -171,6 +178,23 @@ definitions:
uri:
type: string
type: object
models.ClientJSON:
properties:
agent_model:
type: string
agent_version:
type: string
client_id:
type: integer
fcm_token:
type: string
timestamp_created:
type: string
type:
type: string
user_id:
type: integer
type: object
models.CompatMessage:
properties:
body:
@ -290,7 +314,7 @@ paths:
description: Internal Server Error
schema:
$ref: '#/definitions/ginresp.apiError'
summary: Get a user (only self is allowed)
summary: Get a user
patch:
description: The body-values are optional, only send the ones you want to update
operationId: api-user-update
@ -321,7 +345,38 @@ paths:
description: Internal Server Error
schema:
$ref: '#/definitions/ginresp.apiError'
summary: (Partially) update a user (only self allowed)
summary: (Partially) update a user
/api-v2/user/{uid}/clients:
get:
operationId: api-clients-list
parameters:
- description: UserID
in: path
name: uid
required: true
type: integer
responses:
"200":
description: OK
schema:
$ref: '#/definitions/handler.ListClients.result'
"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 clients
/api/ack.php:
get:
operationId: compat-ack