diff --git a/go.mod b/go.mod
index 04a3fbd..1e178de 100644
--- a/go.mod
+++ b/go.mod
@@ -58,6 +58,9 @@ require (
github.com/redis/rueidis v1.0.66 // indirect
github.com/robfig/cron/v3 v3.0.1 // indirect
github.com/sethvargo/go-retry v0.3.0 // indirect
+ github.com/swaggest/jsonschema-go v0.3.74 // indirect
+ github.com/swaggest/openapi-go v0.2.60 // indirect
+ github.com/swaggest/refl v1.3.1 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.3.0 // indirect
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
@@ -72,6 +75,7 @@ require (
golang.org/x/text v0.30.0 // indirect
golang.org/x/tools v0.38.0 // indirect
google.golang.org/protobuf v1.36.10 // indirect
+ gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
sigs.k8s.io/yaml v1.6.0 // indirect
)
diff --git a/go.sum b/go.sum
index 7dc0990..36687f5 100644
--- a/go.sum
+++ b/go.sum
@@ -213,6 +213,12 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
+github.com/swaggest/jsonschema-go v0.3.74 h1:hkAZBK3RxNWU013kPqj0Q/GHGzYCCm9WcUTnfg2yPp0=
+github.com/swaggest/jsonschema-go v0.3.74/go.mod h1:qp+Ym2DIXHlHzch3HKz50gPf2wJhKOrAB/VYqLS2oJU=
+github.com/swaggest/openapi-go v0.2.60 h1:kglHH/WIfqAglfuWL4tu0LPakqNYySzklUWx06SjSKo=
+github.com/swaggest/openapi-go v0.2.60/go.mod h1:jmFOuYdsWGtHU0BOuILlHZQJxLqHiAE6en+baE+QQUk=
+github.com/swaggest/refl v1.3.1 h1:XGplEkYftR7p9cz1lsiwXMM2yzmOymTE9vneVVpaOh4=
+github.com/swaggest/refl v1.3.1/go.mod h1:4uUVFVfPJ0NSX9FPwMPspeHos9wPFlCMGoPRllUbpvA=
github.com/testcontainers/testcontainers-go v0.39.0 h1:uCUJ5tA+fcxbFAB0uP3pIK3EJ2IjjDUHFSZ1H1UxAts=
github.com/testcontainers/testcontainers-go v0.39.0/go.mod h1:qmHpkG7H5uPf/EvOORKvS6EuDkBUPE3zpVGaH9NL7f8=
github.com/testcontainers/testcontainers-go/modules/redis v0.39.0 h1:p54qELdCx4Gftkxzf44k9RJRRhaO/S5ehP9zo8SUTLM=
@@ -280,6 +286,8 @@ google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/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.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/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=
diff --git a/html/redoc.html b/html/redoc.html
new file mode 100644
index 0000000..d455089
--- /dev/null
+++ b/html/redoc.html
@@ -0,0 +1,24 @@
+
+
+
+ Documentation of OstiweStatus API
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/main.go b/main.go
index 7376355..86da5c5 100644
--- a/main.go
+++ b/main.go
@@ -1,20 +1,34 @@
package main
import (
+ "embed"
+ "fmt"
+ "net/http"
+ "os"
+
"git.ostiwe.com/ostiwe-com/status/migration"
appLog "git.ostiwe.com/ostiwe-com/status/modules/log"
"git.ostiwe.com/ostiwe-com/status/pkg/args"
+ "git.ostiwe.com/ostiwe-com/status/router"
"git.ostiwe.com/ostiwe-com/status/server"
- _ "git.ostiwe.com/ostiwe-com/status/settings"
+ "git.ostiwe.com/ostiwe-com/status/settings"
"github.com/alexflint/go-arg"
+ "github.com/gin-gonic/gin"
)
+//go:embed html
+var htmlFolder embed.FS
+
var appArgs args.AppArgs
func main() {
arg.MustParse(&appArgs)
+ defer appLog.Global.Get(appLog.SYSTEM).Debug("Exit from application")
+
if appArgs.Migration != nil && appArgs.Migration.Create != nil {
+ settings.Init()
+
if err := migration.CreateMigration(appArgs.Migration.Create.Name); err != nil {
panic(err)
}
@@ -23,35 +37,111 @@ func main() {
}
if appArgs.Server != nil {
- migration.RunMigration()
+ settings.Init()
+ migration.RunMigration()
server.Run(appArgs.Server)
+
return
}
- // TODO: Rewrite to use gin router, instead of chi router
- // if appArgs.ServerDocumentation != nil {
- // appLog.Global.Get(appLog.SYSTEM).Info("Collect documentation")
- //
- // docs := router.Documentate()
- // if !appArgs.ServerDocumentation.Plain {
- // chiRouter := chi.NewRouter()
- //
- // err := docs.SetupRoutes(chiRouter, docs)
- // if err != nil {
- // appLog.Global.Get(appLog.SYSTEM).Error(fmt.Sprintf("Setup docs routes error: %v", err))
- // return
- // }
- //
- // appLog.Global.Get(appLog.SYSTEM).Info(fmt.Sprintf("Start documentation server on port: %s", appArgs.ServerDocumentation.Port))
- // err = http.ListenAndServe(fmt.Sprintf(":%s", appArgs.ServerDocumentation.Port), chiRouter)
- // if err != nil {
- // appLog.Global.Get(appLog.SYSTEM).Error(fmt.Sprintf("Startup server error: %v", err))
- // }
- //
- // return
- // }
- // }
+ // TODO: Decompose document generation logic into separate methods
+ // Current code block handles both generation and serving logic - should be separated
+ if appArgs.Docs == nil {
+ return
+ }
- appLog.Global.Get(appLog.SYSTEM).Info("Exit from application")
+ if appArgs.Docs.Generate != nil {
+ documentate, err := router.Documentate()
+ if err != nil {
+ appLog.Global.Get(appLog.SYSTEM).Error(err)
+
+ return
+ }
+
+ var file []byte
+
+ switch appArgs.Docs.Generate.Format {
+ case "json":
+ file, err = documentate.MarshalJSON()
+ case "yaml":
+ file, err = documentate.MarshalYAML()
+ }
+
+ if err != nil {
+ appLog.Global.Get(appLog.SYSTEM).Error(err)
+
+ return
+ }
+
+ if appArgs.Docs.Generate.Out == "stdout" {
+ _, err = os.Stdout.Write(file)
+ if err != nil {
+ appLog.Global.Get(appLog.SYSTEM).Error(err)
+
+ return
+ }
+
+ return
+ }
+
+ err = os.WriteFile(appArgs.Docs.Generate.Out, file, os.ModeAppend)
+ if err != nil {
+ appLog.Global.Get(appLog.SYSTEM).Error(err)
+
+ return
+ }
+
+ return
+ }
+
+ if appArgs.Docs.Serve != nil {
+ documentate, err := router.Documentate()
+ if err != nil {
+ appLog.Global.Get(appLog.SYSTEM).Error(err)
+
+ return
+ }
+ docsJson, err := documentate.MarshalJSON()
+ if err != nil {
+ appLog.Global.Get(appLog.SYSTEM).Error(err)
+
+ return
+ }
+
+ html, err := htmlFolder.ReadFile("html/redoc.html")
+ if err != nil {
+ appLog.Global.Get(appLog.SYSTEM).Error(err)
+
+ return
+ }
+
+ g := gin.New()
+ g.Handle("GET", "/static-doc", func(c *gin.Context) {
+ c.Writer.Header().Add("Content-type", "application/json")
+ _, err = c.Writer.Write(docsJson)
+ if err != nil {
+ c.Writer.WriteHeader(http.StatusInternalServerError)
+
+ return
+ }
+ })
+ g.Handle("GET", "/docs/index.html", func(c *gin.Context) {
+ c.Writer.Header().Add("Content-Type", "text/html")
+ _, err = c.Writer.Write(html)
+ if err != nil {
+ c.Writer.WriteHeader(http.StatusInternalServerError)
+
+ return
+ }
+ })
+
+ if err = g.Run(fmt.Sprintf(":%s", appArgs.Docs.Serve.Port)); err != nil {
+ appLog.Global.Get(appLog.SYSTEM).Error(err)
+
+ return
+ }
+
+ return
+ }
}
diff --git a/modules/jwt/jwt.go b/modules/jwt/jwt.go
index 39df32c..d8511e3 100644
--- a/modules/jwt/jwt.go
+++ b/modules/jwt/jwt.go
@@ -21,7 +21,7 @@ var (
AuthMiddleware *ginJwt.GinJWTMiddleware
)
-func init() {
+func Init() {
jwtPublicKeyPath := os.Getenv("JWT_SIGN_PUBLIC_KEY_PATH")
if !strings.HasPrefix(jwtPublicKeyPath, "/") {
jwtPublicKeyPath = settings.WorkingDir + "/" + jwtPublicKeyPath
diff --git a/pkg/args/args.go b/pkg/args/args.go
index 051093a..6ea16fa 100644
--- a/pkg/args/args.go
+++ b/pkg/args/args.go
@@ -3,7 +3,7 @@ package args
import "git.ostiwe.com/ostiwe-com/status/version"
type ServerCmd struct {
- Port string `arg:"-p,--port" help:"Port to listen on" default:"8080"`
+ Port string `arg:"-p,--port,env:APP_PORT" help:"Port to listen on" default:"8080"`
}
type MigrationCreate struct {
@@ -15,15 +15,23 @@ type Migration struct {
}
type ServerDocumentationCmd struct {
- Port string `arg:"-p,--port" help:"Port to listen on" default:"8081"`
- Plain bool `arg:"--plain" help:"Enable plain text output" default:"true"`
- PlainFormat string `arg:"--plain-format" help:"Set format for output (json, yaml)" default:"yaml"`
+ Port string `arg:"-p,--port,env:APP_PORT_DOCS" help:"Port to listen on" default:"8081"`
+}
+
+type GenerateDocumentationCmd struct {
+ Format string `arg:"--format,-f" help:"Set output format (json, yaml)" default:"yaml"`
+ Out string `arg:"--out,-o" help:"Output file name (or stdout)" default:"stdout"`
+}
+
+type DocsCmd struct {
+ Serve *ServerDocumentationCmd `arg:"subcommand:serve" help:"Generate and serve the documentation server"`
+ Generate *GenerateDocumentationCmd `arg:"subcommand:generate" help:"Generate documentation to file"`
}
type AppArgs struct {
- Server *ServerCmd `arg:"subcommand:server" help:"Start the api server"`
- ServerDocumentation *ServerDocumentationCmd `arg:"subcommand:server-docs" help:"Generate documentation for api server and start documentation server"`
- Migration *Migration `arg:"subcommand:migration" help:"Migration utils"`
+ Server *ServerCmd `arg:"subcommand:server" help:"Start the api server"`
+ Docs *DocsCmd `arg:"subcommand:docs" help:"Generate documentation to file or run documentation server"`
+ Migration *Migration `arg:"subcommand:migration" help:"Migration utils"`
}
func (AppArgs) Version() string {
diff --git a/router/controller/controller.go b/router/controller/controller.go
index 6e26ecd..9edb0cd 100644
--- a/router/controller/controller.go
+++ b/router/controller/controller.go
@@ -2,7 +2,8 @@ package controller
import (
"github.com/gin-gonic/gin"
- "github.com/go-andiamo/chioas"
+ "github.com/swaggest/openapi-go"
+ "github.com/swaggest/openapi-go/openapi3"
)
// SecuredController - means, controller has middlewares
@@ -12,7 +13,7 @@ type SecuredController interface {
}
type DocumentateController interface {
- Documentate() (*chioas.Paths, *chioas.Components)
+ Documentate(r *openapi3.Reflector) openapi.OperationContext
}
type Controller interface {
diff --git a/router/controller/ping/controller.go b/router/controller/ping/controller.go
index ff7a0a8..084c712 100644
--- a/router/controller/ping/controller.go
+++ b/router/controller/ping/controller.go
@@ -6,7 +6,8 @@ import (
"git.ostiwe.com/ostiwe-com/status/modules/log"
"git.ostiwe.com/ostiwe-com/status/router/controller"
"github.com/gin-gonic/gin"
- "github.com/go-andiamo/chioas"
+ "github.com/swaggest/openapi-go"
+ "github.com/swaggest/openapi-go/openapi3"
)
type Controller struct {
@@ -34,29 +35,19 @@ func (c *Controller) Handler() gin.HandlerFunc {
}
}
-func (c *Controller) Documentate() (*chioas.Paths, *chioas.Components) {
- return &chioas.Paths{
- "/ping": chioas.Path{
- Methods: map[string]chioas.Method{
- http.MethodGet: {
- Handler: c.Handler(),
- Responses: chioas.Responses{
- http.StatusOK: {
- ContentType: "plain/text",
- Schema: chioas.Schema{Type: "string"},
- Examples: chioas.Examples{
- {
+func (c *Controller) Documentate(r *openapi3.Reflector) openapi.OperationContext {
+ op, err := r.NewOperationContext(c.Method(), c.Path())
+ if err != nil {
+ return nil
+ }
- Name: "200 OK",
- Value: "pong",
- },
- },
- },
- },
- },
- },
- Tag: "service",
- Comment: "Route for check service is alive",
- },
- }, nil
+ op.SetDescription("Route for check service is alive")
+ op.SetSummary("Server ping")
+
+ op.AddRespStructure("pong", func(cu *openapi.ContentUnit) {
+ cu.ContentType = "plain/text"
+ cu.HTTPStatus = http.StatusOK
+ })
+
+ return op
}
diff --git a/router/server.go b/router/server.go
index 7884efe..8a657a2 100644
--- a/router/server.go
+++ b/router/server.go
@@ -2,9 +2,9 @@ package router
import (
"fmt"
- "maps"
"time"
+ "git.ostiwe.com/ostiwe-com/status/modules/jwt"
"git.ostiwe.com/ostiwe-com/status/modules/log"
"git.ostiwe.com/ostiwe-com/status/router/controller"
"git.ostiwe.com/ostiwe-com/status/router/controller/auth"
@@ -12,7 +12,7 @@ import (
"git.ostiwe.com/ostiwe-com/status/router/controller/service"
"git.ostiwe.com/ostiwe-com/status/version"
"github.com/gin-gonic/gin"
- "github.com/go-andiamo/chioas"
+ "github.com/swaggest/openapi-go/openapi3"
)
func getControllers() []controller.Controller {
@@ -27,6 +27,8 @@ func InitRoutes() *gin.Engine {
log.Global.Get(log.SERVER).Info("Setting up routers")
startTime := time.Now()
+ jwt.Init()
+
r := gin.New()
r.Use(
gin.Recovery(),
@@ -61,27 +63,16 @@ func InitRoutes() *gin.Engine {
return r
}
-func Documentate() chioas.Definition {
+func Documentate() (*openapi3.Spec, error) {
ctrlList := getControllers()
- apiDoc := chioas.Definition{
- AutoHeadMethods: true,
- DocOptions: chioas.DocOptions{
- ServeDocs: true,
- HideHeadMethods: true,
- },
- Info: chioas.Info{
- Version: version.AppVersion(),
- Title: "Status page API Documentation",
- },
- Paths: make(chioas.Paths),
- Components: &chioas.Components{
- Schemas: make(chioas.Schemas, 0),
- Requests: make(chioas.CommonRequests),
- Responses: make(chioas.CommonResponses),
- Examples: make(chioas.Examples, 0),
- Parameters: make(chioas.CommonParameters),
- SecuritySchemes: make(chioas.SecuritySchemes, 0),
- Extensions: make(chioas.Extensions),
+
+ reflector := &openapi3.Reflector{
+ Spec: &openapi3.Spec{
+ Openapi: "3.0.3",
+ Info: openapi3.Info{
+ Title: "OstiweStatus API",
+ Version: version.AppVersion(),
+ },
},
}
@@ -91,25 +82,12 @@ func Documentate() chioas.Definition {
continue
}
- documentatePaths, components := documentated.Documentate()
-
- if documentatePaths != nil {
- for path, pathDoc := range *documentatePaths {
- apiDoc.Paths[path] = pathDoc
- }
+ err := reflector.AddOperation(documentated.Documentate(reflector))
+ if err != nil {
+ return nil, err
}
- if components != nil {
- apiDoc.Components.Schemas = append(apiDoc.Components.Schemas, components.Schemas...)
- apiDoc.Components.Examples = append(apiDoc.Components.Examples, components.Examples...)
- apiDoc.Components.SecuritySchemes = append(apiDoc.Components.SecuritySchemes, components.SecuritySchemes...)
-
- maps.Copy(apiDoc.Components.Requests, components.Requests)
- maps.Copy(apiDoc.Components.Responses, components.Responses)
- maps.Copy(apiDoc.Components.Parameters, components.Parameters)
- maps.Copy(apiDoc.Components.Extensions, components.Extensions)
- }
}
- return apiDoc
+ return reflector.Spec, nil
}
diff --git a/settings/settings.go b/settings/settings.go
index ad15572..5817528 100644
--- a/settings/settings.go
+++ b/settings/settings.go
@@ -17,7 +17,9 @@ var (
func init() {
appLog.SetGlobalManager(appLog.NewManager())
appLog.Global.Put(appLog.SYSTEM, logrus.New())
+}
+func Init() {
var err error
WorkingDir, err = os.Getwd()