feat(api): add pave spec to route /v1/spec and add seed to it
Exposes the API spec in pave format, which intends to show information about all API routes. Also pave V1SeedPOST and V1SpecGET
This commit is contained in:
parent
0c7009b16b
commit
7bf489315e
15 changed files with 152 additions and 25 deletions
|
@ -16,6 +16,8 @@ ADD api/ api/
|
||||||
|
|
||||||
ADD apihandler/ apihandler/
|
ADD apihandler/ apihandler/
|
||||||
|
|
||||||
|
ADD apirequest/ apirequest/
|
||||||
|
|
||||||
ADD apiresponse/ apiresponse/
|
ADD apiresponse/ apiresponse/
|
||||||
|
|
||||||
ADD config/ config/
|
ADD config/ config/
|
||||||
|
|
16
api/api.go
16
api/api.go
|
@ -10,6 +10,7 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
"codeberg.org/vlbeaudoin/voki"
|
"codeberg.org/vlbeaudoin/voki"
|
||||||
|
"git.agecem.com/agecem/agecem-org/apirequest"
|
||||||
"git.agecem.com/agecem/agecem-org/apiresponse"
|
"git.agecem.com/agecem/agecem-org/apiresponse"
|
||||||
"git.agecem.com/agecem/agecem-org/config"
|
"git.agecem.com/agecem/agecem-org/config"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
|
@ -35,8 +36,8 @@ func New(client *http.Client, host, key string, port int, protocol string) (*API
|
||||||
return &API{Voki: voki.New(client, host, key, port, protocol)}, nil
|
return &API{Voki: voki.New(client, host, key, port, protocol)}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *API) UploadDocument(bucket string, file_header *multipart.FileHeader) (apiresponse.V1DocumentCreateResponse, error) {
|
func (a *API) UploadDocument(bucket string, file_header *multipart.FileHeader) (apiresponse.V1DocumentCreate, error) {
|
||||||
var response apiresponse.V1DocumentCreateResponse
|
var response apiresponse.V1DocumentCreate
|
||||||
endpoint := fmt.Sprintf("%s://%s:%d",
|
endpoint := fmt.Sprintf("%s://%s:%d",
|
||||||
a.Voki.Protocol,
|
a.Voki.Protocol,
|
||||||
a.Voki.Host,
|
a.Voki.Host,
|
||||||
|
@ -100,6 +101,15 @@ func (a *API) UploadDocument(bucket string, file_header *multipart.FileHeader) (
|
||||||
return response, err
|
return response, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *API) ListBuckets() (response apiresponse.V1BucketListResponse, err error) {
|
func (a *API) ListBuckets() (response apiresponse.V1BucketList, err error) {
|
||||||
return response, a.Voki.Unmarshal(http.MethodGet, "/v1/bucket", nil, true, &response)
|
return response, a.Voki.Unmarshal(http.MethodGet, "/v1/bucket", nil, true, &response)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *API) Seed() (response apiresponse.V1SeedPOST, err error) {
|
||||||
|
request, err := apirequest.NewV1SeedPOST()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return request.Request(a.Voki)
|
||||||
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
|
"codeberg.org/vlbeaudoin/pave"
|
||||||
"git.agecem.com/agecem/agecem-org/apiresponse"
|
"git.agecem.com/agecem/agecem-org/apiresponse"
|
||||||
"git.agecem.com/agecem/agecem-org/config"
|
"git.agecem.com/agecem/agecem-org/config"
|
||||||
"git.agecem.com/agecem/agecem-org/media"
|
"git.agecem.com/agecem/agecem-org/media"
|
||||||
|
@ -15,6 +16,7 @@ import (
|
||||||
type V1Handler struct {
|
type V1Handler struct {
|
||||||
Config config.Config
|
Config config.Config
|
||||||
MediaClient *media.MediaClient
|
MediaClient *media.MediaClient
|
||||||
|
Pave *pave.Pave
|
||||||
}
|
}
|
||||||
|
|
||||||
// API Handlers
|
// API Handlers
|
||||||
|
@ -30,7 +32,7 @@ func (h *V1Handler) HandleV1(c echo.Context) error {
|
||||||
// HandleV1Seed créé des buckets dans minio selon la liste de buckets dans server.documents.buckets
|
// HandleV1Seed créé des buckets dans minio selon la liste de buckets dans server.documents.buckets
|
||||||
// Les buckets sont créés avec paramètres par défaut, et sont ensuite visible dans /v1/bucket.
|
// Les buckets sont créés avec paramètres par défaut, et sont ensuite visible dans /v1/bucket.
|
||||||
func (h *V1Handler) HandleV1Seed(c echo.Context) error {
|
func (h *V1Handler) HandleV1Seed(c echo.Context) error {
|
||||||
var response apiresponse.V1SeedResponse
|
var response apiresponse.V1SeedPOST
|
||||||
|
|
||||||
new_buckets, err := h.MediaClient.Seed()
|
new_buckets, err := h.MediaClient.Seed()
|
||||||
response.Data.Buckets = new_buckets
|
response.Data.Buckets = new_buckets
|
||||||
|
@ -56,7 +58,7 @@ func (h *V1Handler) HandleV1Seed(c echo.Context) error {
|
||||||
|
|
||||||
// HandleV1BucketList affiche les buckets permis par server.documents.buckets, qui existent.
|
// HandleV1BucketList affiche les buckets permis par server.documents.buckets, qui existent.
|
||||||
func (h *V1Handler) HandleV1BucketList(c echo.Context) error {
|
func (h *V1Handler) HandleV1BucketList(c echo.Context) error {
|
||||||
var response apiresponse.V1BucketListResponse
|
var response apiresponse.V1BucketList
|
||||||
|
|
||||||
var buckets = make(map[string]string)
|
var buckets = make(map[string]string)
|
||||||
|
|
||||||
|
@ -83,7 +85,7 @@ func (h *V1Handler) HandleV1BucketList(c echo.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *V1Handler) HandleV1BucketRead(c echo.Context) error {
|
func (h *V1Handler) HandleV1BucketRead(c echo.Context) error {
|
||||||
var response apiresponse.V1BucketReadResponse
|
var response apiresponse.V1BucketRead
|
||||||
|
|
||||||
bucket := c.Param("bucket")
|
bucket := c.Param("bucket")
|
||||||
|
|
||||||
|
@ -137,7 +139,7 @@ func (h *V1Handler) HandleV1BucketRead(c echo.Context) error {
|
||||||
|
|
||||||
// HandleV1DocumentCreate permet d'ajouter un object dans un bucket, par multipart/form-data
|
// HandleV1DocumentCreate permet d'ajouter un object dans un bucket, par multipart/form-data
|
||||||
func (h *V1Handler) HandleV1DocumentCreate(c echo.Context) error {
|
func (h *V1Handler) HandleV1DocumentCreate(c echo.Context) error {
|
||||||
var response apiresponse.V1DocumentCreateResponse
|
var response apiresponse.V1DocumentCreate
|
||||||
|
|
||||||
bucket := c.Param("bucket")
|
bucket := c.Param("bucket")
|
||||||
|
|
||||||
|
|
36
apihandler/spec.go
Normal file
36
apihandler/spec.go
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
package apihandler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"git.agecem.com/agecem/agecem-org/apirequest"
|
||||||
|
"git.agecem.com/agecem/agecem-org/apiresponse"
|
||||||
|
"git.agecem.com/agecem/agecem-org/version"
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
)
|
||||||
|
|
||||||
|
const DescriptionV1SpecGET string = "Afficher le API spec en format pave"
|
||||||
|
|
||||||
|
func (h *V1Handler) HandleV1Spec(c echo.Context) error {
|
||||||
|
var request apirequest.V1SpecGET
|
||||||
|
var response apiresponse.V1SpecGET
|
||||||
|
|
||||||
|
if !request.Complete() {
|
||||||
|
response.Message = "Incomplete V1SpecGET request received"
|
||||||
|
response.StatusCode = http.StatusBadRequest
|
||||||
|
|
||||||
|
return c.JSON(response.StatusCode, response)
|
||||||
|
}
|
||||||
|
|
||||||
|
response.Data.Spec = fmt.Sprintf("# pave spec for agecem-org %s", version.Version())
|
||||||
|
|
||||||
|
for _, route := range h.Pave.SortedRouteStrings() {
|
||||||
|
response.Data.Spec = fmt.Sprintf("%s%s", response.Data.Spec, route)
|
||||||
|
}
|
||||||
|
|
||||||
|
response.Message = "ok"
|
||||||
|
response.StatusCode = http.StatusOK
|
||||||
|
|
||||||
|
return c.JSON(response.StatusCode, response)
|
||||||
|
}
|
29
apirequest/seed.go
Normal file
29
apirequest/seed.go
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
package apirequest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"codeberg.org/vlbeaudoin/voki"
|
||||||
|
"codeberg.org/vlbeaudoin/voki/request"
|
||||||
|
"git.agecem.com/agecem/agecem-org/apiresponse"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ request.Requester[apiresponse.V1SeedPOST] = V1SeedPOST{}
|
||||||
|
|
||||||
|
type V1SeedPOST struct{}
|
||||||
|
|
||||||
|
func NewV1SeedPOST() (request V1SeedPOST, err error) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r V1SeedPOST) Complete() bool { return true }
|
||||||
|
|
||||||
|
func (r V1SeedPOST) Request(v *voki.Voki) (response apiresponse.V1SeedPOST, err error) {
|
||||||
|
if !r.Complete() {
|
||||||
|
err = fmt.Errorf("Incomplete V1SeedPOST")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return response, v.UnmarshalIfComplete(http.MethodPost, "/v1/seed", nil, true, &response)
|
||||||
|
}
|
29
apirequest/spec.go
Normal file
29
apirequest/spec.go
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
package apirequest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"codeberg.org/vlbeaudoin/voki"
|
||||||
|
"codeberg.org/vlbeaudoin/voki/request"
|
||||||
|
"git.agecem.com/agecem/agecem-org/apiresponse"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ request.Requester[apiresponse.V1SpecGET] = V1SpecGET{}
|
||||||
|
|
||||||
|
type V1SpecGET struct{}
|
||||||
|
|
||||||
|
func NewV1SpecGET() (request V1SpecGET, err error) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (request V1SpecGET) Complete() bool { return true }
|
||||||
|
|
||||||
|
func (request V1SpecGET) Request(v *voki.Voki) (response apiresponse.V1SpecGET, err error) {
|
||||||
|
if !request.Complete() {
|
||||||
|
err = fmt.Errorf("Incomplete V1SpecGET")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return response, v.UnmarshalIfComplete(http.MethodGet, "/v1/spec", nil, true, &response)
|
||||||
|
}
|
|
@ -2,27 +2,19 @@ package apiresponse
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"codeberg.org/vlbeaudoin/voki/response"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Responder interface {
|
|
||||||
Respond() Responder
|
|
||||||
}
|
|
||||||
|
|
||||||
type Response struct {
|
type Response struct {
|
||||||
StatusCode int `json:"status_code"`
|
response.ResponseWithError
|
||||||
Message string
|
|
||||||
Error string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r Response) Respond() Responder {
|
|
||||||
return r
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type SimpleResponse struct {
|
type SimpleResponse struct {
|
||||||
Message string
|
Message string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r SimpleResponse) Respond() Responder {
|
func (r SimpleResponse) Respond() response.Responder {
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
package apiresponse
|
package apiresponse
|
||||||
|
|
||||||
type V1BucketListResponse struct {
|
type V1BucketList struct {
|
||||||
Response
|
Response
|
||||||
Data struct {
|
Data struct {
|
||||||
Buckets map[string]string
|
Buckets map[string]string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type V1BucketReadResponse struct {
|
type V1BucketRead struct {
|
||||||
Response
|
Response
|
||||||
Data struct {
|
Data struct {
|
||||||
Keys []string
|
Keys []string
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package apiresponse
|
package apiresponse
|
||||||
|
|
||||||
type V1DocumentCreateResponse struct {
|
type V1DocumentCreate struct {
|
||||||
Response
|
Response
|
||||||
Data struct {
|
Data struct {
|
||||||
Bucket string
|
Bucket string
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package apiresponse
|
package apiresponse
|
||||||
|
|
||||||
type V1SeedResponse struct {
|
type V1SeedPOST struct {
|
||||||
Response
|
Response
|
||||||
Data struct {
|
Data struct {
|
||||||
Buckets []string
|
Buckets []string
|
||||||
|
|
8
apiresponse/spec.go
Normal file
8
apiresponse/spec.go
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
package apiresponse
|
||||||
|
|
||||||
|
type V1SpecGET struct {
|
||||||
|
Response
|
||||||
|
Data struct {
|
||||||
|
Spec string
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,12 +13,15 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"codeberg.org/vlbeaudoin/pave"
|
||||||
"codeberg.org/vlbeaudoin/serpents"
|
"codeberg.org/vlbeaudoin/serpents"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
|
|
||||||
"git.agecem.com/agecem/agecem-org/api"
|
"git.agecem.com/agecem/agecem-org/api"
|
||||||
"git.agecem.com/agecem/agecem-org/apihandler"
|
"git.agecem.com/agecem/agecem-org/apihandler"
|
||||||
|
"git.agecem.com/agecem/agecem-org/apirequest"
|
||||||
|
"git.agecem.com/agecem/agecem-org/apiresponse"
|
||||||
"git.agecem.com/agecem/agecem-org/config"
|
"git.agecem.com/agecem/agecem-org/config"
|
||||||
"git.agecem.com/agecem/agecem-org/media"
|
"git.agecem.com/agecem/agecem-org/media"
|
||||||
"git.agecem.com/agecem/agecem-org/public"
|
"git.agecem.com/agecem/agecem-org/public"
|
||||||
|
@ -205,14 +208,27 @@ func RunServer() {
|
||||||
log.Fatal("Error during NewMediaClientFromViper for API handlers")
|
log.Fatal("Error during NewMediaClientFromViper for API handlers")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
p := pave.New()
|
||||||
|
|
||||||
v1Handler := apihandler.V1Handler{
|
v1Handler := apihandler.V1Handler{
|
||||||
Config: cfg,
|
Config: cfg,
|
||||||
MediaClient: mediaClient,
|
MediaClient: mediaClient,
|
||||||
|
Pave: &p,
|
||||||
}
|
}
|
||||||
|
|
||||||
groupV1.GET("", v1Handler.HandleV1)
|
groupV1.GET("", v1Handler.HandleV1)
|
||||||
|
|
||||||
groupV1.POST("/seed", v1Handler.HandleV1Seed)
|
if err := pave.EchoRegister[
|
||||||
|
apirequest.V1SeedPOST,
|
||||||
|
apiresponse.V1SeedPOST](groupV1, &p, "/v1", http.MethodPost, "/seed", "Créer buckets manquants définis dans `server.documents.buckets`", "V1SeedPOST", v1Handler.HandleV1Seed); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := pave.EchoRegister[
|
||||||
|
apirequest.V1SpecGET,
|
||||||
|
apiresponse.V1SpecGET](groupV1, &p, "/v1", http.MethodGet, "/spec", apihandler.DescriptionV1SpecGET, "V1SpecGET", v1Handler.HandleV1Spec); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
groupV1.GET("/bucket", v1Handler.HandleV1BucketList)
|
groupV1.GET("/bucket", v1Handler.HandleV1BucketList)
|
||||||
|
|
||||||
|
|
1
go.mod
1
go.mod
|
@ -3,6 +3,7 @@ module git.agecem.com/agecem/agecem-org
|
||||||
go 1.21.1
|
go 1.21.1
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
codeberg.org/vlbeaudoin/pave v1.0.1
|
||||||
codeberg.org/vlbeaudoin/serpents v1.1.0
|
codeberg.org/vlbeaudoin/serpents v1.1.0
|
||||||
codeberg.org/vlbeaudoin/voki v1.7.1
|
codeberg.org/vlbeaudoin/voki v1.7.1
|
||||||
github.com/labstack/echo/v4 v4.11.3
|
github.com/labstack/echo/v4 v4.11.3
|
||||||
|
|
2
go.sum
2
go.sum
|
@ -35,6 +35,8 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl
|
||||||
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
||||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||||
cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
|
cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
|
||||||
|
codeberg.org/vlbeaudoin/pave v1.0.1 h1:N7/TIb615By1nds5h+iKSZtPAFeuT3ceuUH/VG6t7Rw=
|
||||||
|
codeberg.org/vlbeaudoin/pave v1.0.1/go.mod h1:D/Lb/EmfJzl066A+2g4wc42e1Pb/l4nmXjIGouYBviM=
|
||||||
codeberg.org/vlbeaudoin/serpents v1.1.0 h1:U9f2+2D1yUVHx90yePi2ZOLRLG/Wkoob4JXDIVyoBwA=
|
codeberg.org/vlbeaudoin/serpents v1.1.0 h1:U9f2+2D1yUVHx90yePi2ZOLRLG/Wkoob4JXDIVyoBwA=
|
||||||
codeberg.org/vlbeaudoin/serpents v1.1.0/go.mod h1:3bE/R0ToABwcUJtS1VcGEBa86K5FYhrZGAbFl2qL8kQ=
|
codeberg.org/vlbeaudoin/serpents v1.1.0/go.mod h1:3bE/R0ToABwcUJtS1VcGEBa86K5FYhrZGAbFl2qL8kQ=
|
||||||
codeberg.org/vlbeaudoin/voki v1.7.1 h1:Eywgk2A8NQmg4vucJjtheUpB0S2RYlDS8A7VwP+wFHU=
|
codeberg.org/vlbeaudoin/voki v1.7.1 h1:Eywgk2A8NQmg4vucJjtheUpB0S2RYlDS8A7VwP+wFHU=
|
||||||
|
|
|
@ -66,7 +66,7 @@ func (h *WebHandler) HandleDocumentation(c echo.Context) error {
|
||||||
|
|
||||||
for bucket, displayName := range v1BucketListResponse.Data.Buckets {
|
for bucket, displayName := range v1BucketListResponse.Data.Buckets {
|
||||||
// TODO move call to dedicated API client method
|
// TODO move call to dedicated API client method
|
||||||
var v1BucketReadResponse apiresponse.V1BucketReadResponse
|
var v1BucketReadResponse apiresponse.V1BucketRead
|
||||||
|
|
||||||
if err = h.ApiClient.Voki.Unmarshal(http.MethodGet, fmt.Sprintf("/v1/bucket/%s", bucket), nil, true, &v1BucketReadResponse); err != nil {
|
if err = h.ApiClient.Voki.Unmarshal(http.MethodGet, fmt.Sprintf("/v1/bucket/%s", bucket), nil, true, &v1BucketReadResponse); err != nil {
|
||||||
response.Error = err.Error()
|
response.Error = err.Error()
|
||||||
|
|
Loading…
Reference in a new issue