parent
43451aa32e
commit
d1878c9e97
9
Makefile
9
Makefile
|
@ -18,14 +18,19 @@ install-golangci-lint:
|
|||
@echo "Installing github.com/golangci/golangci-lint..."
|
||||
@(test -f $(GOLANGCI_LINT_PATH) && echo "github.com/golangci/golangci-lint is already installed. Skipping...") || curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(GOBIN) v1.55.2
|
||||
|
||||
.PHONY: install-oapi-codegen
|
||||
install-oapi-codegen:
|
||||
@echo "Installing github.com/deepmap/oapi-codegen..."
|
||||
@go install github.com/deepmap/oapi-codegen/v2/cmd/oapi-codegen@v2.0.0
|
||||
|
||||
.PHONY: install-tools
|
||||
install-tools: install-golangci-lint
|
||||
install-tools: install-golangci-lint install-oapi-codegen
|
||||
|
||||
.PHONY: install
|
||||
install: install-tools install-git-hooks
|
||||
|
||||
.PHONY: generate
|
||||
generate:
|
||||
generate: install-oapi-codegen
|
||||
@echo "Running go generate..."
|
||||
go generate ./...
|
||||
|
||||
|
|
|
@ -0,0 +1,144 @@
|
|||
openapi: 3.0.0
|
||||
info:
|
||||
version: 2.0.0
|
||||
title: TWHelp API
|
||||
description: REST API to interact with [TWHelp](https://tribalwarshelp.com).
|
||||
contact:
|
||||
name: Dawid Wysokiński
|
||||
url: https://dwysokinski.me
|
||||
email: contact@dwysokinski.me
|
||||
license:
|
||||
name: MIT
|
||||
tags:
|
||||
- name: versions
|
||||
servers:
|
||||
- url: "{scheme}://{hostname}/api"
|
||||
variables:
|
||||
scheme:
|
||||
default: http
|
||||
enum:
|
||||
- http
|
||||
- https
|
||||
hostname:
|
||||
default: localhost:9913
|
||||
paths:
|
||||
/v2/versions:
|
||||
get:
|
||||
operationId: listVersions
|
||||
tags:
|
||||
- versions
|
||||
description: List versions
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/CursorQueryParam"
|
||||
- $ref: "#/components/parameters/LimitQueryParam"
|
||||
responses:
|
||||
200:
|
||||
$ref: "#/components/responses/ListVersionsResponse"
|
||||
default:
|
||||
$ref: "#/components/responses/ErrorResponse"
|
||||
components:
|
||||
schemas:
|
||||
Error:
|
||||
type: object
|
||||
required:
|
||||
- slug
|
||||
- message
|
||||
additionalProperties: false
|
||||
properties:
|
||||
slug:
|
||||
type: string
|
||||
message:
|
||||
type: string
|
||||
params:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
x-go-type-skip-optional-pointer: true
|
||||
Version:
|
||||
type: object
|
||||
required:
|
||||
- code
|
||||
- host
|
||||
- name
|
||||
- timezone
|
||||
properties:
|
||||
code:
|
||||
type: string
|
||||
example: pl
|
||||
host:
|
||||
type: string
|
||||
example: www.tribalwars.net
|
||||
name:
|
||||
type: string
|
||||
example: Poland
|
||||
timezone:
|
||||
type: string
|
||||
example: Europe/Warsaw
|
||||
PaginationResponse:
|
||||
type: object
|
||||
properties:
|
||||
self:
|
||||
description: Pagination link pointing to the current page.
|
||||
type: string
|
||||
format: uri
|
||||
x-go-type-skip-optional-pointer: true
|
||||
first:
|
||||
description: Pagination link pointing to the first page.
|
||||
type: string
|
||||
format: uri
|
||||
x-go-type-skip-optional-pointer: true
|
||||
prev:
|
||||
description: Pagination link pointing to the previous page.
|
||||
type: string
|
||||
format: uri
|
||||
x-go-type-skip-optional-pointer: true
|
||||
next:
|
||||
description: Pagination link pointing to the next page.
|
||||
type: string
|
||||
format: uri
|
||||
x-go-type-skip-optional-pointer: true
|
||||
last:
|
||||
description: Pagination link pointing to the last page.
|
||||
type: string
|
||||
format: uri
|
||||
x-go-type-skip-optional-pointer: true
|
||||
parameters:
|
||||
CursorQueryParam:
|
||||
in: query
|
||||
name: cursor
|
||||
schema:
|
||||
type: string
|
||||
required: false
|
||||
LimitQueryParam:
|
||||
in: query
|
||||
name: limit
|
||||
schema:
|
||||
type: integer
|
||||
required: false
|
||||
responses:
|
||||
ListVersionsResponse:
|
||||
description: ""
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: "#/components/schemas/PaginationResponse"
|
||||
- type: object
|
||||
required:
|
||||
- items
|
||||
properties:
|
||||
items:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/Version"
|
||||
ErrorResponse:
|
||||
description: Default error response.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required:
|
||||
- error
|
||||
additionalProperties: false
|
||||
properties:
|
||||
error:
|
||||
$ref: "#/components/schemas/Error"
|
|
@ -43,7 +43,8 @@ func newApp(name, version string) *appWrapper {
|
|||
app.Name = name
|
||||
app.HelpName = name
|
||||
app.Version = version
|
||||
app.Commands = []*cli.Command{cmdDB, cmdJob, cmdConsumer}
|
||||
app.Commands = []*cli.Command{cmdDB, cmdJob, cmdConsumer, cmdServe}
|
||||
app.DefaultCommand = cmdServe.Name
|
||||
app.Flags = concatSlices(appFlags, logFlags)
|
||||
app.Before = app.handleBefore
|
||||
return app
|
||||
|
|
|
@ -0,0 +1,183 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"gitea.dwysokinski.me/twhelp/corev3/internal/chislog"
|
||||
"gitea.dwysokinski.me/twhelp/corev3/internal/port"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/chi/v5/middleware"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var (
|
||||
apiServerPortFlag = &cli.UintFlag{
|
||||
Name: "api.port",
|
||||
EnvVars: []string{"API_PORT"},
|
||||
Value: 9234, //nolint:gomnd
|
||||
}
|
||||
apiServerReadTimeoutFlag = &cli.DurationFlag{
|
||||
Name: "api.readTimeout",
|
||||
EnvVars: []string{"API_READ_TIMEOUT"},
|
||||
Value: 5 * time.Second, //nolint:gomnd
|
||||
}
|
||||
apiServerReadHeaderTimeoutFlag = &cli.DurationFlag{
|
||||
Name: "api.readHeaderTimeout",
|
||||
EnvVars: []string{"API_READ_HEADER_TIMEOUT"},
|
||||
Value: time.Second,
|
||||
}
|
||||
apiServerWriteTimeoutFlag = &cli.DurationFlag{
|
||||
Name: "api.writeTimeout",
|
||||
EnvVars: []string{"API_WRITE_TIMEOUT"},
|
||||
Value: 10 * time.Second, //nolint:gomnd
|
||||
}
|
||||
apiServerIdleTimeoutFlag = &cli.DurationFlag{
|
||||
Name: "api.idleTimeout",
|
||||
EnvVars: []string{"API_IDLE_TIMEOUT"},
|
||||
Value: 180 * time.Second, //nolint:gomnd
|
||||
}
|
||||
apiServerShutdownTimeoutFlag = &cli.DurationFlag{
|
||||
Name: "api.shutdownTimeout",
|
||||
EnvVars: []string{"API_SHUTDOWN_TIMEOUT"},
|
||||
Value: 10 * time.Second, //nolint:gomnd
|
||||
}
|
||||
apiServerOpenAPIEnabledFlag = &cli.BoolFlag{
|
||||
Name: "api.openApi.enabled",
|
||||
EnvVars: []string{"API_OPENAPI_ENABLED"},
|
||||
Value: true,
|
||||
}
|
||||
apiServerOpenAPISwaggerEnabledFlag = &cli.BoolFlag{
|
||||
Name: "api.openApi.swaggerEnabled",
|
||||
EnvVars: []string{"API_OPENAPI_SWAGGER_ENABLED"},
|
||||
Value: true,
|
||||
}
|
||||
apiServerOpenAPIServersFlag = &cli.StringSliceFlag{
|
||||
Name: "api.openApi.servers",
|
||||
EnvVars: []string{"API_OPENAPI_SERVERS"},
|
||||
}
|
||||
apiServerFlags = []cli.Flag{
|
||||
apiServerPortFlag,
|
||||
apiServerReadTimeoutFlag,
|
||||
apiServerReadHeaderTimeoutFlag,
|
||||
apiServerWriteTimeoutFlag,
|
||||
apiServerIdleTimeoutFlag,
|
||||
apiServerOpenAPIEnabledFlag,
|
||||
apiServerOpenAPISwaggerEnabledFlag,
|
||||
apiServerOpenAPIServersFlag,
|
||||
}
|
||||
)
|
||||
|
||||
const apiBasePath = "/api"
|
||||
|
||||
var cmdServe = &cli.Command{
|
||||
Name: "serve",
|
||||
Usage: "Run the HTTP server",
|
||||
Flags: concatSlices(apiServerFlags, dbFlags),
|
||||
Action: func(c *cli.Context) error {
|
||||
logger := loggerFromCtx(c.Context)
|
||||
|
||||
bunDB, err := newBunDBFromFlags(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
logger.Debug("closing db connections...", slog.Int("db.openConnections", bunDB.Stats().OpenConnections))
|
||||
if dbCloseErr := bunDB.Close(); dbCloseErr != nil {
|
||||
logger.Warn("couldn't close db connections", slog.Any("error", dbCloseErr))
|
||||
} else {
|
||||
logger.Debug("db connections closed")
|
||||
}
|
||||
}()
|
||||
|
||||
server, err := newHTTPServer(
|
||||
httpServerConfig{
|
||||
port: c.Uint(apiServerPortFlag.Name),
|
||||
readTimeout: c.Duration(apiServerReadTimeoutFlag.Name),
|
||||
readHeaderTimeout: c.Duration(apiServerReadHeaderTimeoutFlag.Name),
|
||||
writeTimeout: c.Duration(apiServerWriteTimeoutFlag.Name),
|
||||
idleTimeout: c.Duration(apiServerIdleTimeoutFlag.Name),
|
||||
},
|
||||
func(r chi.Router) error {
|
||||
r.Use(newAPIMiddlewares(logger)...)
|
||||
|
||||
oapiCfg, oapiCfgErr := newOpenAPIConfigFromFlags(c)
|
||||
if oapiCfgErr != nil {
|
||||
return oapiCfgErr
|
||||
}
|
||||
|
||||
r.Mount(
|
||||
apiBasePath,
|
||||
port.NewAPIHTTPHandler(port.WithOpenAPIConfig(oapiCfg)),
|
||||
)
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("couldn't construct HTTP server: %w", err)
|
||||
}
|
||||
|
||||
idleConnsClosed := make(chan struct{})
|
||||
go func() {
|
||||
waitForShutdownSignal(c.Context)
|
||||
|
||||
logger.Debug("received shutdown signal")
|
||||
|
||||
ctx, cancel := context.WithTimeout(c.Context, c.Duration(apiServerShutdownTimeoutFlag.Name))
|
||||
defer cancel()
|
||||
|
||||
if err := server.Shutdown(ctx); err != nil {
|
||||
logger.Warn("shutdown failed", slog.Any("error", err))
|
||||
}
|
||||
|
||||
close(idleConnsClosed)
|
||||
}()
|
||||
|
||||
logger.Info("Server is listening on the addr "+server.Addr, slog.String("addr", server.Addr))
|
||||
|
||||
if err := server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
return fmt.Errorf("ListenAndServe failed: %w", err)
|
||||
}
|
||||
|
||||
<-idleConnsClosed
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func newOpenAPIConfigFromFlags(c *cli.Context) (port.OpenAPIConfig, error) {
|
||||
rawURLs := c.StringSlice(apiServerOpenAPIServersFlag.Name)
|
||||
|
||||
servers := make([]port.OpenAPIConfigServer, 0, len(rawURLs))
|
||||
|
||||
for _, raw := range rawURLs {
|
||||
u, err := url.Parse(raw)
|
||||
if err != nil {
|
||||
return port.OpenAPIConfig{}, err
|
||||
}
|
||||
|
||||
servers = append(servers, port.OpenAPIConfigServer{
|
||||
URL: u,
|
||||
})
|
||||
}
|
||||
|
||||
return port.OpenAPIConfig{
|
||||
Enabled: c.Bool(apiServerOpenAPIEnabledFlag.Name),
|
||||
SwaggerEnabled: c.Bool(apiServerOpenAPISwaggerEnabledFlag.Name),
|
||||
BasePath: apiBasePath,
|
||||
Servers: servers,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func newAPIMiddlewares(logger *slog.Logger) chi.Middlewares {
|
||||
return chi.Middlewares{
|
||||
chislog.Logger(logger),
|
||||
middleware.Recoverer,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
type httpServerConfig struct {
|
||||
port uint
|
||||
readTimeout time.Duration
|
||||
readHeaderTimeout time.Duration
|
||||
writeTimeout time.Duration
|
||||
idleTimeout time.Duration
|
||||
}
|
||||
|
||||
func newHTTPServer(cfg httpServerConfig, registerHandlers func(r chi.Router) error) (*http.Server, error) {
|
||||
r := chi.NewRouter()
|
||||
|
||||
if err := registerHandlers(r); err != nil {
|
||||
return nil, fmt.Errorf("couldn't register handlers: %w", err)
|
||||
}
|
||||
|
||||
return &http.Server{
|
||||
Addr: fmt.Sprintf(":%d", cfg.port),
|
||||
Handler: r,
|
||||
ReadTimeout: cfg.readTimeout,
|
||||
ReadHeaderTimeout: cfg.readHeaderTimeout,
|
||||
WriteTimeout: cfg.writeTimeout,
|
||||
IdleTimeout: cfg.idleTimeout,
|
||||
}, nil
|
||||
}
|
12
go.mod
12
go.mod
|
@ -8,9 +8,12 @@ require (
|
|||
github.com/brianvoe/gofakeit/v6 v6.28.0
|
||||
github.com/cenkalti/backoff/v4 v4.2.1
|
||||
github.com/elliotchance/phpserialize v1.3.3
|
||||
github.com/getkin/kin-openapi v0.122.0
|
||||
github.com/go-chi/chi/v5 v5.0.11
|
||||
github.com/go-chi/render v1.0.3
|
||||
github.com/google/go-cmp v0.6.0
|
||||
github.com/google/uuid v1.5.0
|
||||
github.com/oapi-codegen/runtime v1.1.1
|
||||
github.com/ory/dockertest/v3 v3.10.0
|
||||
github.com/stretchr/testify v1.8.4
|
||||
github.com/uptrace/bun v1.1.17
|
||||
|
@ -27,6 +30,8 @@ require (
|
|||
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.0 // indirect
|
||||
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
|
||||
github.com/ajg/form v1.5.1 // indirect
|
||||
github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
|
||||
github.com/cenkalti/backoff/v3 v3.2.2 // indirect
|
||||
github.com/containerd/continuity v0.3.0 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
||||
|
@ -37,23 +42,30 @@ require (
|
|||
github.com/docker/go-units v0.4.0 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/fatih/color v1.16.0 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.6 // indirect
|
||||
github.com/go-openapi/swag v0.22.4 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/imdario/mergo v0.3.12 // indirect
|
||||
github.com/invopop/yaml v0.2.0 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||
github.com/lithammer/shortuuid/v3 v3.0.7 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.19 // indirect
|
||||
github.com/mitchellh/mapstructure v1.4.1 // indirect
|
||||
github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 // indirect
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
|
||||
github.com/oklog/ulid v1.3.1 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.0.2 // indirect
|
||||
github.com/opencontainers/runc v1.1.5 // indirect
|
||||
github.com/perimeterx/marshmallow v1.1.5 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/rabbitmq/amqp091-go v1.9.0 // indirect
|
||||
|
|
47
go.sum
47
go.sum
|
@ -5,10 +5,16 @@ github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2y
|
|||
github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE=
|
||||
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw=
|
||||
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk=
|
||||
github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk=
|
||||
github.com/ThreeDotsLabs/watermill v1.3.5 h1:50JEPEhMGZQMh08ct0tfO1PsgMOAOhV3zxK2WofkbXg=
|
||||
github.com/ThreeDotsLabs/watermill v1.3.5/go.mod h1:O/u/Ptyrk5MPTxSeWM5vzTtZcZfxXfO9PK9eXTYiFZY=
|
||||
github.com/ThreeDotsLabs/watermill-amqp/v2 v2.1.1 h1:OxvMB2/3YtcQuC7quC+CGmFpGz9oaxP2ef5wkp+R2oM=
|
||||
github.com/ThreeDotsLabs/watermill-amqp/v2 v2.1.1/go.mod h1:MCNoh0HUg4w0bY64on9BnhUodHeimz8+vMfXrzyuWN8=
|
||||
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
|
||||
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
|
||||
github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ=
|
||||
github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk=
|
||||
github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w=
|
||||
github.com/brianvoe/gofakeit/v6 v6.28.0 h1:Xib46XXuQfmlLS2EXRuJpqcw8St6qSZz75OUo0tgAW4=
|
||||
github.com/brianvoe/gofakeit/v6 v6.28.0/go.mod h1:Xj58BMSnFqcn/fAQeSK+/PLtC5kSb7FJIq4JyGa8vEs=
|
||||
github.com/cenkalti/backoff/v3 v3.2.2 h1:cfUAAO3yvKMYKPrvhDuHSwQnhZNk/RMHKdZqKTxfm6M=
|
||||
|
@ -24,6 +30,7 @@ github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV
|
|||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw=
|
||||
github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
|
||||
|
@ -45,10 +52,21 @@ github.com/elliotchance/phpserialize v1.3.3/go.mod h1:gt7XX9+ETUcLXbtTKEuyrqW3lc
|
|||
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
||||
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
||||
github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
|
||||
github.com/getkin/kin-openapi v0.122.0 h1:WB9Jbl0Hp/T79/JF9xlSW5Kl9uYdk/AWD0yAd9HOM10=
|
||||
github.com/getkin/kin-openapi v0.122.0/go.mod h1:PCWw/lfBrJY4HcdqE3jj+QFkaFK8ABoqo7PvqVhXXqw=
|
||||
github.com/go-chi/chi/v5 v5.0.11 h1:BnpYbFZ3T3S1WMpD79r7R5ThWX40TaFB7L31Y8xqSwA=
|
||||
github.com/go-chi/chi/v5 v5.0.11/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=
|
||||
github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
|
||||
github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE=
|
||||
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
|
||||
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
|
||||
github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU=
|
||||
github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
|
||||
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
|
||||
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
|
||||
github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
|
@ -74,14 +92,21 @@ github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+l
|
|||
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
||||
github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
|
||||
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
||||
github.com/invopop/yaml v0.2.0 h1:7zky/qH+O0DwAyoobXUqvVBwgBFRxKoQ/3FjcVpjTMY=
|
||||
github.com/invopop/yaml v0.2.0/go.mod h1:2XuRLgs/ouIrW3XNzuNj7J3Nvu/Dig5MXvbCEdiBN3Q=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE=
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
|
@ -90,6 +115,8 @@ github.com/lib/pq v0.0.0-20180327071824-d34b9ff171c2 h1:hRGSmZu7j271trc9sneMrpOW
|
|||
github.com/lib/pq v0.0.0-20180327071824-d34b9ff171c2/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lithammer/shortuuid/v3 v3.0.7 h1:trX0KTHy4Pbwo/6ia8fscyHoGA+mf1jWbPJVuvyJQQ8=
|
||||
github.com/lithammer/shortuuid/v3 v3.0.7/go.mod h1:vMk8ke37EmiewwolSO1NLW8vP4ZaKlRuDIi8tWWmAts=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
|
@ -102,9 +129,11 @@ github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR
|
|||
github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU=
|
||||
github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 h1:rzf0wL0CHVc8CEsgyygG0Mn9CNCCPZqOPaz8RiiHYQk=
|
||||
github.com/moby/term v0.0.0-20201216013528-df9cb8a40635/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc=
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
|
||||
github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/oapi-codegen/runtime v1.1.1 h1:EXLHh0DXIJnWhdRPN2w4MXAzFyE4CskzhNLUmtpMYro=
|
||||
github.com/oapi-codegen/runtime v1.1.1/go.mod h1:SK9X900oXmPWilYR5/WKPzt3Kqxn/uS/+lbpREv+eCg=
|
||||
github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
|
||||
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
|
@ -117,6 +146,8 @@ github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.m
|
|||
github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI=
|
||||
github.com/ory/dockertest/v3 v3.10.0 h1:4K3z2VMe8Woe++invjaTB7VRyQXQy5UY+loujO4aNE4=
|
||||
github.com/ory/dockertest/v3 v3.10.0/go.mod h1:nr57ZbRWMqfsdGdFNLHz5jjNdDb7VVFnzAeW1n5N1Lg=
|
||||
github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s=
|
||||
github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
|
@ -126,6 +157,8 @@ github.com/rabbitmq/amqp091-go v1.9.0 h1:qrQtyzB4H8BQgEuJwhmVQqVHB9O4+MNDJCCAcpc
|
|||
github.com/rabbitmq/amqp091-go v1.9.0/go.mod h1:+jPrT9iY2eLjRaMSRHUhc3z14E/l85kv/f+6luSD3pc=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
|
@ -136,17 +169,22 @@ github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic
|
|||
github.com/sony/gobreaker v0.5.0 h1:dRCvqm0P490vZPmy7ppEk2qCnCieBooFJ+YoXGYB+yg=
|
||||
github.com/sony/gobreaker v0.5.0/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo=
|
||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs=
|
||||
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
|
||||
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
github.com/uptrace/bun v1.1.17 h1:qxBaEIo0hC/8O3O6GrMDKxqyT+mw5/s0Pn/n6xjyGIk=
|
||||
github.com/uptrace/bun v1.1.17/go.mod h1:hATAzivtTIRsSJR4B8AXR+uABqnQxr3myKDKEf5iQ9U=
|
||||
github.com/uptrace/bun/dbfixture v1.1.17 h1:/zvsLC582KizT7Ksignl64OXU5ut5x9sIs/fUhW8ssA=
|
||||
|
@ -236,12 +274,13 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0
|
|||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
package port
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
"gitea.dwysokinski.me/twhelp/corev3/internal/port/internal/apimodel"
|
||||
"gitea.dwysokinski.me/twhelp/corev3/internal/port/internal/swgui"
|
||||
"github.com/getkin/kin-openapi/openapi3"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/render"
|
||||
)
|
||||
|
||||
type apiHTTPHandler struct {
|
||||
apimodel.Unimplemented
|
||||
getOpenAPISchema func() (*openapi3.T, error)
|
||||
}
|
||||
|
||||
func WithOpenAPIConfig(oapiCfg OpenAPIConfig) APIHTTPHandlerOption {
|
||||
return func(cfg *apiHTTPHandlerConfig) {
|
||||
cfg.openAPI = oapiCfg
|
||||
}
|
||||
}
|
||||
|
||||
func NewAPIHTTPHandler(opts ...APIHTTPHandlerOption) http.Handler {
|
||||
cfg := newAPIHTTPHandlerConfig(opts...)
|
||||
|
||||
h := &apiHTTPHandler{
|
||||
getOpenAPISchema: sync.OnceValues(func() (*openapi3.T, error) {
|
||||
return getOpenAPISchema(cfg.openAPI)
|
||||
}),
|
||||
}
|
||||
|
||||
r := chi.NewRouter()
|
||||
|
||||
if cfg.openAPI.Enabled {
|
||||
r.Group(func(r chi.Router) {
|
||||
if cfg.openAPI.SwaggerEnabled {
|
||||
r.Handle("/v2/swagger/*", swgui.Handler(
|
||||
cfg.openAPI.BasePath+"/v2/swagger",
|
||||
cfg.openAPI.BasePath+"/v2/openapi3.json",
|
||||
))
|
||||
}
|
||||
r.Get("/v2/openapi3.json", h.sendOpenAPIJSON)
|
||||
})
|
||||
}
|
||||
|
||||
return apimodel.HandlerWithOptions(h, apimodel.ChiServerOptions{BaseRouter: r})
|
||||
}
|
||||
|
||||
func (h *apiHTTPHandler) renderJSON(w http.ResponseWriter, r *http.Request, status int, body any) {
|
||||
render.Status(r, status)
|
||||
render.JSON(w, r, body)
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
package port
|
||||
|
||||
import "net/url"
|
||||
|
||||
type OpenAPIConfigServer struct {
|
||||
URL *url.URL
|
||||
}
|
||||
|
||||
type OpenAPIConfig struct {
|
||||
Enabled bool
|
||||
SwaggerEnabled bool
|
||||
BasePath string
|
||||
Servers []OpenAPIConfigServer
|
||||
}
|
||||
|
||||
type apiHTTPHandlerConfig struct {
|
||||
openAPI OpenAPIConfig
|
||||
}
|
||||
|
||||
type APIHTTPHandlerOption func(cfg *apiHTTPHandlerConfig)
|
||||
|
||||
func newAPIHTTPHandlerConfig(opts ...APIHTTPHandlerOption) *apiHTTPHandlerConfig {
|
||||
cfg := &apiHTTPHandlerConfig{}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(cfg)
|
||||
}
|
||||
|
||||
return cfg
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
package port
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"gitea.dwysokinski.me/twhelp/corev3/internal/port/internal/apimodel"
|
||||
"github.com/getkin/kin-openapi/openapi3"
|
||||
)
|
||||
|
||||
func (h *apiHTTPHandler) sendOpenAPIJSON(w http.ResponseWriter, r *http.Request) {
|
||||
schema, err := h.getOpenAPISchema()
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
h.renderJSON(w, r, http.StatusOK, schema)
|
||||
}
|
||||
|
||||
func getOpenAPISchema(cfg OpenAPIConfig) (*openapi3.T, error) {
|
||||
if !cfg.Enabled {
|
||||
return nil, errors.New("openapi is disabled")
|
||||
}
|
||||
|
||||
schema, err := apimodel.GetSwagger()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(cfg.Servers) == 0 {
|
||||
return schema, nil
|
||||
}
|
||||
|
||||
schema.Servers = make(openapi3.Servers, 0, len(cfg.Servers))
|
||||
|
||||
for _, s := range cfg.Servers {
|
||||
schema.Servers = append(schema.Servers, &openapi3.Server{
|
||||
URL: s.URL.String(),
|
||||
})
|
||||
}
|
||||
|
||||
return schema, nil
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
package port_test
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"gitea.dwysokinski.me/twhelp/corev3/internal/port"
|
||||
"github.com/brianvoe/gofakeit/v6"
|
||||
"github.com/getkin/kin-openapi/openapi3"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestOpenAPI(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("OK: openapi3.json", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
u1, err := url.Parse(gofakeit.URL())
|
||||
require.NoError(t, err)
|
||||
|
||||
u2, err := url.Parse(gofakeit.URL())
|
||||
require.NoError(t, err)
|
||||
|
||||
servers := []port.OpenAPIConfigServer{
|
||||
{
|
||||
URL: u1,
|
||||
},
|
||||
{
|
||||
URL: u2,
|
||||
},
|
||||
}
|
||||
|
||||
handler := newAPIHTTPHandler(t, func(cfg *apiHTTPHandlerConfig) {
|
||||
cfg.options = append(cfg.options, port.WithOpenAPIConfig(port.OpenAPIConfig{
|
||||
Enabled: true,
|
||||
Servers: servers,
|
||||
}))
|
||||
})
|
||||
|
||||
//nolint:bodyclose
|
||||
resp := doRequest(handler, http.MethodGet, "/v2/openapi3.json", nil)
|
||||
defer require.NoError(t, resp.Body.Close())
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
b, err := io.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
body, err := openapi3.NewLoader().LoadFromData(b)
|
||||
require.NoError(t, err)
|
||||
for i, expected := range servers {
|
||||
idx := slices.IndexFunc(body.Servers, func(s *openapi3.Server) bool {
|
||||
return s.URL == expected.URL.String()
|
||||
})
|
||||
assert.GreaterOrEqualf(t, idx, 0, "expected[%d] not found", i)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("OK: Swagger", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
handler := newAPIHTTPHandler(t, func(cfg *apiHTTPHandlerConfig) {
|
||||
cfg.options = append(cfg.options, port.WithOpenAPIConfig(port.OpenAPIConfig{
|
||||
Enabled: true,
|
||||
SwaggerEnabled: true,
|
||||
}))
|
||||
})
|
||||
|
||||
tests := []struct {
|
||||
target string
|
||||
expectedContentType string
|
||||
}{
|
||||
{
|
||||
target: "/v2/swagger/",
|
||||
expectedContentType: "text/html; charset=utf-8",
|
||||
},
|
||||
{
|
||||
target: "/v2/swagger/swagger-initializer.js",
|
||||
expectedContentType: "text/javascript; charset=utf-8",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
|
||||
t.Run(tt.target, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
resp := doRequest(handler, http.MethodGet, tt.target, nil)
|
||||
defer require.NoError(t, resp.Body.Close())
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
assert.Equal(t, tt.expectedContentType, resp.Header.Get("Content-Type"))
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
package port_test
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"gitea.dwysokinski.me/twhelp/corev3/internal/port"
|
||||
)
|
||||
|
||||
type apiHTTPHandlerConfig struct {
|
||||
options []port.APIHTTPHandlerOption
|
||||
}
|
||||
|
||||
func newAPIHTTPHandler(tb testing.TB, opts ...func(cfg *apiHTTPHandlerConfig)) http.Handler {
|
||||
tb.Helper()
|
||||
|
||||
cfg := &apiHTTPHandlerConfig{}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(cfg)
|
||||
}
|
||||
|
||||
return port.NewAPIHTTPHandler(cfg.options...)
|
||||
}
|
||||
|
||||
func doRequest(h http.Handler, method, target string, body io.Reader) *http.Response {
|
||||
return doCustomRequest(h, httptest.NewRequest(method, target, body))
|
||||
}
|
||||
|
||||
func doCustomRequest(h http.Handler, r *http.Request) *http.Response {
|
||||
rr := httptest.NewRecorder()
|
||||
h.ServeHTTP(rr, r)
|
||||
return rr.Result()
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
*.gen.go
|
|
@ -0,0 +1,3 @@
|
|||
package apimodel
|
||||
|
||||
//go:generate oapi-codegen --config=config.yml ../../../../api/openapi3.yml
|
|
@ -0,0 +1,8 @@
|
|||
package: apimodel
|
||||
generate:
|
||||
chi-server: true
|
||||
models: true
|
||||
embedded-spec: true
|
||||
compatibility:
|
||||
apply-chi-middleware-first-to-last: true
|
||||
output: apimodel.gen.go
|
Binary file not shown.
After Width: | Height: | Size: 665 B |
Binary file not shown.
After Width: | Height: | Size: 628 B |
|
@ -0,0 +1,16 @@
|
|||
html {
|
||||
box-sizing: border-box;
|
||||
overflow: -moz-scrollbars-vertical;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
*,
|
||||
*:before,
|
||||
*:after {
|
||||
box-sizing: inherit;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
background: #fafafa;
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
<!-- HTML for static distribution bundle build -->
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Swagger UI</title>
|
||||
<link rel="stylesheet" type="text/css" href="./swagger-ui.css" />
|
||||
<link rel="stylesheet" type="text/css" href="./index.css" />
|
||||
<link rel="icon" type="image/png" href="./favicon-32x32.png" sizes="32x32" />
|
||||
<link rel="icon" type="image/png" href="./favicon-16x16.png" sizes="16x16" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="swagger-ui"></div>
|
||||
<script src="./swagger-ui-bundle.js" charset="UTF-8"> </script>
|
||||
<script src="./swagger-ui-standalone-preset.js" charset="UTF-8"> </script>
|
||||
<script src="./swagger-initializer.js" charset="UTF-8"> </script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,79 @@
|
|||
<!doctype html>
|
||||
<html lang="en-US">
|
||||
<head>
|
||||
<title>Swagger UI: OAuth2 Redirect</title>
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
'use strict';
|
||||
function run () {
|
||||
var oauth2 = window.opener.swaggerUIRedirectOauth2;
|
||||
var sentState = oauth2.state;
|
||||
var redirectUrl = oauth2.redirectUrl;
|
||||
var isValid, qp, arr;
|
||||
|
||||
if (/code|token|error/.test(window.location.hash)) {
|
||||
qp = window.location.hash.substring(1).replace('?', '&');
|
||||
} else {
|
||||
qp = location.search.substring(1);
|
||||
}
|
||||
|
||||
arr = qp.split("&");
|
||||
arr.forEach(function (v,i,_arr) { _arr[i] = '"' + v.replace('=', '":"') + '"';});
|
||||
qp = qp ? JSON.parse('{' + arr.join() + '}',
|
||||
function (key, value) {
|
||||
return key === "" ? value : decodeURIComponent(value);
|
||||
}
|
||||
) : {};
|
||||
|
||||
isValid = qp.state === sentState;
|
||||
|
||||
if ((
|
||||
oauth2.auth.schema.get("flow") === "accessCode" ||
|
||||
oauth2.auth.schema.get("flow") === "authorizationCode" ||
|
||||
oauth2.auth.schema.get("flow") === "authorization_code"
|
||||
) && !oauth2.auth.code) {
|
||||
if (!isValid) {
|
||||
oauth2.errCb({
|
||||
authId: oauth2.auth.name,
|
||||
source: "auth",
|
||||
level: "warning",
|
||||
message: "Authorization may be unsafe, passed state was changed in server. The passed state wasn't returned from auth server."
|
||||
});
|
||||
}
|
||||
|
||||
if (qp.code) {
|
||||
delete oauth2.state;
|
||||
oauth2.auth.code = qp.code;
|
||||
oauth2.callback({auth: oauth2.auth, redirectUrl: redirectUrl});
|
||||
} else {
|
||||
let oauthErrorMsg;
|
||||
if (qp.error) {
|
||||
oauthErrorMsg = "["+qp.error+"]: " +
|
||||
(qp.error_description ? qp.error_description+ ". " : "no accessCode received from the server. ") +
|
||||
(qp.error_uri ? "More info: "+qp.error_uri : "");
|
||||
}
|
||||
|
||||
oauth2.errCb({
|
||||
authId: oauth2.auth.name,
|
||||
source: "auth",
|
||||
level: "error",
|
||||
message: oauthErrorMsg || "[Authorization failed]: no accessCode received from the server."
|
||||
});
|
||||
}
|
||||
} else {
|
||||
oauth2.callback({auth: oauth2.auth, token: qp, isValid: isValid, redirectUrl: redirectUrl});
|
||||
}
|
||||
window.close();
|
||||
}
|
||||
|
||||
if (document.readyState !== 'loading') {
|
||||
run();
|
||||
} else {
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
run();
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,47 @@
|
|||
package swgui
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
//go:embed *.png *.js *.html *.css
|
||||
var fs embed.FS
|
||||
|
||||
const initializerTpl = `
|
||||
window.onload = function() {
|
||||
//<editor-fold desc="Changeable Configuration Block">
|
||||
|
||||
window.ui = SwaggerUIBundle({
|
||||
url: "%s",
|
||||
dom_id: '#swagger-ui',
|
||||
deepLinking: true,
|
||||
presets: [
|
||||
SwaggerUIBundle.presets.apis,
|
||||
SwaggerUIStandalonePreset
|
||||
],
|
||||
plugins: [
|
||||
SwaggerUIBundle.plugins.DownloadUrl
|
||||
],
|
||||
layout: "StandaloneLayout"
|
||||
});
|
||||
|
||||
//</editor-fold>
|
||||
};
|
||||
`
|
||||
|
||||
func Handler(basePath string, openAPISchemaURL string) http.Handler {
|
||||
fileServer := http.StripPrefix(basePath, http.FileServer(http.FS(fs)))
|
||||
swaggerInitializer := []byte(fmt.Sprintf(initializerTpl, openAPISchemaURL))
|
||||
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == http.MethodGet && r.URL.Path == basePath+"/swagger-initializer.js" {
|
||||
w.Header().Set("Content-Type", "text/javascript; charset=utf-8")
|
||||
_, _ = w.Write(swaggerInitializer)
|
||||
return
|
||||
}
|
||||
|
||||
fileServer.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: twhelp-api-v2-deployment
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: twhelp-api-v2
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: twhelp-api-v2
|
||||
spec:
|
||||
containers:
|
||||
- name: twhelp-api-v2
|
||||
image: twhelp
|
||||
args: [serve]
|
||||
env:
|
||||
- name: APP_MODE
|
||||
value: development
|
||||
- name: LOG_LEVEL
|
||||
value: debug
|
||||
- name: DB_CONNECTION_STRING
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: twhelp-secret
|
||||
key: db-connection-string
|
||||
- name: RABBITMQ_CONNECTION_STRING
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: twhelp-secret
|
||||
key: rabbitmq-connection-string
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 100Mi
|
||||
limits:
|
||||
cpu: 200m
|
||||
memory: 300Mi
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: twhelp-api-v2-service
|
||||
spec:
|
||||
ports:
|
||||
- port: 9234
|
||||
targetPort: 9234
|
||||
protocol: TCP
|
||||
name: http
|
||||
selector:
|
||||
app: twhelp-api-v2
|
|
@ -7,6 +7,7 @@ resources:
|
|||
- player-consumer.yml
|
||||
- village-consumer.yml
|
||||
- ennoblement-consumer.yml
|
||||
- api.yml
|
||||
images:
|
||||
- name: twhelp
|
||||
newName: twhelp
|
||||
|
|
Loading…
Reference in New Issue