init: first steps
This commit is contained in:
6
.env
Normal file
6
.env
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
DATABASE_HOST=
|
||||||
|
DATABASE_PORT=
|
||||||
|
DATABASE_USER=
|
||||||
|
DATABASE_PASS=
|
||||||
|
DATABASE_DB=
|
||||||
|
DATABASE_TZ=Europe/Moscow
|
||||||
55
.gitignore
vendored
Normal file
55
.gitignore
vendored
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
cmake-build-*/
|
||||||
|
*.iws
|
||||||
|
out/
|
||||||
|
.idea_modules/
|
||||||
|
atlassian-ide-plugin.xml
|
||||||
|
com_crashlytics_export_strings.xml
|
||||||
|
crashlytics.properties
|
||||||
|
crashlytics-build.properties
|
||||||
|
fabric.properties
|
||||||
|
.idea/
|
||||||
|
*~
|
||||||
|
.fuse_hidden*
|
||||||
|
.directory
|
||||||
|
.Trash-*
|
||||||
|
.nfs*
|
||||||
|
*.exe
|
||||||
|
*.exe~
|
||||||
|
*.dll
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
*.test
|
||||||
|
*.out
|
||||||
|
go.work
|
||||||
|
go.work.sum
|
||||||
|
.env.*
|
||||||
|
Thumbs.db
|
||||||
|
Thumbs.db:encryptable
|
||||||
|
ehthumbs.db
|
||||||
|
ehthumbs_vista.db
|
||||||
|
*.stackdump
|
||||||
|
[Dd]esktop.ini
|
||||||
|
$RECYCLE.BIN/
|
||||||
|
*.cab
|
||||||
|
*.msi
|
||||||
|
*.msix
|
||||||
|
*.msm
|
||||||
|
*.msp
|
||||||
|
*.lnk
|
||||||
|
.DS_Store
|
||||||
|
.AppleDouble
|
||||||
|
.LSOverride
|
||||||
|
Icon
|
||||||
|
._*
|
||||||
|
.DocumentRevisions-V100
|
||||||
|
.fseventsd
|
||||||
|
.Spotlight-V100
|
||||||
|
.TemporaryItems
|
||||||
|
.Trashes
|
||||||
|
.VolumeIcon.icns
|
||||||
|
.com.apple.timemachine.donotpresent
|
||||||
|
.AppleDB
|
||||||
|
.AppleDesktop
|
||||||
|
Network Trash Folder
|
||||||
|
Temporary Items
|
||||||
|
.apdisk
|
||||||
16
docker-compose.yml
Normal file
16
docker-compose.yml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
services:
|
||||||
|
|
||||||
|
postgresql:
|
||||||
|
image: postgres:latest
|
||||||
|
environment:
|
||||||
|
- POSTGRES_USER=status
|
||||||
|
- POSTGRES_DB=status
|
||||||
|
- POSTGRES_PASSWORD=status
|
||||||
|
- POSTGRES_HOST_AUTH_METHOD=trust
|
||||||
|
volumes:
|
||||||
|
- database_postgres:/var/lib/postgresql/data
|
||||||
|
ports:
|
||||||
|
- "5444:5432"
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
database_postgres:
|
||||||
9
dto/service.go
Normal file
9
dto/service.go
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
package dto
|
||||||
|
|
||||||
|
import "git.ostiwe.com/ostiwe-com/status/model"
|
||||||
|
|
||||||
|
type PublicService struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
Statuses []model.Status `json:"statuses"`
|
||||||
|
}
|
||||||
33
go.mod
Normal file
33
go.mod
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
module git.ostiwe.com/ostiwe-com/status
|
||||||
|
|
||||||
|
go 1.24.2
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/alexflint/go-arg v1.6.0
|
||||||
|
github.com/go-andiamo/chioas v1.16.4
|
||||||
|
github.com/go-chi/chi/v5 v5.2.2
|
||||||
|
github.com/joho/godotenv v1.5.1
|
||||||
|
github.com/sirupsen/logrus v1.9.3
|
||||||
|
go.uber.org/mock v0.5.2
|
||||||
|
gorm.io/driver/postgres v1.6.0
|
||||||
|
gorm.io/gorm v1.30.0
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/alexflint/go-scalar v1.2.0 // indirect
|
||||||
|
github.com/go-andiamo/splitter v1.2.5 // indirect
|
||||||
|
github.com/go-andiamo/urit v1.2.1 // indirect
|
||||||
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||||
|
github.com/jackc/pgx/v5 v5.7.5 // indirect
|
||||||
|
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||||
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
|
github.com/jinzhu/now v1.1.5 // indirect
|
||||||
|
github.com/kr/pretty v0.3.1 // indirect
|
||||||
|
golang.org/x/crypto v0.40.0 // indirect
|
||||||
|
golang.org/x/exp v0.0.0-20250718183923-645b1fa84792 // indirect
|
||||||
|
golang.org/x/sync v0.16.0 // indirect
|
||||||
|
golang.org/x/sys v0.34.0 // indirect
|
||||||
|
golang.org/x/text v0.27.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
)
|
||||||
80
go.sum
Normal file
80
go.sum
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
github.com/alexflint/go-arg v1.6.0 h1:wPP9TwTPO54fUVQl4nZoxbFfKCcy5E6HBCumj1XVRSo=
|
||||||
|
github.com/alexflint/go-arg v1.6.0/go.mod h1:A7vTJzvjoaSTypg4biM5uYNTkJ27SkNTArtYXnlqVO8=
|
||||||
|
github.com/alexflint/go-scalar v1.2.0 h1:WR7JPKkeNpnYIOfHRa7ivM21aWAdHD0gEWHCx+WQBRw=
|
||||||
|
github.com/alexflint/go-scalar v1.2.0/go.mod h1:LoFvNMqS1CPrMVltza4LvnGKhaSpc3oyLEBUZVhhS2o=
|
||||||
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/go-andiamo/chioas v1.16.4 h1:aHtA3KLmfQfHRsGxjYPNjrD8cMR2uTncbjvcxY64B3Q=
|
||||||
|
github.com/go-andiamo/chioas v1.16.4/go.mod h1:5ZZYYuGwlF/amxErKFUu3eXz6hZ5GEYu5vCk+Guw+uc=
|
||||||
|
github.com/go-andiamo/splitter v1.2.5 h1:P3NovWMY2V14TJJSolXBvlOmGSZo3Uz+LtTl2bsV/eY=
|
||||||
|
github.com/go-andiamo/splitter v1.2.5/go.mod h1:8WHU24t9hcMKU5FXDQb1hysSEC/GPuivIp0uKY1J8gw=
|
||||||
|
github.com/go-andiamo/urit v1.2.1 h1:5JHJb+TuzuGvXw9Y/LK/lQltCL2gpgHkCznI2vv9ZeY=
|
||||||
|
github.com/go-andiamo/urit v1.2.1/go.mod h1:9kgXBxUPHFZvXwlOPN1GimDuHl+JCfQuetN5nxBNlpQ=
|
||||||
|
github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8=
|
||||||
|
github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
||||||
|
github.com/go-chi/chi/v5 v5.2.2 h1:CMwsvRVTbXVytCk1Wd72Zy1LAsAh9GxMmSNWLHCG618=
|
||||||
|
github.com/go-chi/chi/v5 v5.2.2/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
||||||
|
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||||
|
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||||
|
github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY=
|
||||||
|
github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw=
|
||||||
|
github.com/jackc/pgx/v5 v5.7.5 h1:JHGfMnQY+IEtGM63d+NGMjoRpysB2JBwDr5fsngwmJs=
|
||||||
|
github.com/jackc/pgx/v5 v5.7.5/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M=
|
||||||
|
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
|
||||||
|
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||||
|
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/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||||
|
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||||
|
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||||
|
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||||
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
|
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
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/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||||
|
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
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.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||||
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
|
go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko=
|
||||||
|
go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o=
|
||||||
|
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
||||||
|
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||||
|
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
|
||||||
|
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
|
||||||
|
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df h1:UA2aFVmmsIlefxMk29Dp2juaUSth8Pyn3Tq5Y5mJGME=
|
||||||
|
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
|
||||||
|
golang.org/x/exp v0.0.0-20250718183923-645b1fa84792 h1:R9PFI6EUdfVKgwKjZef7QIwGcBKu86OEFpJ9nUEP2l4=
|
||||||
|
golang.org/x/exp v0.0.0-20250718183923-645b1fa84792/go.mod h1:A+z0yzpGtvnG90cToK5n2tu8UJVP2XUATh+r+sfOOOc=
|
||||||
|
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
||||||
|
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||||
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
||||||
|
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
|
||||||
|
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
|
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
|
||||||
|
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
|
||||||
|
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.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=
|
||||||
|
gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4=
|
||||||
|
gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo=
|
||||||
|
gorm.io/gorm v1.30.0 h1:qbT5aPv1UH8gI99OsRlvDToLxW5zR7FzS9acZDOZcgs=
|
||||||
|
gorm.io/gorm v1.30.0/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=
|
||||||
100
main.go
Normal file
100
main.go
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"git.ostiwe.com/ostiwe-com/status/model"
|
||||||
|
"git.ostiwe.com/ostiwe-com/status/modules/db"
|
||||||
|
appLog "git.ostiwe.com/ostiwe-com/status/modules/log"
|
||||||
|
"git.ostiwe.com/ostiwe-com/status/router"
|
||||||
|
"git.ostiwe.com/ostiwe-com/status/version"
|
||||||
|
"github.com/alexflint/go-arg"
|
||||||
|
"github.com/go-chi/chi/v5"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
_ "git.ostiwe.com/ostiwe-com/status/settings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ServerCmd struct {
|
||||||
|
Port string `arg:"-p,--port" help:"Port to listen on" default:"8080"`
|
||||||
|
}
|
||||||
|
|
||||||
|
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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var args appArgs
|
||||||
|
|
||||||
|
func (appArgs) Version() string {
|
||||||
|
return version.AppVersion()
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
arg.MustParse(&args)
|
||||||
|
|
||||||
|
connect, err := db.Connect()
|
||||||
|
if err != nil {
|
||||||
|
appLog.Global.Get(appLog.SYSTEM).Error(fmt.Sprintf("Startup server error, failed connect to database: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
db.SetGlobal(connect)
|
||||||
|
appLog.Global.Get(appLog.SYSTEM).Info("Run db migration")
|
||||||
|
if err = runMigrate(); err != nil {
|
||||||
|
appLog.Global.Get(appLog.SYSTEM).Error(fmt.Sprintf("Migration failed, error: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if args.Server != nil {
|
||||||
|
appLog.Global.Put(appLog.SERVER, logrus.New())
|
||||||
|
appLog.Global.Get(appLog.SERVER).Info("Startup server on port: ", args.Server.Port)
|
||||||
|
|
||||||
|
err := http.ListenAndServe(fmt.Sprintf(":%s", args.Server.Port), router.InitRoutes())
|
||||||
|
if err != nil {
|
||||||
|
appLog.Global.Get(appLog.SERVER).Error(fmt.Sprintf("Startup server error: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if args.ServerDocumentation != nil {
|
||||||
|
appLog.Global.Get(appLog.SYSTEM).Info("Collect documentation")
|
||||||
|
|
||||||
|
docs := router.Documentate()
|
||||||
|
if !args.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", args.ServerDocumentation.Port))
|
||||||
|
err = http.ListenAndServe(fmt.Sprintf(":%s", args.ServerDocumentation.Port), chiRouter)
|
||||||
|
if err != nil {
|
||||||
|
appLog.Global.Get(appLog.SYSTEM).Error(fmt.Sprintf("Startup server error: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
appLog.Global.Get(appLog.SYSTEM).Info("Exit from application")
|
||||||
|
}
|
||||||
|
|
||||||
|
func runMigrate() error {
|
||||||
|
return db.Global.AutoMigrate(
|
||||||
|
model.Service{},
|
||||||
|
model.Status{},
|
||||||
|
)
|
||||||
|
}
|
||||||
34
model/service.go
Normal file
34
model/service.go
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
type HTTPConfig struct {
|
||||||
|
Authorization string `json:"authorization"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ServiceTypeCheckConfig struct {
|
||||||
|
Version string `json:"version"`
|
||||||
|
HTTPConfig *HTTPConfig `json:"http_config"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Service struct {
|
||||||
|
// Unique ID for entity
|
||||||
|
ID int `gorm:"primary_key;auto_increment" json:"id"`
|
||||||
|
// Human-readable service name
|
||||||
|
Name string `gorm:"size:255;not null" json:"name"`
|
||||||
|
// Human-readable service description
|
||||||
|
Description string `gorm:"size:255" json:"description"`
|
||||||
|
PublicDescription string `gorm:"size:255" json:"public_description"`
|
||||||
|
Public *bool `gorm:"default:false" json:"public"`
|
||||||
|
// Host to check, for example 192.168.1.44
|
||||||
|
Host string `gorm:"size:255;not null" json:"host"`
|
||||||
|
// Port to check, for example 5432 (postgresql)
|
||||||
|
Port *int `gorm:"default:null" json:"port"`
|
||||||
|
// Type for check, for now is TCP or HTTP
|
||||||
|
Type string `gorm:"size:255;not null" json:"type"`
|
||||||
|
TypeConfig *ServiceTypeCheckConfig `gorm:"serializer:json" json:"type_config"`
|
||||||
|
|
||||||
|
Statuses []Status `gorm:"foreignkey:ServiceID" json:"statuses"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (Service) TableName() string {
|
||||||
|
return "service"
|
||||||
|
}
|
||||||
25
model/status.go
Normal file
25
model/status.go
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Status struct {
|
||||||
|
ID int `gorm:"primary_key;auto_increment" json:"-"`
|
||||||
|
ServiceID int `gorm:"one" json:"-"`
|
||||||
|
Status string `gorm:"size:255;not null" json:"status"`
|
||||||
|
Description *string `gorm:"size:255" json:"description"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (Status) TableName() string {
|
||||||
|
return "status"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Status) BeforeCreate(*gorm.DB) error {
|
||||||
|
s.CreatedAt = time.Now()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
53
modules/db/db.go
Normal file
53
modules/db/db.go
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
package db
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
appLog "git.ostiwe.com/ostiwe-com/status/modules/log"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
"gorm.io/driver/postgres"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"gorm.io/gorm/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
var Global *gorm.DB
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
appLog.Global.Put(appLog.DATABASE, logrus.New())
|
||||||
|
}
|
||||||
|
|
||||||
|
func Connect() (*gorm.DB, error) {
|
||||||
|
dsn := fmt.Sprintf(
|
||||||
|
"host=%s user=%s password=%s dbname=%s port=%s sslmode=disable TimeZone=%s",
|
||||||
|
os.Getenv("DATABASE_HOST"),
|
||||||
|
os.Getenv("DATABASE_USER"),
|
||||||
|
os.Getenv("DATABASE_PASS"),
|
||||||
|
os.Getenv("DATABASE_DB"),
|
||||||
|
os.Getenv("DATABASE_PORT"),
|
||||||
|
os.Getenv("DATABASE_TZ"),
|
||||||
|
)
|
||||||
|
|
||||||
|
newLogger := logger.New(
|
||||||
|
appLog.Global.Get(appLog.DATABASE),
|
||||||
|
logger.Config{
|
||||||
|
SlowThreshold: time.Second, // Slow SQL threshold
|
||||||
|
LogLevel: logger.Info, // Log level
|
||||||
|
IgnoreRecordNotFoundError: true, // Ignore ErrRecordNotFound error for logger
|
||||||
|
ParameterizedQueries: false, // Don't include params in the SQL log
|
||||||
|
Colorful: false, // Disable color
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
return gorm.Open(
|
||||||
|
postgres.Open(dsn),
|
||||||
|
&gorm.Config{
|
||||||
|
Logger: newLogger,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetGlobal(bd *gorm.DB) {
|
||||||
|
Global = bd
|
||||||
|
}
|
||||||
28
modules/log/formatter.go
Normal file
28
modules/log/formatter.go
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
package log
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TextFormatter struct {
|
||||||
|
logrusFormatter logrus.TextFormatter
|
||||||
|
prefix []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTextFormatter(prefix string) *TextFormatter {
|
||||||
|
return &TextFormatter{
|
||||||
|
logrusFormatter: logrus.TextFormatter{},
|
||||||
|
prefix: []byte(fmt.Sprintf("[%s] ", prefix)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TextFormatter) Format(item *logrus.Entry) ([]byte, error) {
|
||||||
|
original, err := t.logrusFormatter.Format(item)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return append(t.prefix, original...), nil
|
||||||
|
}
|
||||||
47
modules/log/manager.go
Normal file
47
modules/log/manager.go
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
package log
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
SERVER = "server"
|
||||||
|
SYSTEM = "system"
|
||||||
|
DATABASE = "database"
|
||||||
|
)
|
||||||
|
|
||||||
|
var Global *LoggerManager
|
||||||
|
|
||||||
|
func SetGlobalManager(manager *LoggerManager) {
|
||||||
|
Global = manager
|
||||||
|
}
|
||||||
|
|
||||||
|
type LoggerManager struct {
|
||||||
|
loggers map[string]*logrus.Logger
|
||||||
|
mu sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *LoggerManager) Get(name string) *logrus.Logger {
|
||||||
|
if logger, ok := m.loggers[name]; ok {
|
||||||
|
return logger
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *LoggerManager) Put(name string, logger *logrus.Logger) {
|
||||||
|
m.mu.Lock()
|
||||||
|
defer m.mu.Unlock()
|
||||||
|
|
||||||
|
logger.Formatter = NewTextFormatter(name)
|
||||||
|
|
||||||
|
m.loggers[name] = logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewManager() *LoggerManager {
|
||||||
|
return &LoggerManager{
|
||||||
|
loggers: make(map[string]*logrus.Logger),
|
||||||
|
}
|
||||||
|
}
|
||||||
118
pkg/http/errors.go
Normal file
118
pkg/http/errors.go
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/go-chi/chi/v5/middleware"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ResponseErr struct {
|
||||||
|
Message string `json:"message"`
|
||||||
|
Trace string `json:"trace"`
|
||||||
|
Details map[string]any `json:"details"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ResponseErrReadyToSend interface {
|
||||||
|
Send(http.ResponseWriter) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type ResponseErrBuilder interface {
|
||||||
|
WithDetails(map[string]any) ResponseErrBuilder
|
||||||
|
WithMessage(string) ResponseErrBuilder
|
||||||
|
WithStatusCode(int) ResponseErrBuilder
|
||||||
|
WithTrace(string) ResponseErrBuilder
|
||||||
|
Ready() ResponseErrReadyToSend
|
||||||
|
Send(http.ResponseWriter, *http.Request) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type responseErrBuilder struct {
|
||||||
|
details map[string]any
|
||||||
|
message string
|
||||||
|
status int
|
||||||
|
trace string
|
||||||
|
|
||||||
|
ready *ResponseErr
|
||||||
|
}
|
||||||
|
|
||||||
|
type readyResponseErr struct {
|
||||||
|
builder responseErrBuilder
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r readyResponseErr) Send(response http.ResponseWriter) error {
|
||||||
|
format, err := json.Marshal(r.builder.ready)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.builder.status == 0 {
|
||||||
|
r.builder.status = http.StatusInternalServerError
|
||||||
|
}
|
||||||
|
|
||||||
|
response.WriteHeader(r.builder.status)
|
||||||
|
|
||||||
|
_, err = response.Write(format)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r responseErrBuilder) WithTrace(s string) ResponseErrBuilder {
|
||||||
|
r.trace = s
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r responseErrBuilder) WithStatusCode(i int) ResponseErrBuilder {
|
||||||
|
r.status = i
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewResponseErrBuilder() ResponseErrBuilder {
|
||||||
|
return &responseErrBuilder{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r responseErrBuilder) WithDetails(m map[string]any) ResponseErrBuilder {
|
||||||
|
r.details = m
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r responseErrBuilder) WithMessage(s string) ResponseErrBuilder {
|
||||||
|
r.message = s
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r responseErrBuilder) Ready() ResponseErrReadyToSend {
|
||||||
|
r.ready = &ResponseErr{
|
||||||
|
Message: r.message,
|
||||||
|
Trace: r.trace,
|
||||||
|
Details: r.details,
|
||||||
|
}
|
||||||
|
|
||||||
|
return readyResponseErr{
|
||||||
|
builder: r,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r responseErrBuilder) Send(response http.ResponseWriter, request *http.Request) error {
|
||||||
|
if r.ready == nil {
|
||||||
|
r.ready = &ResponseErr{
|
||||||
|
Message: r.message,
|
||||||
|
Trace: middleware.GetReqID(request.Context()),
|
||||||
|
Details: r.details,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
format, err := json.Marshal(r.ready)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.status == 0 {
|
||||||
|
r.status = http.StatusInternalServerError
|
||||||
|
}
|
||||||
|
|
||||||
|
response.WriteHeader(r.status)
|
||||||
|
|
||||||
|
_, err = response.Write(format)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
46
pkg/http/extract.go
Normal file
46
pkg/http/extract.go
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ExtractLimitOffset(r *http.Request) (int, int, ResponseErrReadyToSend) {
|
||||||
|
limit, offset := r.URL.Query().Get("limit"), r.URL.Query().Get("offset")
|
||||||
|
|
||||||
|
if limit == "" {
|
||||||
|
limit = "20"
|
||||||
|
}
|
||||||
|
|
||||||
|
if offset == "" {
|
||||||
|
offset = "0"
|
||||||
|
}
|
||||||
|
|
||||||
|
limitInt, err := strconv.Atoi(limit)
|
||||||
|
if err != nil {
|
||||||
|
writeErr := NewResponseErrBuilder().
|
||||||
|
WithMessage("Incorrect limit parameter").
|
||||||
|
WithDetails(map[string]interface{}{
|
||||||
|
"err": err.Error(),
|
||||||
|
}).
|
||||||
|
WithStatusCode(http.StatusBadRequest).
|
||||||
|
Ready()
|
||||||
|
|
||||||
|
return 0, 0, writeErr
|
||||||
|
}
|
||||||
|
|
||||||
|
offsetInt, err := strconv.Atoi(offset)
|
||||||
|
if err != nil {
|
||||||
|
writeErr := NewResponseErrBuilder().
|
||||||
|
WithMessage("Incorrect offset parameter").
|
||||||
|
WithDetails(map[string]interface{}{
|
||||||
|
"err": err.Error(),
|
||||||
|
}).
|
||||||
|
WithStatusCode(http.StatusBadRequest).
|
||||||
|
Ready()
|
||||||
|
|
||||||
|
return 0, 0, writeErr
|
||||||
|
}
|
||||||
|
|
||||||
|
return limitInt, offsetInt, nil
|
||||||
|
}
|
||||||
19
pkg/http/response.go
Normal file
19
pkg/http/response.go
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func JSON(w http.ResponseWriter, i any, httpCode int) error {
|
||||||
|
response, err := json.Marshal(i)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(httpCode)
|
||||||
|
_, err = w.Write(response)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
57
repository/mocks/service.go
Normal file
57
repository/mocks/service.go
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
// Code generated by MockGen. DO NOT EDIT.
|
||||||
|
// Source: service.go
|
||||||
|
//
|
||||||
|
// Generated by this command:
|
||||||
|
//
|
||||||
|
// mockgen -source=service.go -destination=mocks/service.go
|
||||||
|
//
|
||||||
|
|
||||||
|
// Package mock_repository is a generated GoMock package.
|
||||||
|
package mock_repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
context "context"
|
||||||
|
reflect "reflect"
|
||||||
|
|
||||||
|
model "git.ostiwe.com/ostiwe-com/status/model"
|
||||||
|
gomock "go.uber.org/mock/gomock"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MockService is a mock of Service interface.
|
||||||
|
type MockService struct {
|
||||||
|
ctrl *gomock.Controller
|
||||||
|
recorder *MockServiceMockRecorder
|
||||||
|
isgomock struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockServiceMockRecorder is the mock recorder for MockService.
|
||||||
|
type MockServiceMockRecorder struct {
|
||||||
|
mock *MockService
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMockService creates a new mock instance.
|
||||||
|
func NewMockService(ctrl *gomock.Controller) *MockService {
|
||||||
|
mock := &MockService{ctrl: ctrl}
|
||||||
|
mock.recorder = &MockServiceMockRecorder{mock}
|
||||||
|
return mock
|
||||||
|
}
|
||||||
|
|
||||||
|
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||||
|
func (m *MockService) EXPECT() *MockServiceMockRecorder {
|
||||||
|
return m.recorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// All mocks base method.
|
||||||
|
func (m *MockService) All(ctx context.Context, limit, offset int) ([]model.Service, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "All", ctx, limit, offset)
|
||||||
|
ret0, _ := ret[0].([]model.Service)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// All indicates an expected call of All.
|
||||||
|
func (mr *MockServiceMockRecorder) All(ctx, limit, offset any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "All", reflect.TypeOf((*MockService)(nil).All), ctx, limit, offset)
|
||||||
|
}
|
||||||
7
repository/repository.go
Normal file
7
repository/repository.go
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package repository
|
||||||
|
|
||||||
|
import "gorm.io/gorm"
|
||||||
|
|
||||||
|
type repository struct {
|
||||||
|
db *gorm.DB
|
||||||
|
}
|
||||||
50
repository/service.go
Normal file
50
repository/service.go
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
package repository
|
||||||
|
|
||||||
|
//go:generate mockgen -source=$GOFILE -destination=mocks/$GOFILE
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.ostiwe.com/ostiwe-com/status/model"
|
||||||
|
"git.ostiwe.com/ostiwe-com/status/modules/db"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Service interface {
|
||||||
|
All(ctx context.Context, limit, offset int, publicOnly bool) ([]model.Service, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type service struct {
|
||||||
|
repository
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewServiceRepository() Service {
|
||||||
|
return &service{
|
||||||
|
repository{db: db.Global},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *service) All(ctx context.Context, limit, offset int, publicOnly bool) ([]model.Service, error) {
|
||||||
|
items := make([]model.Service, 0)
|
||||||
|
|
||||||
|
query := s.db.
|
||||||
|
WithContext(ctx).
|
||||||
|
Preload("Statuses", func(db *gorm.DB) *gorm.DB {
|
||||||
|
return db.
|
||||||
|
Where("created_at > ?", time.Now().Truncate(24*time.Hour)).
|
||||||
|
Order("created_at desc")
|
||||||
|
})
|
||||||
|
|
||||||
|
if publicOnly {
|
||||||
|
query = query.Where("public = ?", true)
|
||||||
|
}
|
||||||
|
|
||||||
|
query = query.
|
||||||
|
Limit(limit).
|
||||||
|
Offset(offset).
|
||||||
|
Find(&items)
|
||||||
|
|
||||||
|
return items, query.
|
||||||
|
Error
|
||||||
|
}
|
||||||
12
router/controller/controller.go
Normal file
12
router/controller/controller.go
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/go-andiamo/chioas"
|
||||||
|
"github.com/go-chi/chi/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Controller interface {
|
||||||
|
New() Controller
|
||||||
|
Group(r chi.Router)
|
||||||
|
Documentate() (*chioas.Paths, *chioas.Components)
|
||||||
|
}
|
||||||
57
router/controller/ping/controller.go
Normal file
57
router/controller/ping/controller.go
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
package ping
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"git.ostiwe.com/ostiwe-com/status/modules/log"
|
||||||
|
"git.ostiwe.com/ostiwe-com/status/router/controller"
|
||||||
|
"github.com/go-andiamo/chioas"
|
||||||
|
"github.com/go-chi/chi/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Controller struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (Controller) New() controller.Controller {
|
||||||
|
return &Controller{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) Group(r chi.Router) {
|
||||||
|
|
||||||
|
r.Get("/ping", c.PingHandler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) PingHandler(w http.ResponseWriter, _ *http.Request) {
|
||||||
|
_, err := w.Write([]byte("pong"))
|
||||||
|
if err != nil {
|
||||||
|
log.Global.Get(log.SERVER).Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) Documentate() (*chioas.Paths, *chioas.Components) {
|
||||||
|
return &chioas.Paths{
|
||||||
|
"/ping": chioas.Path{
|
||||||
|
Methods: map[string]chioas.Method{
|
||||||
|
http.MethodGet: {
|
||||||
|
Handler: c.PingHandler,
|
||||||
|
Responses: chioas.Responses{
|
||||||
|
http.StatusOK: {
|
||||||
|
ContentType: "plain/text",
|
||||||
|
Schema: chioas.Schema{Type: "string"},
|
||||||
|
Examples: chioas.Examples{
|
||||||
|
{
|
||||||
|
|
||||||
|
Name: "200 OK",
|
||||||
|
Value: "pong",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Tag: "service",
|
||||||
|
Comment: "Route for check service is alive",
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
106
router/controller/service/controller.go
Normal file
106
router/controller/service/controller.go
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"git.ostiwe.com/ostiwe-com/status/modules/log"
|
||||||
|
http2 "git.ostiwe.com/ostiwe-com/status/pkg/http"
|
||||||
|
"git.ostiwe.com/ostiwe-com/status/repository"
|
||||||
|
"git.ostiwe.com/ostiwe-com/status/router/controller"
|
||||||
|
"git.ostiwe.com/ostiwe-com/status/transform"
|
||||||
|
"github.com/go-andiamo/chioas"
|
||||||
|
"github.com/go-chi/chi/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Controller struct {
|
||||||
|
serviceRepository repository.Service
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Controller) New() controller.Controller {
|
||||||
|
return &Controller{
|
||||||
|
serviceRepository: repository.NewServiceRepository(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) Group(r chi.Router) {
|
||||||
|
c.public(r)
|
||||||
|
c.internal(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) public(r chi.Router) {
|
||||||
|
r.Get("/api/public/service", c.GetAllServicesPublic)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) internal(r chi.Router) {
|
||||||
|
r.Get("/api/v1/service", c.GetAllServices)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) GetAllServicesPublic(w http.ResponseWriter, r *http.Request) {
|
||||||
|
limit, offset, errReady := http2.ExtractLimitOffset(r)
|
||||||
|
if errReady != nil {
|
||||||
|
err := errReady.Send(w)
|
||||||
|
if err != nil {
|
||||||
|
log.Global.Get(log.SERVER).Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
items, err := c.serviceRepository.All(r.Context(), limit, offset, true)
|
||||||
|
if err != nil {
|
||||||
|
log.Global.Get(log.SERVER).Error(err)
|
||||||
|
|
||||||
|
writeErr := http2.NewResponseErrBuilder().
|
||||||
|
WithMessage("Fetch service error").
|
||||||
|
WithStatusCode(http.StatusInternalServerError).
|
||||||
|
Send(w, r)
|
||||||
|
if writeErr != nil {
|
||||||
|
log.Global.Get(log.SERVER).Error(writeErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = http2.JSON(w, transform.PublicServices(items...), http.StatusOK)
|
||||||
|
if err != nil {
|
||||||
|
log.Global.Get(log.SERVER).Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) GetAllServices(w http.ResponseWriter, r *http.Request) {
|
||||||
|
limit, offset, errReady := http2.ExtractLimitOffset(r)
|
||||||
|
if errReady != nil {
|
||||||
|
err := errReady.Send(w)
|
||||||
|
if err != nil {
|
||||||
|
log.Global.Get(log.SERVER).Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
items, err := c.serviceRepository.All(r.Context(), limit, offset, false)
|
||||||
|
if err != nil {
|
||||||
|
log.Global.Get(log.SERVER).Error(err)
|
||||||
|
|
||||||
|
writeErr := http2.NewResponseErrBuilder().
|
||||||
|
WithMessage("Fetch service error").
|
||||||
|
WithStatusCode(http.StatusInternalServerError).
|
||||||
|
Send(w, r)
|
||||||
|
if writeErr != nil {
|
||||||
|
log.Global.Get(log.SERVER).Error(writeErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = http2.JSON(w, items, http.StatusOK)
|
||||||
|
if err != nil {
|
||||||
|
log.Global.Get(log.SERVER).Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) Documentate() (*chioas.Paths, *chioas.Components) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
99
router/init.go
Normal file
99
router/init.go
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
package router
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"maps"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.ostiwe.com/ostiwe-com/status/modules/log"
|
||||||
|
"git.ostiwe.com/ostiwe-com/status/router/controller"
|
||||||
|
"git.ostiwe.com/ostiwe-com/status/router/controller/ping"
|
||||||
|
"git.ostiwe.com/ostiwe-com/status/router/controller/service"
|
||||||
|
"git.ostiwe.com/ostiwe-com/status/version"
|
||||||
|
"github.com/go-andiamo/chioas"
|
||||||
|
"github.com/go-chi/chi/v5"
|
||||||
|
"github.com/go-chi/chi/v5/middleware"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getControllers() []controller.Controller {
|
||||||
|
return []controller.Controller{
|
||||||
|
ping.Controller{}.New(),
|
||||||
|
service.Controller{}.New(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func InitRoutes() *chi.Mux {
|
||||||
|
log.Global.Get(log.SERVER).Info("Setting up routers")
|
||||||
|
startTime := time.Now()
|
||||||
|
|
||||||
|
httpLogger := middleware.RequestLogger(&middleware.DefaultLogFormatter{Logger: log.Global.Get(log.SERVER), NoColor: false})
|
||||||
|
|
||||||
|
r := chi.NewRouter()
|
||||||
|
r.Use(httpLogger)
|
||||||
|
r.Use(
|
||||||
|
middleware.RequestID,
|
||||||
|
middleware.RealIP,
|
||||||
|
middleware.StripSlashes,
|
||||||
|
middleware.Recoverer,
|
||||||
|
middleware.CleanPath,
|
||||||
|
middleware.Timeout(15*time.Second),
|
||||||
|
)
|
||||||
|
|
||||||
|
ctrlList := getControllers()
|
||||||
|
for _, ctrl := range ctrlList {
|
||||||
|
r.Group(ctrl.Group)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Global.Get(log.SERVER).Info(fmt.Sprintf("Initialized %d routers", len(ctrlList)))
|
||||||
|
log.Global.Get(log.SERVER).Info(fmt.Sprintf("Setting up routers is done for %dms, start server", time.Since(startTime).Milliseconds()))
|
||||||
|
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func Documentate() chioas.Definition {
|
||||||
|
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),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ctrl := range ctrlList {
|
||||||
|
documentatePaths, components := ctrl.Documentate()
|
||||||
|
|
||||||
|
if documentatePaths != nil {
|
||||||
|
for path, pathDoc := range *documentatePaths {
|
||||||
|
apiDoc.Paths[path] = pathDoc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
48
settings/settings.go
Normal file
48
settings/settings.go
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
package settings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
appLog "git.ostiwe.com/ostiwe-com/status/modules/log"
|
||||||
|
"github.com/joho/godotenv"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
AppVersion string
|
||||||
|
AppStartTime time.Time
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
appLog.SetGlobalManager(appLog.NewManager())
|
||||||
|
appLog.Global.Put(appLog.SYSTEM, logrus.New())
|
||||||
|
|
||||||
|
if AppVersion == "" {
|
||||||
|
AppVersion = "dev"
|
||||||
|
}
|
||||||
|
|
||||||
|
AppStartTime = time.Now()
|
||||||
|
|
||||||
|
env := os.Getenv("APP_ENV")
|
||||||
|
if env == "" {
|
||||||
|
env = "development"
|
||||||
|
}
|
||||||
|
|
||||||
|
appLog.Global.Get(appLog.SYSTEM).Info("Load environment variables for ", env, " env")
|
||||||
|
|
||||||
|
err := godotenv.Load()
|
||||||
|
if err != nil {
|
||||||
|
appLog.Global.Get(appLog.SYSTEM).Errorf("Error loading .env file %v", err)
|
||||||
|
appLog.Global.Get(appLog.SYSTEM).Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
appLog.Global.Get(appLog.SYSTEM).Info("Loaded .env file")
|
||||||
|
|
||||||
|
appLog.Global.Get(appLog.SYSTEM).Info("Try to load .env.local file")
|
||||||
|
if err = godotenv.Overload(".env.local"); err != nil {
|
||||||
|
appLog.Global.Get(appLog.SYSTEM).Info("Failed to load .env.local file")
|
||||||
|
} else {
|
||||||
|
appLog.Global.Get(appLog.SYSTEM).Info("Loaded .env.local file")
|
||||||
|
}
|
||||||
|
}
|
||||||
24
transform/service.go
Normal file
24
transform/service.go
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
package transform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.ostiwe.com/ostiwe-com/status/dto"
|
||||||
|
"git.ostiwe.com/ostiwe-com/status/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
func PublicServices(items ...model.Service) []dto.PublicService {
|
||||||
|
result := make([]dto.PublicService, 0, len(items))
|
||||||
|
|
||||||
|
for _, item := range items {
|
||||||
|
result = append(result, PublicService(item))
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func PublicService(item model.Service) dto.PublicService {
|
||||||
|
return dto.PublicService{
|
||||||
|
Name: item.Name,
|
||||||
|
Description: item.Description,
|
||||||
|
Statuses: item.Statuses,
|
||||||
|
}
|
||||||
|
}
|
||||||
5
version/version.go
Normal file
5
version/version.go
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
package version
|
||||||
|
|
||||||
|
func AppVersion() string {
|
||||||
|
return "0.0.1"
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user