MVP
This commit is contained in:
commit
1f4a477077
|
@ -0,0 +1,92 @@
|
|||
|
||||
|
||||
_build
|
||||
.run-data
|
||||
_run-data
|
||||
|
||||
DOCKER_GIT_INFO
|
||||
|
||||
.swaggobin
|
||||
|
||||
##############
|
||||
|
||||
|
||||
.idea/**/workspace.xml
|
||||
.idea/**/tasks.xml
|
||||
.idea/**/usage.statistics.xml
|
||||
.idea/**/dictionaries
|
||||
.idea/**/shelf
|
||||
.idea/**/aws.xml
|
||||
.idea/**/contentModel.xml
|
||||
.idea/**/dataSources/
|
||||
.idea/**/dataSources.ids
|
||||
.idea/**/dataSources.local.xml
|
||||
.idea/**/sqlDataSources.xml
|
||||
.idea/**/dynamic.xml
|
||||
.idea/**/uiDesigner.xml
|
||||
.idea/**/dbnavigator.xml
|
||||
.idea/**/gradle.xml
|
||||
.idea/**/libraries
|
||||
.idea/**/mongoSettings.xml
|
||||
.idea/**/sonarlint/
|
||||
.idea/**/sonarIssues.xml
|
||||
.idea/**/markdown-navigator.xml
|
||||
.idea/**/markdown-navigator-enh.xml
|
||||
.idea/**/markdown-navigator/
|
||||
.idea/**/azureSettings.xml
|
||||
|
||||
.idea/replstate.xml
|
||||
.idea/sonarlint/
|
||||
.idea/httpRequests
|
||||
.idea/caches/build_file_checksums.ser
|
||||
.idea/$CACHE_FILE$
|
||||
.idea/codestream.xml
|
||||
|
||||
.idea_modules/
|
||||
|
||||
|
||||
|
||||
|
||||
cmake-build-*/
|
||||
*.iws
|
||||
out/
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
|
||||
|
||||
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
fabric.properties
|
||||
|
||||
|
||||
|
||||
|
||||
*~
|
||||
.fuse_hidden*
|
||||
.directory
|
||||
.Trash-*
|
||||
.nfs*
|
||||
|
||||
|
||||
|
||||
|
||||
.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
|
||||
*.icloud
|
|
@ -0,0 +1,40 @@
|
|||
# https://golangci-lint.run/usage/configuration/
|
||||
|
||||
run:
|
||||
go: '1.19'
|
||||
|
||||
linters:
|
||||
enable-all: true
|
||||
disable:
|
||||
- golint # deprecated
|
||||
- exhaustivestruct # deprecated
|
||||
- deadcode # deprecated
|
||||
- scopelint # deprecated
|
||||
- structcheck # deprecated
|
||||
- varcheck # deprecated
|
||||
- nosnakecase # deprecated
|
||||
- maligned # deprecated
|
||||
- interfacer # deprecated
|
||||
- ifshort # deprecated
|
||||
- dupl # (i disagree)
|
||||
- ireturn # (i disagree)
|
||||
- wrapcheck # (waiting for bferr)
|
||||
- goerr113 # (waiting for bferr)
|
||||
- varnamelen # (too many false-positives)
|
||||
- gomnd # (i disagree)
|
||||
- depguard # (not configured)
|
||||
- gofumpt # (we do not use gofumpt)
|
||||
- gci # (we do no use gci)
|
||||
- lll # (i disagree)
|
||||
- gochecknoglobals # (i disagree)
|
||||
|
||||
issues:
|
||||
exclude-rules:
|
||||
- path: api/handler/.*.go
|
||||
linters:
|
||||
- funlen
|
||||
|
||||
linters-settings:
|
||||
tagalign:
|
||||
align: true
|
||||
sort: false
|
|
@ -0,0 +1,8 @@
|
|||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
|
@ -0,0 +1,11 @@
|
|||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="LanguageDetectionInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
|
||||
<option name="processCode" value="true" />
|
||||
<option name="processLiterals" value="true" />
|
||||
<option name="processComments" value="true" />
|
||||
</inspection_tool>
|
||||
</profile>
|
||||
</component>
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="Go" enabled="true" />
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/locbunny.iml" filepath="$PROJECT_DIR$/.idea/locbunny.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
|
@ -0,0 +1,25 @@
|
|||
|
||||
|
||||
FROM golang:1-alpine AS builder
|
||||
|
||||
RUN apk add --no-cache tzdata ca-certificates openssl make git tar coreutils bash
|
||||
|
||||
COPY . /buildsrc
|
||||
|
||||
RUN cd /buildsrc && make build
|
||||
|
||||
|
||||
|
||||
FROM alpine:latest
|
||||
|
||||
RUN apk add --no-cache tzdata
|
||||
|
||||
COPY --from=builder /buildsrc/_build/bunny_backend /app/server
|
||||
|
||||
RUN mkdir /data
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
EXPOSE 80
|
||||
|
||||
CMD ["/app/server"]
|
|
@ -0,0 +1,95 @@
|
|||
DOCKER_REPO=registry.blackforestbytes.com
|
||||
DOCKER_NAME=mikescher/locbunny
|
||||
|
||||
NAMESPACE=$(shell git rev-parse --abbrev-ref HEAD)
|
||||
HASH=$(shell git rev-parse HEAD)
|
||||
|
||||
SWAGGO_VERSION=v1.8.12
|
||||
SWAGGO=github.com/swaggo/swag/cmd/swag@$(SWAGGO_VERSION)
|
||||
|
||||
.PHONY: fmt swagger clean run build-docker run-docker-local inspect-docker push-docker lint
|
||||
|
||||
build: enums ids swagger fmt
|
||||
mkdir -p _build
|
||||
rm -f ./_build/bunny_backend
|
||||
go build -v -buildvcs=false -o _build/bunny_backend ./cmd/server
|
||||
|
||||
enums:
|
||||
go generate models/enums.go
|
||||
|
||||
ids:
|
||||
go generate models/ids.go
|
||||
|
||||
run: build
|
||||
mkdir -p .run-data
|
||||
sudo _build/bunny_backend
|
||||
|
||||
gow:
|
||||
# go install github.com/mitranim/gow@latest
|
||||
gow -c run blackforestbytes.com/locbunny/cmd/server
|
||||
|
||||
dgi:
|
||||
[ ! -f "DOCKER_GIT_INFO" ] || rm DOCKER_GIT_INFO
|
||||
echo -n "VCSTYPE=" >> DOCKER_GIT_INFO ; echo "git" >> DOCKER_GIT_INFO
|
||||
echo -n "BRANCH=" >> DOCKER_GIT_INFO ; git rev-parse --abbrev-ref HEAD >> DOCKER_GIT_INFO
|
||||
echo -n "HASH=" >> DOCKER_GIT_INFO ; git rev-parse HEAD >> DOCKER_GIT_INFO
|
||||
echo -n "COMMITTIME=" >> DOCKER_GIT_INFO ; git log -1 --format=%cd --date=iso >> DOCKER_GIT_INFO
|
||||
echo -n "REMOTE=" >> DOCKER_GIT_INFO ; git config --get remote.origin.url >> DOCKER_GIT_INFO
|
||||
|
||||
docker: dgi
|
||||
docker build \
|
||||
-t "$(DOCKER_NAME):$(HASH)" \
|
||||
-t "$(DOCKER_NAME):$(NAMESPACE)-latest" \
|
||||
-t "$(DOCKER_NAME):latest" \
|
||||
-t "$(DOCKER_REPO)/$(DOCKER_NAME):$(HASH)" \
|
||||
-t "$(DOCKER_REPO)/$(DOCKER_NAME):$(NAMESPACE)-latest" \
|
||||
-t "$(DOCKER_REPO)/$(DOCKER_NAME):latest" \
|
||||
.
|
||||
|
||||
swagger-setup:
|
||||
mkdir -p ".swaggobin"
|
||||
[ -f ".swaggobin/swag_$(SWAGGO_VERSION)" ] || { GOBIN=/tmp/_swaggo go install $(SWAGGO); cp "/tmp/_swaggo/swag" ".swaggobin/swag_$(SWAGGO_VERSION)"; rm -rf "/tmp/_swaggo"; }
|
||||
|
||||
swagger: swagger-setup
|
||||
".swaggobin/swag_$(SWAGGO_VERSION)" init -generalInfo ./api/router.go --propertyStrategy camelcase --output ./swagger/ --outputTypes "json,yaml" --overridesFile override.swag
|
||||
|
||||
run-docker-local: docker
|
||||
mkdir -p .run-data
|
||||
docker run --rm \
|
||||
--init \
|
||||
--env "CONF_NS=local-docker" \
|
||||
--volume "$(shell pwd)/.run-data/docker-local:/data" \
|
||||
--publish "8080:80" \
|
||||
$(DOCKER_NAME):latest
|
||||
|
||||
inspect-docker: docker
|
||||
mkdir -p .run-data
|
||||
docker run -ti \
|
||||
--rm \
|
||||
--volume "$(shell pwd)/.run-data/docker-inspect:/data" \
|
||||
$(DOCKER_NAME):latest \
|
||||
bash
|
||||
|
||||
push-docker:
|
||||
docker image push "$(DOCKER_REPO)/$(DOCKER_NAME):$(HASH)"
|
||||
docker image push "$(DOCKER_REPO)/$(DOCKER_NAME):$(NAMESPACE)-latest"
|
||||
docker image push "$(DOCKER_REPO)/$(DOCKER_NAME):latest"
|
||||
|
||||
clean:
|
||||
rm -rf _build/*
|
||||
rm -rf .run-data/*
|
||||
git clean -fdx
|
||||
! which go 2>&1 >> /dev/null || go clean
|
||||
! which go 2>&1 >> /dev/null || go clean -testcache
|
||||
|
||||
fmt: swagger-setup
|
||||
go fmt ./...
|
||||
".swaggobin/swag_$(SWAGGO_VERSION)" fmt
|
||||
|
||||
.PHONY: test
|
||||
test:
|
||||
go test ./test/...
|
||||
|
||||
lint:
|
||||
# curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.53.2
|
||||
golangci-lint run ./...
|
|
@ -0,0 +1,20 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"gogs.mikescher.com/BlackForestBytes/goext/bfcodegen"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
dest := os.Args[2]
|
||||
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = bfcodegen.GenerateEnumSpecs(wd, dest)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"gogs.mikescher.com/BlackForestBytes/goext/bfcodegen"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
dest := os.Args[2]
|
||||
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = bfcodegen.GenerateIDSpecs(wd, dest)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,154 @@
|
|||
package handler
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/gin-gonic/gin"
|
||||
"gogs.mikescher.com/BlackForestBytes/goext/ginext"
|
||||
"gogs.mikescher.com/BlackForestBytes/goext/timeext"
|
||||
"locbunny/logic"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
type CommonHandler struct {
|
||||
app *logic.Application
|
||||
}
|
||||
|
||||
func NewCommonHandler(app *logic.Application) CommonHandler {
|
||||
return CommonHandler{
|
||||
app: app,
|
||||
}
|
||||
}
|
||||
|
||||
type pingResponse struct {
|
||||
Message string `json:"message"`
|
||||
Info pingResponseInfo `json:"info"`
|
||||
}
|
||||
type pingResponseInfo struct {
|
||||
Method string `json:"method"`
|
||||
Request string `json:"request"`
|
||||
Headers map[string][]string `json:"headers"`
|
||||
URI string `json:"uri"`
|
||||
Address string `json:"addr"`
|
||||
}
|
||||
|
||||
// Ping swaggerdoc
|
||||
//
|
||||
// @Summary Simple endpoint to test connection (any http method)
|
||||
// @Tags Common
|
||||
//
|
||||
// @Success 200 {object} pingResponse
|
||||
// @Failure 500 {object} models.APIError
|
||||
//
|
||||
// @Router /api/ping [get]
|
||||
// @Router /api/ping [post]
|
||||
// @Router /api/ping [put]
|
||||
// @Router /api/ping [delete]
|
||||
// @Router /api/ping [patch]
|
||||
func (h CommonHandler) Ping(pctx ginext.PreContext) ginext.HTTPResponse {
|
||||
ctx, g, errResp := pctx.Start()
|
||||
if errResp != nil {
|
||||
return *errResp
|
||||
}
|
||||
defer ctx.Cancel()
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
_, _ = buf.ReadFrom(g.Request.Body)
|
||||
resuestBody := buf.String()
|
||||
|
||||
return ginext.JSON(http.StatusOK, pingResponse{
|
||||
Message: "Pong",
|
||||
Info: pingResponseInfo{
|
||||
Method: g.Request.Method,
|
||||
Request: resuestBody,
|
||||
Headers: g.Request.Header,
|
||||
URI: g.Request.RequestURI,
|
||||
Address: g.Request.RemoteAddr,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// Health swaggerdoc
|
||||
//
|
||||
// @Summary Server Health-checks
|
||||
// @Tags Common
|
||||
//
|
||||
// @Success 200 {object} handler.Health.response
|
||||
// @Failure 500 {object} models.APIError
|
||||
//
|
||||
// @Router /api/health [get]
|
||||
func (h CommonHandler) Health(pctx ginext.PreContext) ginext.HTTPResponse {
|
||||
type response struct {
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
ctx, _, errResp := pctx.Start()
|
||||
if errResp != nil {
|
||||
return *errResp
|
||||
}
|
||||
defer ctx.Cancel()
|
||||
|
||||
return ginext.JSON(http.StatusOK, response{Status: "ok"})
|
||||
}
|
||||
|
||||
// Sleep swaggerdoc
|
||||
//
|
||||
// @Summary Return 200 after x seconds
|
||||
// @Tags Common
|
||||
//
|
||||
// @Param secs path number true "sleep delay (in seconds)"
|
||||
//
|
||||
// @Success 200 {object} handler.Sleep.response
|
||||
// @Failure 400 {object} models.APIError
|
||||
// @Failure 500 {object} models.APIError
|
||||
//
|
||||
// @Router /api/sleep/:secs [post]
|
||||
func (h CommonHandler) Sleep(pctx ginext.PreContext) ginext.HTTPResponse {
|
||||
type uri struct {
|
||||
Seconds float64 `uri:"secs"`
|
||||
}
|
||||
type response struct {
|
||||
Start string `json:"start"`
|
||||
End string `json:"end"`
|
||||
Duration float64 `json:"duration"`
|
||||
}
|
||||
|
||||
var u uri
|
||||
ctx, _, errResp := pctx.URI(&u).Start()
|
||||
if errResp != nil {
|
||||
return *errResp
|
||||
}
|
||||
defer ctx.Cancel()
|
||||
|
||||
t0 := time.Now().Format(time.RFC3339Nano)
|
||||
|
||||
time.Sleep(timeext.FromSeconds(u.Seconds))
|
||||
|
||||
t1 := time.Now().Format(time.RFC3339Nano)
|
||||
|
||||
return ginext.JSON(http.StatusOK, response{
|
||||
Start: t0,
|
||||
End: t1,
|
||||
Duration: u.Seconds,
|
||||
})
|
||||
}
|
||||
|
||||
func (h CommonHandler) NoRoute(pctx ginext.PreContext) ginext.HTTPResponse {
|
||||
|
||||
ctx, g, errResp := pctx.Start()
|
||||
if errResp != nil {
|
||||
return *errResp
|
||||
}
|
||||
defer ctx.Cancel()
|
||||
|
||||
return ginext.JSON(http.StatusNotFound, gin.H{
|
||||
"": "================ ROUTE NOT FOUND ================",
|
||||
"FullPath": g.FullPath(),
|
||||
"Method": g.Request.Method,
|
||||
"URL": g.Request.URL.String(),
|
||||
"RequestURI": g.Request.RequestURI,
|
||||
"Proto": g.Request.Proto,
|
||||
"Header": g.Request.Header,
|
||||
"~": "================ ROUTE NOT FOUND ================",
|
||||
})
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
package handler
|
||||
|
||||
import (
|
||||
"gogs.mikescher.com/BlackForestBytes/goext/ginext"
|
||||
"gogs.mikescher.com/BlackForestBytes/goext/langext"
|
||||
"locbunny/logic"
|
||||
"locbunny/models"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type WebHandler struct {
|
||||
app *logic.Application
|
||||
}
|
||||
|
||||
func NewWebHandler(app *logic.Application) WebHandler {
|
||||
return WebHandler{
|
||||
app: app,
|
||||
}
|
||||
}
|
||||
|
||||
// ListServer swaggerdoc
|
||||
//
|
||||
// @Summary List running server
|
||||
//
|
||||
// @Success 200 {object} handler.ListServer.response
|
||||
// @Failure 400 {object} models.APIError
|
||||
// @Failure 500 {object} models.APIError
|
||||
//
|
||||
// @Router /server [GET]
|
||||
func (h WebHandler) ListServer(pctx ginext.PreContext) ginext.HTTPResponse {
|
||||
type response struct {
|
||||
Server []models.Server `json:"server"`
|
||||
}
|
||||
|
||||
ctx, _, errResp := pctx.Start()
|
||||
if errResp != nil {
|
||||
return *errResp
|
||||
}
|
||||
defer ctx.Cancel()
|
||||
|
||||
srvs, err := h.app.ListServer(ctx)
|
||||
if err != nil {
|
||||
return ginext.Error(err)
|
||||
}
|
||||
|
||||
langext.SortBy(srvs, func(v models.Server) int { return v.Port })
|
||||
|
||||
return ginext.JSON(http.StatusOK, response{Server: srvs})
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gogs.mikescher.com/BlackForestBytes/goext/ginext"
|
||||
bunny "locbunny"
|
||||
"locbunny/api/handler"
|
||||
"locbunny/logic"
|
||||
"locbunny/swagger"
|
||||
)
|
||||
|
||||
type Router struct {
|
||||
app *logic.Application
|
||||
|
||||
commonHandler handler.CommonHandler
|
||||
webHandler handler.WebHandler
|
||||
}
|
||||
|
||||
func NewRouter(app *logic.Application) *Router {
|
||||
return &Router{
|
||||
app: app,
|
||||
|
||||
commonHandler: handler.NewCommonHandler(app),
|
||||
webHandler: handler.NewWebHandler(app),
|
||||
}
|
||||
}
|
||||
|
||||
// Init swaggerdocs
|
||||
//
|
||||
// @title LocalHostBunny
|
||||
// @version 1.0
|
||||
// @host localhost
|
||||
//
|
||||
// @BasePath /api/v1/
|
||||
func (r *Router) Init(e *ginext.GinWrapper) {
|
||||
|
||||
api := e.Routes().Group("/api").Group(fmt.Sprintf("/v%d", bunny.APILevel))
|
||||
|
||||
// ================ General ================
|
||||
|
||||
api.Any("/ping").Handle(r.commonHandler.Ping)
|
||||
api.GET("/health").Handle(r.commonHandler.Health)
|
||||
api.POST("/sleep/:secs").Handle(r.commonHandler.Sleep)
|
||||
|
||||
// ================ Swagger ================
|
||||
|
||||
docs := e.Routes().Group("/documentation")
|
||||
{
|
||||
docs.GET("/swagger").Handle(ginext.RedirectTemporary("/documentation/swagger/"))
|
||||
docs.GET("/swagger/*sub").Handle(swagger.Handle)
|
||||
}
|
||||
|
||||
// ================ API ================
|
||||
|
||||
api.GET("/server").Handle(r.webHandler.ListServer)
|
||||
|
||||
// ================ ================
|
||||
|
||||
if r.app.Config.Custom404 {
|
||||
e.NoRoute(r.commonHandler.NoRoute)
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/rs/zerolog/log"
|
||||
"gogs.mikescher.com/BlackForestBytes/goext/ginext"
|
||||
bunny "locbunny"
|
||||
"locbunny/api"
|
||||
"locbunny/logic"
|
||||
)
|
||||
|
||||
func main() {
|
||||
conf := bunny.Conf
|
||||
|
||||
bunny.Init(conf)
|
||||
|
||||
log.Info().Msg(fmt.Sprintf("Starting with config-namespace <%s>", conf.Namespace))
|
||||
|
||||
app := logic.NewApp()
|
||||
|
||||
ginengine := ginext.NewEngine(conf.Cors, conf.GinDebug, true, conf.RequestTimeout)
|
||||
|
||||
router := api.NewRouter(app)
|
||||
|
||||
appjobs := make([]logic.Job, 0)
|
||||
|
||||
app.Init(conf, ginengine, appjobs)
|
||||
|
||||
router.Init(ginengine)
|
||||
|
||||
app.Run()
|
||||
}
|
|
@ -0,0 +1,146 @@
|
|||
package bunny
|
||||
|
||||
import (
|
||||
"github.com/rs/xid"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
"gogs.mikescher.com/BlackForestBytes/goext/confext"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
const APILevel = 1
|
||||
|
||||
type Config struct {
|
||||
Namespace string
|
||||
GinDebug bool `env:"GINDEBUG"`
|
||||
ReturnRawErrors bool `env:"RETURNERRORS"`
|
||||
Custom404 bool `env:"CUSTOM404"`
|
||||
LogLevel zerolog.Level `env:"LOGLEVEL"`
|
||||
ServerIP string `env:"IP"`
|
||||
ServerPort string `env:"PORT"`
|
||||
RequestTimeout time.Duration `env:"REQUEST_TIMEOUT"`
|
||||
Cors bool `env:"CORS"`
|
||||
}
|
||||
|
||||
type MailConfig struct {
|
||||
Host string `env:"HOST"`
|
||||
Port int `env:"PORT"`
|
||||
Username string `env:"USERNAME"`
|
||||
Password string `env:"PASSWORD"`
|
||||
Sender string `env:"SENDER"`
|
||||
}
|
||||
|
||||
var Conf Config
|
||||
|
||||
var configLocHost = func() Config {
|
||||
return Config{
|
||||
Namespace: "local",
|
||||
GinDebug: true,
|
||||
ServerIP: "0.0.0.0",
|
||||
ServerPort: "80",
|
||||
Custom404: true,
|
||||
ReturnRawErrors: true,
|
||||
RequestTimeout: 16 * time.Second,
|
||||
LogLevel: zerolog.DebugLevel,
|
||||
Cors: true,
|
||||
}
|
||||
}
|
||||
|
||||
var configLocDocker = func() Config {
|
||||
return Config{
|
||||
Namespace: "local-docker",
|
||||
GinDebug: true,
|
||||
ServerIP: "0.0.0.0",
|
||||
ServerPort: "80",
|
||||
Custom404: true,
|
||||
ReturnRawErrors: true,
|
||||
RequestTimeout: 16 * time.Second,
|
||||
LogLevel: zerolog.DebugLevel,
|
||||
Cors: true,
|
||||
}
|
||||
}
|
||||
|
||||
var configDev = func() Config {
|
||||
return Config{
|
||||
Namespace: "develop",
|
||||
GinDebug: true,
|
||||
ServerIP: "0.0.0.0",
|
||||
ServerPort: "80",
|
||||
Custom404: false,
|
||||
ReturnRawErrors: false,
|
||||
RequestTimeout: 16 * time.Second,
|
||||
LogLevel: zerolog.DebugLevel,
|
||||
Cors: false,
|
||||
}
|
||||
}
|
||||
|
||||
var configStag = func() Config {
|
||||
return Config{
|
||||
Namespace: "staging",
|
||||
GinDebug: true,
|
||||
ServerIP: "0.0.0.0",
|
||||
ServerPort: "80",
|
||||
Custom404: false,
|
||||
ReturnRawErrors: false,
|
||||
RequestTimeout: 16 * time.Second,
|
||||
LogLevel: zerolog.DebugLevel,
|
||||
Cors: false,
|
||||
}
|
||||
}
|
||||
|
||||
var configProd = func() Config {
|
||||
return Config{
|
||||
Namespace: "production",
|
||||
GinDebug: false,
|
||||
ServerIP: "0.0.0.0",
|
||||
ServerPort: "80",
|
||||
Custom404: false,
|
||||
ReturnRawErrors: false,
|
||||
RequestTimeout: 16 * time.Second,
|
||||
LogLevel: zerolog.InfoLevel,
|
||||
Cors: false,
|
||||
}
|
||||
}
|
||||
|
||||
var allConfig = map[string]func() Config{
|
||||
"local-host": configLocHost,
|
||||
"local-docker": configLocDocker,
|
||||
"develop": configDev,
|
||||
"staging": configStag,
|
||||
"production": configProd,
|
||||
}
|
||||
|
||||
var instID xid.ID
|
||||
|
||||
func InstanceID() string {
|
||||
return instID.String()
|
||||
}
|
||||
|
||||
func getConfig(ns string) (Config, bool) {
|
||||
if ns == "" {
|
||||
ns = "local-host"
|
||||
}
|
||||
if cfn, ok := allConfig[ns]; ok {
|
||||
c := cfn()
|
||||
err := confext.ApplyEnvOverrides("BUNNY_", &c, "_")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return c, true
|
||||
}
|
||||
return Config{}, false
|
||||
}
|
||||
|
||||
func init() {
|
||||
instID = xid.New()
|
||||
|
||||
ns := os.Getenv("CONF_NS")
|
||||
|
||||
cfg, ok := getConfig(ns)
|
||||
if !ok {
|
||||
log.Fatal().Str("ns", ns).Msg("Unknown config-namespace")
|
||||
}
|
||||
|
||||
Conf = cfg
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
package bunny
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"gogs.mikescher.com/BlackForestBytes/goext/langext"
|
||||
"strings"
|
||||
)
|
||||
|
||||
//go:embed DOCKER_GIT_INFO
|
||||
var FileDockerGitInfo string
|
||||
|
||||
var CommitHash *string
|
||||
var VCSType *string
|
||||
var CommitTime *string
|
||||
var BranchName *string
|
||||
var RemoteURL *string
|
||||
|
||||
func init() {
|
||||
for _, v := range strings.Split(FileDockerGitInfo, "\n") {
|
||||
if v == "" {
|
||||
continue
|
||||
} else if strings.HasPrefix(v, "VCSTYPE=") {
|
||||
VCSType = langext.Ptr(v[len("VCSTYPE="):])
|
||||
fmt.Printf("Found DGI Config: '%s' := '%s'\n", "VCSType", *VCSType)
|
||||
} else if strings.HasPrefix(v, "BRANCH=") {
|
||||
BranchName = langext.Ptr(v[len("BRANCH="):])
|
||||
fmt.Printf("Found DGI Config: '%s' := '%s'\n", "BranchName", *BranchName)
|
||||
} else if strings.HasPrefix(v, "HASH=") {
|
||||
CommitHash = langext.Ptr(v[len("HASH="):])
|
||||
fmt.Printf("Found DGI Config: '%s' := '%s'\n", "CommitHash", *CommitHash)
|
||||
} else if strings.HasPrefix(v, "COMMITTIME=") {
|
||||
CommitTime = langext.Ptr(v[len("COMMITTIME="):])
|
||||
fmt.Printf("Found DGI Config: '%s' := '%s'\n", "CommitTime", *CommitTime)
|
||||
} else if strings.HasPrefix(v, "REMOTE=") {
|
||||
RemoteURL = langext.Ptr(v[len("REMOTE="):])
|
||||
fmt.Printf("Found DGI Config: '%s' := '%s'\n", "RemoteURL", *RemoteURL)
|
||||
} else {
|
||||
fmt.Printf("[ERROR] Failed to parse DGI Config '%s'\n", v)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
package bunny
|
||||
|
||||
import (
|
||||
"gogs.mikescher.com/BlackForestBytes/goext/exerr"
|
||||
"gogs.mikescher.com/BlackForestBytes/goext/langext"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrInternal = exerr.TypeInternal
|
||||
ErrPanic = exerr.TypePanic
|
||||
ErrWrap = exerr.TypeWrap
|
||||
ErrNotImplemented = exerr.TypeNotImplemented
|
||||
|
||||
ErrBindFailURI = exerr.TypeBindFailURI
|
||||
ErrBindFailQuery = exerr.TypeBindFailQuery
|
||||
ErrBindFailJSON = exerr.TypeBindFailJSON
|
||||
ErrBindFailFormData = exerr.TypeBindFailFormData
|
||||
|
||||
ErrUnauthorized = exerr.TypeUnauthorized
|
||||
ErrAuthFailed = exerr.TypeAuthFailed
|
||||
|
||||
ErrDatabaseError = exerr.NewType("DATABASE_ERROR", langext.Ptr(500))
|
||||
ErrFilesystemError = exerr.NewType("FILESYSTEM_ERROR", langext.Ptr(500))
|
||||
ErrInvalidStateError = exerr.NewType("INV_STATE_ERROR", langext.Ptr(500))
|
||||
|
||||
ErrInvalidRequestParams = exerr.NewType("INVALID_REQUEST_PARAMETER", langext.Ptr(400))
|
||||
ErrMissingRequestParams = exerr.NewType("MISSING_REQUEST_PARAMETER", langext.Ptr(400))
|
||||
ErrSelfDelete = exerr.NewType("SELF_DELETE", langext.Ptr(400))
|
||||
ErrInvalidRefKey = exerr.NewType("INVALID_REF_KEY", langext.Ptr(400))
|
||||
ErrInvalidMimeType = exerr.NewType("INVALID_MIME_TYPE", langext.Ptr(400))
|
||||
ErrInvalidBlobType = exerr.NewType("INVALID_BLOB_TYPE", langext.Ptr(400))
|
||||
ErrInvalidAuthType = exerr.NewType("INVALID_AUTH_TYPE", langext.Ptr(400))
|
||||
ErrInvalidSubType = exerr.NewType("INVALID_SUB_TYPE", langext.Ptr(400))
|
||||
ErrPostURLFormat = exerr.NewType("POST_URL_FORMAT", langext.Ptr(400))
|
||||
|
||||
ErrEntityNotFound = exerr.NewType("ENTITY_NOT_FOUND", langext.Ptr(400))
|
||||
ErrInvalidCursorToken = exerr.NewType("INVALID_CURSOR_TOKEN", langext.Ptr(400))
|
||||
|
||||
ErrUsernameCollision = exerr.NewType("USERNAME_COLLISION", langext.Ptr(400))
|
||||
ErrEmailCollision = exerr.NewType("EMAIL_COLLISION", langext.Ptr(400))
|
||||
|
||||
ErrPreconditionFailed = exerr.NewType("PRECONDITION_FAILED", langext.Ptr(400))
|
||||
|
||||
ErrMissingPermissions = exerr.NewType("MISSING_PERMISSIONS", langext.Ptr(400))
|
||||
|
||||
ErrWrongOldPassword = exerr.NewType("WRONG_OLD_PASSWORD", langext.Ptr(400))
|
||||
ErrWrongSecret = exerr.NewType("WRONG_SECRET", langext.Ptr(400))
|
||||
|
||||
ErrMarshalBSON = exerr.NewType("MARSHAL_BSON", langext.Ptr(400))
|
||||
|
||||
ErrInvalidJWT = exerr.NewType("INVALID_JWT", langext.Ptr(400))
|
||||
|
||||
ErrFAPIError = exerr.NewType("FAPI_ERROR", langext.Ptr(500))
|
||||
ErrFAPIUnsupported = exerr.NewType("FAPI_UNSUPPORTED", langext.Ptr(500))
|
||||
ErrSolseitAPIError = exerr.NewType("SOLSEIT_API_ERROR", langext.Ptr(500))
|
||||
ErrScraper = exerr.NewType("SCRAPER", langext.Ptr(500))
|
||||
|
||||
ErrInvalidEnum = exerr.NewType("INVALID_ENUM", langext.Ptr(500))
|
||||
|
||||
ErrJob = exerr.NewType("JOB", langext.Ptr(500))
|
||||
|
||||
ErrUnmarshalJSON = exerr.NewType("UNMARSHAL_JSON", langext.Ptr(500))
|
||||
ErrFeatureDisabled = exerr.NewType("FEATURE_DISABLED", langext.Ptr(500))
|
||||
)
|
|
@ -0,0 +1,41 @@
|
|||
module locbunny
|
||||
|
||||
go 1.21
|
||||
|
||||
require (
|
||||
github.com/gin-gonic/gin v1.9.1
|
||||
github.com/rs/xid v1.5.0
|
||||
github.com/rs/zerolog v1.31.0
|
||||
go.mongodb.org/mongo-driver v1.12.1
|
||||
gogs.mikescher.com/BlackForestBytes/goext v0.0.288
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/bytedance/sonic v1.10.2 // indirect
|
||||
github.com/cakturk/go-netstat v0.0.0-20200220111822-e5b49efee7a5
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
|
||||
github.com/chenzhuoyu/iasm v0.9.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.15.5 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.5 // indirect
|
||||
github.com/leodido/go-urn v1.2.4 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.11 // indirect
|
||||
golang.org/x/arch v0.5.0 // indirect
|
||||
golang.org/x/crypto v0.14.0 // indirect
|
||||
golang.org/x/net v0.17.0 // indirect
|
||||
golang.org/x/sys v0.13.0 // indirect
|
||||
golang.org/x/text v0.13.0 // indirect
|
||||
google.golang.org/protobuf v1.31.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
|
@ -0,0 +1,148 @@
|
|||
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
||||
github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM=
|
||||
github.com/bytedance/sonic v1.10.2 h1:GQebETVBxYB7JGWJtLBi07OVzWwt+8dWA00gEVW2ZFE=
|
||||
github.com/bytedance/sonic v1.10.2/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4=
|
||||
github.com/cakturk/go-netstat v0.0.0-20200220111822-e5b49efee7a5 h1:BjkPE3785EwPhhyuFkbINB+2a1xATwk8SNDWnJiD41g=
|
||||
github.com/cakturk/go-netstat v0.0.0-20200220111822-e5b49efee7a5/go.mod h1:jtAfVaU/2cu1+wdSRPWE2c1N2qeAA3K4RH9pYgqwets=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA=
|
||||
github.com/chenzhuoyu/iasm v0.9.0 h1:9fhXjVzq5hUy2gkhhgHl95zG2cEAhw9OSGs8toWWAwo=
|
||||
github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
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/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
|
||||
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
|
||||
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
|
||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.15.5 h1:LEBecTWb/1j5TNY1YYG2RcOUN3R7NLylN+x8TTueE24=
|
||||
github.com/go-playground/validator/v10 v10.15.5/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
|
||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
|
||||
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
|
||||
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=
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
|
||||
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
|
||||
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
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/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc=
|
||||
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||
github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A=
|
||||
github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
|
||||
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.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.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.2/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/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
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/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
|
||||
github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
|
||||
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
|
||||
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
go.mongodb.org/mongo-driver v1.12.1 h1:nLkghSU8fQNaK7oUmDhQFsnrtcoNy7Z6LVFKsEecqgE=
|
||||
go.mongodb.org/mongo-driver v1.12.1/go.mod h1:/rGBTebI3XYboVmgz+Wv3Bcbl3aD0QF9zl6kDDw18rQ=
|
||||
gogs.mikescher.com/BlackForestBytes/goext v0.0.288 h1:t4K/rUNGgvP1jSCX1M9fX3I8mD/NOkqCG4DT+zLTEv8=
|
||||
gogs.mikescher.com/BlackForestBytes/goext v0.0.288/go.mod h1:vzrX73AdSKv6PcqrKZW9yzopTkitDW1T5LknU9oVci0=
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/arch v0.5.0 h1:jpGode6huXQxcskEIpOCvrU+tzo81b6+oFLUYXWtH/Y=
|
||||
golang.org/x/arch v0.5.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
|
||||
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
|
||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
|
||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
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=
|
||||
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
|
@ -0,0 +1,54 @@
|
|||
package bunny
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
"gogs.mikescher.com/BlackForestBytes/goext/exerr"
|
||||
"gogs.mikescher.com/BlackForestBytes/goext/langext"
|
||||
"os"
|
||||
)
|
||||
|
||||
func Init(cfg Config) {
|
||||
exerr.Init(exerr.ErrorPackageConfigInit{
|
||||
ZeroLogErrTraces: langext.PTrue,
|
||||
ZeroLogAllTraces: langext.PTrue,
|
||||
RecursiveErrors: langext.PTrue,
|
||||
ExtendedGinOutput: &cfg.ReturnRawErrors,
|
||||
IncludeMetaInGinOutput: &cfg.ReturnRawErrors,
|
||||
ExtendGinOutput: func(err *exerr.ExErr, json map[string]any) {
|
||||
if fapiMsg := err.RecursiveMeta("fapiMessage"); fapiMsg != nil {
|
||||
json["fapiMessage"] = fapiMsg.ValueString()
|
||||
}
|
||||
},
|
||||
ExtendGinDataOutput: func(err *exerr.ExErr, depth int, json map[string]any) {
|
||||
if fapiMsg, ok := err.Meta["fapiMessage"]; ok {
|
||||
json["fapiMessage"] = fapiMsg.ValueString()
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
cw := zerolog.ConsoleWriter{
|
||||
Out: os.Stdout,
|
||||
TimeFormat: "2006-01-02 15:04:05 Z07:00",
|
||||
}
|
||||
|
||||
zerolog.TimeFieldFormat = zerolog.TimeFormatUnix
|
||||
multi := zerolog.MultiLevelWriter(cw)
|
||||
logger := zerolog.New(multi).With().
|
||||
Timestamp().
|
||||
Caller().
|
||||
Logger()
|
||||
|
||||
log.Logger = logger
|
||||
|
||||
if cfg.GinDebug {
|
||||
gin.SetMode(gin.DebugMode)
|
||||
} else {
|
||||
gin.SetMode(gin.ReleaseMode)
|
||||
}
|
||||
|
||||
zerolog.SetGlobalLevel(cfg.LogLevel)
|
||||
|
||||
log.Debug().Msg("Initialized")
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
package jobs
|
||||
|
||||
import (
|
||||
"gogs.mikescher.com/BlackForestBytes/goext/rfctime"
|
||||
"locbunny/logic"
|
||||
"locbunny/models"
|
||||
"time"
|
||||
)
|
||||
|
||||
type JobListener struct {
|
||||
execID models.JobExecutionID
|
||||
start time.Time
|
||||
jobName string
|
||||
logs []models.JobLog
|
||||
app *logic.Application
|
||||
}
|
||||
|
||||
func NewJobListener(app *logic.Application, id models.JobExecutionID, jobName string) *JobListener {
|
||||
return &JobListener{
|
||||
execID: id,
|
||||
jobName: jobName,
|
||||
start: time.Now(),
|
||||
logs: make([]models.JobLog, 0),
|
||||
app: app,
|
||||
}
|
||||
}
|
||||
|
||||
func (lstr *JobListener) Log(lvl models.JobLogLevel, logtype string, msg string, extra any) {
|
||||
logentry := models.JobLog{
|
||||
JobLogID: models.NewJobLogID(),
|
||||
JobExecutionID: lstr.execID,
|
||||
JobName: lstr.jobName,
|
||||
Type: logtype,
|
||||
Time: rfctime.NowRFC3339Nano(),
|
||||
Message: msg,
|
||||
Level: lvl,
|
||||
Extra: extra,
|
||||
}
|
||||
|
||||
lstr.logs = append(lstr.logs, logentry)
|
||||
}
|
||||
|
||||
func (lstr *JobListener) LogDebug(logtype string, msg string, extra any) {
|
||||
lstr.Log(models.JobLogLevelDebug, logtype, msg, extra)
|
||||
}
|
||||
|
||||
func (lstr *JobListener) LogInfo(logtype string, msg string, extra any) {
|
||||
lstr.Log(models.JobLogLevelInfo, logtype, msg, extra)
|
||||
}
|
||||
|
||||
func (lstr *JobListener) LogWarn(logtype string, msg string, extra any) {
|
||||
lstr.Log(models.JobLogLevelWarn, logtype, msg, extra)
|
||||
}
|
||||
|
||||
func (lstr *JobListener) LogError(logtype string, msg string, extra any) {
|
||||
lstr.Log(models.JobLogLevelError, logtype, msg, extra)
|
||||
}
|
||||
|
||||
func (lstr *JobListener) LogFatal(logtype string, msg string, extra any) {
|
||||
lstr.Log(models.JobLogLevelFatal, logtype, msg, extra)
|
||||
}
|
|
@ -0,0 +1,173 @@
|
|||
package jobs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/rs/zerolog/log"
|
||||
"gogs.mikescher.com/BlackForestBytes/goext/exerr"
|
||||
"gogs.mikescher.com/BlackForestBytes/goext/langext"
|
||||
"gogs.mikescher.com/BlackForestBytes/goext/mathext"
|
||||
"gogs.mikescher.com/BlackForestBytes/goext/rfctime"
|
||||
"gogs.mikescher.com/BlackForestBytes/goext/syncext"
|
||||
bunny "locbunny"
|
||||
"locbunny/logic"
|
||||
"locbunny/models"
|
||||
"math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
type JobFunction[TData any] func(ctx context.Context, app *logic.Application, lstr *JobListener, data *TData) (int, error)
|
||||
|
||||
type JobRunner[TData any] struct {
|
||||
name string
|
||||
app *logic.Application
|
||||
isRunning *syncext.AtomicBool
|
||||
isStarted bool
|
||||
interval time.Duration
|
||||
sigChannel chan string
|
||||
runTimeout time.Duration
|
||||
jobFunc JobFunction[TData]
|
||||
data *TData
|
||||
}
|
||||
|
||||
func NewJobRunner[TData any](app *logic.Application, name string, interval time.Duration, timeout time.Duration, fn JobFunction[TData], data *TData) *JobRunner[TData] {
|
||||
return &JobRunner[TData]{
|
||||
app: app,
|
||||
isRunning: syncext.NewAtomicBool(false),
|
||||
isStarted: false,
|
||||
sigChannel: make(chan string),
|
||||
interval: interval,
|
||||
runTimeout: timeout,
|
||||
name: name,
|
||||
jobFunc: fn,
|
||||
data: data,
|
||||
}
|
||||
}
|
||||
|
||||
func (j *JobRunner[TData]) Start() error {
|
||||
if j.isRunning.Get() {
|
||||
return exerr.New(bunny.ErrJob, "job already running").Build()
|
||||
}
|
||||
if j.isStarted {
|
||||
return exerr.New(bunny.ErrJob, "job was already started").Build() // re-start after stop is not allowed
|
||||
}
|
||||
|
||||
j.isStarted = true
|
||||
|
||||
go j.mainLoop()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (j *JobRunner[TData]) Stop() {
|
||||
log.Info().Msg(fmt.Sprintf("Stopping Job [%s]", j.name))
|
||||
syncext.WriteNonBlocking(j.sigChannel, "stop")
|
||||
j.isRunning.Wait(false)
|
||||
log.Info().Msg(fmt.Sprintf("Stopped Job [%s]", j.name))
|
||||
}
|
||||
|
||||
func (j *JobRunner[TData]) Running() bool {
|
||||
return j.isRunning.Get()
|
||||
}
|
||||
|
||||
func (j *JobRunner[TData]) mainLoop() {
|
||||
j.isRunning.Set(true)
|
||||
|
||||
firstRun := true
|
||||
for {
|
||||
|
||||
interval := j.interval
|
||||
if firstRun {
|
||||
// randomize first interval to spread jobs around
|
||||
perc := mathext.Clamp(rand.Float64(), 0.1, 0.5)
|
||||
interval = time.Duration(int64(float64(interval) * perc))
|
||||
}
|
||||
firstRun = false
|
||||
|
||||
signal, okay := syncext.ReadChannelWithTimeout(j.sigChannel, interval)
|
||||
if okay {
|
||||
if signal == "stop" {
|
||||
log.Info().Msg(fmt.Sprintf("Job [%s] received <stop> signal", j.name))
|
||||
break
|
||||
} else if signal == "run" {
|
||||
log.Info().Msg(fmt.Sprintf("Job [%s] received <run> signal", j.name))
|
||||
continue
|
||||
} else {
|
||||
log.Error().Msg(fmt.Sprintf("Received unknown job signal: <%s> in job [%s]", signal, j.name))
|
||||
}
|
||||
}
|
||||
|
||||
log.Debug().Msg(fmt.Sprintf("Run job [%s]", j.name))
|
||||
|
||||
err := j.execute()
|
||||
if err != nil {
|
||||
log.Err(err).Msg(fmt.Sprintf("Failed to execute job [%s]: %s", j.name, err.Error()))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
log.Info().Msg(fmt.Sprintf("Job [%s] exiting main-loop", j.name))
|
||||
|
||||
j.isRunning.Set(false)
|
||||
}
|
||||
|
||||
func (j *JobRunner[TData]) execute() (err error) {
|
||||
defer func() {
|
||||
if rec := recover(); rec != nil {
|
||||
err = exerr.New(bunny.ErrJob, "Recovered panic in JobRunner::execute").Any("recover", rec).Build()
|
||||
}
|
||||
}()
|
||||
|
||||
runCtx, cancelRunCtx := context.WithTimeout(context.Background(), j.runTimeout)
|
||||
defer cancelRunCtx()
|
||||
|
||||
jobExec := models.JobExecution{
|
||||
JobExecutionID: models.NewJobExecutionID(),
|
||||
JobName: j.name,
|
||||
StartTime: rfctime.NowRFC3339Nano(),
|
||||
EndTime: nil,
|
||||
Changes: 0,
|
||||
Status: models.JobStatusRunning,
|
||||
}
|
||||
|
||||
lstr := NewJobListener(j.app, jobExec.JobExecutionID, j.name)
|
||||
|
||||
lstr.LogInfo("JOB_START", "Job started", nil)
|
||||
|
||||
changes, err := langext.RunPanicSafeR2(func() (int, error) { return j.jobFunc(runCtx, j.app, lstr, j.data) })
|
||||
|
||||
finCtx, cancelFinCtx := context.WithTimeout(context.Background(), time.Minute)
|
||||
defer cancelFinCtx()
|
||||
|
||||
//goland:noinspection GoTypeAssertionOnErrors
|
||||
if panicerr, ok := err.(langext.PanicWrappedErr); ok {
|
||||
|
||||
jobExec.EndTime = langext.Ptr(rfctime.NowRFC3339Nano())
|
||||
jobExec.Error = langext.Ptr(panicerr.Error())
|
||||
jobExec.Status = models.JobStatusFailed
|
||||
jobExec.Changes = changes
|
||||
lstr.LogFatal("JOB_PANIC", "Job finished with a panic", langext.H{"msg": panicerr.Error(), "obj": panicerr.ReoveredObj()})
|
||||
log.Error().Str("panic", panicerr.Error()).Msg(fmt.Sprintf("Job '%s' <%s> finished with panic and %d changes after %f minutes", j.name, jobExec.JobExecutionID, changes, jobExec.Delta().Minutes()))
|
||||
|
||||
} else if err != nil {
|
||||
|
||||
jobExec.EndTime = langext.Ptr(rfctime.NowRFC3339Nano())
|
||||
jobExec.Error = langext.Ptr(err.Error())
|
||||
jobExec.Status = models.JobStatusFailed
|
||||
jobExec.Changes = changes
|
||||
lstr.LogFatal("JOB_ERR", "Job finished with an error", langext.H{"msg": err.Error(), "err_obj": exerr.FromError(err).ToAPIJson(false, true, true)})
|
||||
log.Error().Str("err", err.Error()).Msg(fmt.Sprintf("Job '%s' <%s> finished with an error and %d changes after %f minutes", j.name, jobExec.JobExecutionID, changes, jobExec.Delta().Minutes()))
|
||||
|
||||
} else {
|
||||
|
||||
jobExec.EndTime = langext.Ptr(rfctime.NowRFC3339Nano())
|
||||
jobExec.Error = nil
|
||||
jobExec.Status = models.JobStatusSuccess
|
||||
jobExec.Changes = changes
|
||||
lstr.LogInfo("JOB_FINISH", "Job finished", nil)
|
||||
log.Info().Msg(fmt.Sprintf("Job '%s' <%s> finished successfully with %d changes after %f minutes", j.name, jobExec.JobExecutionID, changes, jobExec.Delta().Minutes()))
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,121 @@
|
|||
package logic
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/cakturk/go-netstat/netstat"
|
||||
"github.com/rs/zerolog/log"
|
||||
"gogs.mikescher.com/BlackForestBytes/goext/ginext"
|
||||
"gogs.mikescher.com/BlackForestBytes/goext/syncext"
|
||||
bunny "locbunny"
|
||||
"locbunny/models"
|
||||
"net"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Application struct {
|
||||
Config bunny.Config
|
||||
|
||||
stopChan chan bool
|
||||
Port string
|
||||
IsRunning *syncext.AtomicBool
|
||||
|
||||
Gin *ginext.GinWrapper
|
||||
Jobs []Job
|
||||
}
|
||||
|
||||
func NewApp() *Application {
|
||||
return &Application{
|
||||
stopChan: make(chan bool),
|
||||
IsRunning: syncext.NewAtomicBool(false),
|
||||
}
|
||||
}
|
||||
|
||||
func (app *Application) Init(cfg bunny.Config, g *ginext.GinWrapper, jobs []Job) {
|
||||
app.Config = cfg
|
||||
app.Gin = g
|
||||
app.Jobs = jobs
|
||||
}
|
||||
|
||||
func (app *Application) Stop() {
|
||||
syncext.WriteNonBlocking(app.stopChan, true)
|
||||
}
|
||||
|
||||
func (app *Application) Run() {
|
||||
|
||||
addr := net.JoinHostPort(app.Config.ServerIP, app.Config.ServerPort)
|
||||
|
||||
errChan, httpserver := app.Gin.ListenAndServeHTTP(addr, func(port string) {
|
||||
app.Port = port
|
||||
app.IsRunning.Set(true)
|
||||
})
|
||||
|
||||
sigstop := make(chan os.Signal, 1)
|
||||
signal.Notify(sigstop, os.Interrupt, syscall.SIGTERM)
|
||||
|
||||
for _, job := range app.Jobs {
|
||||
err := job.Start()
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("Failed to start job")
|
||||
}
|
||||
}
|
||||
|
||||
select {
|
||||
case <-sigstop:
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
log.Info().Msg("Stopping HTTP-Server")
|
||||
|
||||
err := httpserver.Shutdown(ctx)
|
||||
|
||||
if err != nil {
|
||||
log.Info().Err(err).Msg("Error while stopping the http-server")
|
||||
} else {
|
||||
log.Info().Msg("Stopped HTTP-Server")
|
||||
}
|
||||
|
||||
case err := <-errChan:
|
||||
log.Error().Err(err).Msg("HTTP-Server failed")
|
||||
|
||||
case _ = <-app.stopChan:
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
log.Info().Msg("Manually stopping HTTP-Server")
|
||||
|
||||
err := httpserver.Shutdown(ctx)
|
||||
|
||||
if err != nil {
|
||||
log.Info().Err(err).Msg("Error while stopping the http-server")
|
||||
} else {
|
||||
log.Info().Msg("Manually stopped HTTP-Server")
|
||||
}
|
||||
}
|
||||
|
||||
for _, job := range app.Jobs {
|
||||
job.Stop()
|
||||
}
|
||||
|
||||
app.IsRunning.Set(false)
|
||||
}
|
||||
|
||||
func (app *Application) ListServer(ctx *ginext.AppContext) ([]models.Server, error) {
|
||||
|
||||
socks, err := netstat.TCPSocks(netstat.NoopFilter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res := make([]models.Server, 0)
|
||||
|
||||
for _, sock := range socks {
|
||||
|
||||
res = append(res, models.Server{Port: int(sock.LocalAddr.Port)})
|
||||
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
package logic
|
||||
|
||||
type Job interface {
|
||||
Start() error
|
||||
Stop()
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package models
|
||||
|
||||
type APIError struct {
|
||||
ErrorCode string `json:"errorcode"`
|
||||
Message string `json:"message"`
|
||||
FAPIErrorMessage *string `json:"fapiMessage,omitempty"`
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
package models
|
||||
|
||||
import (
|
||||
"go.mongodb.org/mongo-driver/bson/bsontype"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
)
|
||||
|
||||
//go:generate go run ../_gen/id-generate.go -- ids_gen.go
|
||||
|
||||
type EntityID interface {
|
||||
ObjID() (primitive.ObjectID, error)
|
||||
String() string
|
||||
MarshalBSONValue() (bsontype.Type, []byte, error)
|
||||
AsAny() AnyID
|
||||
}
|
||||
|
||||
type AnyID string //@id:type
|
||||
|
||||
type JobLogID string //@id:type
|
||||
|
||||
type JobExecutionID string //@id:type
|
|
@ -0,0 +1,103 @@
|
|||
// Code generated by id-generate.go DO NOT EDIT.
|
||||
|
||||
package models
|
||||
|
||||
import "go.mongodb.org/mongo-driver/bson"
|
||||
import "go.mongodb.org/mongo-driver/bson/bsontype"
|
||||
import "go.mongodb.org/mongo-driver/bson/primitive"
|
||||
import "gogs.mikescher.com/BlackForestBytes/goext/exerr"
|
||||
|
||||
const ChecksumIDGenerator = "8fa696914bf8d1c1c4b9f80be45d0a9dfbd0fed789856bf84ced2979c789b958" // GoExtVersion: 0.0.288
|
||||
|
||||
// ================================ AnyID (ids.go) ================================
|
||||
|
||||
func (i AnyID) MarshalBSONValue() (bsontype.Type, []byte, error) {
|
||||
if objId, err := primitive.ObjectIDFromHex(string(i)); err == nil {
|
||||
return bson.MarshalValue(objId)
|
||||
} else {
|
||||
return 0, nil, exerr.New(exerr.TypeMarshalEntityID, "Failed to marshal AnyID("+i.String()+") to ObjectId").Str("value", string(i)).Type("type", i).Build()
|
||||
}
|
||||
}
|
||||
|
||||
func (i AnyID) String() string {
|
||||
return string(i)
|
||||
}
|
||||
|
||||
func (i AnyID) ObjID() (primitive.ObjectID, error) {
|
||||
return primitive.ObjectIDFromHex(string(i))
|
||||
}
|
||||
|
||||
func (i AnyID) Valid() bool {
|
||||
_, err := primitive.ObjectIDFromHex(string(i))
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func (i AnyID) AsAny() AnyID {
|
||||
return AnyID(i)
|
||||
}
|
||||
|
||||
func NewAnyID() AnyID {
|
||||
return AnyID(primitive.NewObjectID().Hex())
|
||||
}
|
||||
|
||||
// ================================ JobLogID (ids.go) ================================
|
||||
|
||||
func (i JobLogID) MarshalBSONValue() (bsontype.Type, []byte, error) {
|
||||
if objId, err := primitive.ObjectIDFromHex(string(i)); err == nil {
|
||||
return bson.MarshalValue(objId)
|
||||
} else {
|
||||
return 0, nil, exerr.New(exerr.TypeMarshalEntityID, "Failed to marshal JobLogID("+i.String()+") to ObjectId").Str("value", string(i)).Type("type", i).Build()
|
||||
}
|
||||
}
|
||||
|
||||
func (i JobLogID) String() string {
|
||||
return string(i)
|
||||
}
|
||||
|
||||
func (i JobLogID) ObjID() (primitive.ObjectID, error) {
|
||||
return primitive.ObjectIDFromHex(string(i))
|
||||
}
|
||||
|
||||
func (i JobLogID) Valid() bool {
|
||||
_, err := primitive.ObjectIDFromHex(string(i))
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func (i JobLogID) AsAny() AnyID {
|
||||
return AnyID(i)
|
||||
}
|
||||
|
||||
func NewJobLogID() JobLogID {
|
||||
return JobLogID(primitive.NewObjectID().Hex())
|
||||
}
|
||||
|
||||
// ================================ JobExecutionID (ids.go) ================================
|
||||
|
||||
func (i JobExecutionID) MarshalBSONValue() (bsontype.Type, []byte, error) {
|
||||
if objId, err := primitive.ObjectIDFromHex(string(i)); err == nil {
|
||||
return bson.MarshalValue(objId)
|
||||
} else {
|
||||
return 0, nil, exerr.New(exerr.TypeMarshalEntityID, "Failed to marshal JobExecutionID("+i.String()+") to ObjectId").Str("value", string(i)).Type("type", i).Build()
|
||||
}
|
||||
}
|
||||
|
||||
func (i JobExecutionID) String() string {
|
||||
return string(i)
|
||||
}
|
||||
|
||||
func (i JobExecutionID) ObjID() (primitive.ObjectID, error) {
|
||||
return primitive.ObjectIDFromHex(string(i))
|
||||
}
|
||||
|
||||
func (i JobExecutionID) Valid() bool {
|
||||
_, err := primitive.ObjectIDFromHex(string(i))
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func (i JobExecutionID) AsAny() AnyID {
|
||||
return AnyID(i)
|
||||
}
|
||||
|
||||
func NewJobExecutionID() JobExecutionID {
|
||||
return JobExecutionID(primitive.NewObjectID().Hex())
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
package models
|
||||
|
||||
import (
|
||||
"gogs.mikescher.com/BlackForestBytes/goext/rfctime"
|
||||
"time"
|
||||
)
|
||||
|
||||
type JobStatus string //@enum:type
|
||||
|
||||
const (
|
||||
JobStatusRunning JobStatus = "RUNNING"
|
||||
JobStatusSuccess JobStatus = "SUCCESS"
|
||||
JobStatusFailed JobStatus = "FAILED"
|
||||
)
|
||||
|
||||
type JobExecution struct {
|
||||
JobExecutionID JobExecutionID `bson:"_id,omitempty" json:"id"`
|
||||
JobName string `bson:"jobName" json:"jobName"`
|
||||
StartTime rfctime.RFC3339NanoTime `bson:"startTime" json:"startTime"`
|
||||
EndTime *rfctime.RFC3339NanoTime `bson:"endTime" json:"endTime"`
|
||||
Changes int `bson:"changes" json:"changes"`
|
||||
Status JobStatus `bson:"status" json:"status"`
|
||||
Error *string `bson:"error" json:"error"`
|
||||
}
|
||||
|
||||
func (u JobExecution) GetID() AnyID {
|
||||
return u.JobExecutionID.AsAny()
|
||||
}
|
||||
|
||||
func (u JobExecution) GetCreationTime() time.Time {
|
||||
return u.StartTime.Time()
|
||||
}
|
||||
|
||||
func (u JobExecution) Delta() time.Duration {
|
||||
if u.EndTime != nil {
|
||||
return u.EndTime.Sub(u.StartTime)
|
||||
} else {
|
||||
return time.Now().Sub(u.StartTime.Time())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
package models
|
||||
|
||||
import (
|
||||
"gogs.mikescher.com/BlackForestBytes/goext/rfctime"
|
||||
"time"
|
||||
)
|
||||
|
||||
type JobLogLevel string //@enum:type
|
||||
|
||||
const (
|
||||
JobLogLevelDebug JobLogLevel = "DEBUG"
|
||||
JobLogLevelInfo JobLogLevel = "INFO"
|
||||
JobLogLevelWarn JobLogLevel = "WARN"
|
||||
JobLogLevelError JobLogLevel = "ERROR"
|
||||
JobLogLevelFatal JobLogLevel = "FATAL"
|
||||
)
|
||||
|
||||
type JobLog struct {
|
||||
JobLogID JobLogID `bson:"_id,omitempty" json:"id"`
|
||||
JobExecutionID JobExecutionID `bson:"executionId" json:"executionId"`
|
||||
JobName string `bson:"jobName" json:"jobName"`
|
||||
Type string `bson:"type" json:"type"`
|
||||
Time rfctime.RFC3339NanoTime `bson:"time" json:"time"`
|
||||
Message string `bson:"message" json:"message"`
|
||||
Level JobLogLevel `bson:"level" json:"level"`
|
||||
Extra any `bson:"extra" json:"extra"`
|
||||
Deleted *rfctime.RFC3339NanoTime `bson:"deleted" json:"deleted"`
|
||||
}
|
||||
|
||||
func (u JobLog) GetID() AnyID {
|
||||
return u.JobLogID.AsAny()
|
||||
}
|
||||
|
||||
func (u JobLog) GetCreationTime() time.Time {
|
||||
return u.Time.Time()
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package models
|
||||
|
||||
type Server struct {
|
||||
Port int
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
replace rfctime.RFC3339NanoTime string
|
||||
replace rfctime.SecondsF64 string
|
||||
replace cryptext.PassHash string
|
||||
replace enums.EnumMetaValue any
|
|
@ -0,0 +1,35 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>API Documentation</title>
|
||||
<link rel="stylesheet" href="swagger-ui.css" />
|
||||
<!-- <link rel="stylesheet" href="themes/theme-feeling-blue.css" /> -->
|
||||
<!-- <link rel="stylesheet" href="themes/theme-flattop.css" /> -->
|
||||
<!-- <link rel="stylesheet" href="themes/theme-material.css" /> -->
|
||||
<!-- <link rel="stylesheet" href="themes/theme-monokai.css" /> -->
|
||||
<!-- <link rel="stylesheet" href="themes/theme-muted.css" /> -->
|
||||
<!-- <link rel="stylesheet" href="themes/theme-newspaper.css" /> -->
|
||||
<!-- <link rel="stylesheet" href="themes/theme-outline.css" /> -->
|
||||
</head>
|
||||
<body>
|
||||
<div id="swagger-ui"></div>
|
||||
<script src="swagger-ui-bundle.js" crossorigin></script>
|
||||
<script src="swagger-ui-standalone-preset.js" crossorigin></script>
|
||||
<script>
|
||||
window.onload = () => {
|
||||
window.ui = SwaggerUIBundle({
|
||||
url: './swagger.json',
|
||||
dom_id: '#swagger-ui',
|
||||
presets: [
|
||||
SwaggerUIBundle.presets.apis,
|
||||
SwaggerUIStandalonePreset
|
||||
],
|
||||
layout: "StandaloneLayout",
|
||||
defaultModelsExpandDepth: 0,
|
||||
});
|
||||
};
|
||||
</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
|
@ -0,0 +1,73 @@
|
|||
package swagger
|
||||
|
||||
import (
|
||||
"embed"
|
||||
_ "embed"
|
||||
"github.com/gin-gonic/gin"
|
||||
"gogs.mikescher.com/BlackForestBytes/goext/ginext"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
//go:embed *.html
|
||||
//go:embed *.json
|
||||
//go:embed *.yaml
|
||||
//go:embed *.js
|
||||
//go:embed *.css
|
||||
//go:embed themes/*
|
||||
var assets embed.FS
|
||||
|
||||
func getAsset(fn string) ([]byte, string, bool) {
|
||||
data, err := assets.ReadFile(fn)
|
||||
if err != nil {
|
||||
return nil, "", false
|
||||
}
|
||||
|
||||
mime := "text/plain"
|
||||
|
||||
lowerFN := strings.ToLower(fn)
|
||||
if strings.HasSuffix(lowerFN, ".html") || strings.HasSuffix(lowerFN, ".htm") {
|
||||
mime = "text/html"
|
||||
} else if strings.HasSuffix(lowerFN, ".css") {
|
||||
mime = "text/css"
|
||||
} else if strings.HasSuffix(lowerFN, ".js") {
|
||||
mime = "text/javascript"
|
||||
} else if strings.HasSuffix(lowerFN, ".json") {
|
||||
mime = "application/json"
|
||||
} else if strings.HasSuffix(lowerFN, ".jpeg") || strings.HasSuffix(lowerFN, ".jpg") {
|
||||
mime = "image/jpeg"
|
||||
} else if strings.HasSuffix(lowerFN, ".png") {
|
||||
mime = "image/png"
|
||||
} else if strings.HasSuffix(lowerFN, ".svg") {
|
||||
mime = "image/svg+xml"
|
||||
}
|
||||
|
||||
return data, mime, true
|
||||
}
|
||||
|
||||
func Handle(pctx ginext.PreContext) ginext.HTTPResponse {
|
||||
type uri struct {
|
||||
Filename string `uri:"sub"`
|
||||
}
|
||||
|
||||
var u uri
|
||||
|
||||
ctx, _, errResp := pctx.URI(&u).Start()
|
||||
if errResp != nil {
|
||||
return *errResp
|
||||
}
|
||||
defer ctx.Cancel()
|
||||
|
||||
u.Filename = strings.TrimLeft(u.Filename, "/")
|
||||
|
||||
if u.Filename == "" {
|
||||
index, _, _ := getAsset("index.html")
|
||||
return ginext.Data(http.StatusOK, "text/html", index)
|
||||
}
|
||||
|
||||
if data, mime, ok := getAsset(u.Filename); ok {
|
||||
return ginext.Data(http.StatusOK, mime, data)
|
||||
}
|
||||
|
||||
return ginext.JSON(http.StatusNotFound, gin.H{"error": "AssetNotFound", "filename": u.Filename})
|
||||
}
|
|
@ -0,0 +1,292 @@
|
|||
{
|
||||
"swagger": "2.0",
|
||||
"info": {
|
||||
"title": "LocalHostBunny",
|
||||
"contact": {},
|
||||
"version": "1.0"
|
||||
},
|
||||
"host": "localhost",
|
||||
"basePath": "/api/v1/",
|
||||
"paths": {
|
||||
"/api/health": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Common"
|
||||
],
|
||||
"summary": "Server Health-checks",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handler.Health.response"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.APIError"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/ping": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Common"
|
||||
],
|
||||
"summary": "Simple endpoint to test connection (any http method)",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handler.pingResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.APIError"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"put": {
|
||||
"tags": [
|
||||
"Common"
|
||||
],
|
||||
"summary": "Simple endpoint to test connection (any http method)",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handler.pingResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.APIError"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"tags": [
|
||||
"Common"
|
||||
],
|
||||
"summary": "Simple endpoint to test connection (any http method)",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handler.pingResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.APIError"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"tags": [
|
||||
"Common"
|
||||
],
|
||||
"summary": "Simple endpoint to test connection (any http method)",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handler.pingResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.APIError"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"patch": {
|
||||
"tags": [
|
||||
"Common"
|
||||
],
|
||||
"summary": "Simple endpoint to test connection (any http method)",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handler.pingResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.APIError"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/sleep/:secs": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"Common"
|
||||
],
|
||||
"summary": "Return 200 after x seconds",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "number",
|
||||
"description": "sleep delay (in seconds)",
|
||||
"name": "secs",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handler.Sleep.response"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.APIError"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.APIError"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/server": {
|
||||
"get": {
|
||||
"summary": "List running server",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handler.ListServer.response"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.APIError"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.APIError"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"handler.Health.response": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"status": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"handler.ListServer.response": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"server": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/models.Server"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"handler.Sleep.response": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"duration": {
|
||||
"type": "number"
|
||||
},
|
||||
"end": {
|
||||
"type": "string"
|
||||
},
|
||||
"start": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"handler.pingResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"info": {
|
||||
"$ref": "#/definitions/handler.pingResponseInfo"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"handler.pingResponseInfo": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"addr": {
|
||||
"type": "string"
|
||||
},
|
||||
"headers": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"method": {
|
||||
"type": "string"
|
||||
},
|
||||
"request": {
|
||||
"type": "string"
|
||||
},
|
||||
"uri": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.APIError": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"errorcode": {
|
||||
"type": "string"
|
||||
},
|
||||
"fapiMessage": {
|
||||
"type": "string"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.Server": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"port": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,188 @@
|
|||
basePath: /api/v1/
|
||||
definitions:
|
||||
handler.Health.response:
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
type: object
|
||||
handler.ListServer.response:
|
||||
properties:
|
||||
server:
|
||||
items:
|
||||
$ref: '#/definitions/models.Server'
|
||||
type: array
|
||||
type: object
|
||||
handler.Sleep.response:
|
||||
properties:
|
||||
duration:
|
||||
type: number
|
||||
end:
|
||||
type: string
|
||||
start:
|
||||
type: string
|
||||
type: object
|
||||
handler.pingResponse:
|
||||
properties:
|
||||
info:
|
||||
$ref: '#/definitions/handler.pingResponseInfo'
|
||||
message:
|
||||
type: string
|
||||
type: object
|
||||
handler.pingResponseInfo:
|
||||
properties:
|
||||
addr:
|
||||
type: string
|
||||
headers:
|
||||
additionalProperties:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
type: object
|
||||
method:
|
||||
type: string
|
||||
request:
|
||||
type: string
|
||||
uri:
|
||||
type: string
|
||||
type: object
|
||||
models.APIError:
|
||||
properties:
|
||||
errorcode:
|
||||
type: string
|
||||
fapiMessage:
|
||||
type: string
|
||||
message:
|
||||
type: string
|
||||
type: object
|
||||
models.Server:
|
||||
properties:
|
||||
port:
|
||||
type: integer
|
||||
type: object
|
||||
host: localhost
|
||||
info:
|
||||
contact: {}
|
||||
title: LocalHostBunny
|
||||
version: "1.0"
|
||||
paths:
|
||||
/api/health:
|
||||
get:
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/handler.Health.response'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/models.APIError'
|
||||
summary: Server Health-checks
|
||||
tags:
|
||||
- Common
|
||||
/api/ping:
|
||||
delete:
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/handler.pingResponse'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/models.APIError'
|
||||
summary: Simple endpoint to test connection (any http method)
|
||||
tags:
|
||||
- Common
|
||||
get:
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/handler.pingResponse'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/models.APIError'
|
||||
summary: Simple endpoint to test connection (any http method)
|
||||
tags:
|
||||
- Common
|
||||
patch:
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/handler.pingResponse'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/models.APIError'
|
||||
summary: Simple endpoint to test connection (any http method)
|
||||
tags:
|
||||
- Common
|
||||
post:
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/handler.pingResponse'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/models.APIError'
|
||||
summary: Simple endpoint to test connection (any http method)
|
||||
tags:
|
||||
- Common
|
||||
put:
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/handler.pingResponse'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/models.APIError'
|
||||
summary: Simple endpoint to test connection (any http method)
|
||||
tags:
|
||||
- Common
|
||||
/api/sleep/:secs:
|
||||
post:
|
||||
parameters:
|
||||
- description: sleep delay (in seconds)
|
||||
in: path
|
||||
name: secs
|
||||
required: true
|
||||
type: number
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/handler.Sleep.response'
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
$ref: '#/definitions/models.APIError'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/models.APIError'
|
||||
summary: Return 200 after x seconds
|
||||
tags:
|
||||
- Common
|
||||
/server:
|
||||
get:
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/handler.ListServer.response'
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
$ref: '#/definitions/models.APIError'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/models.APIError'
|
||||
summary: List running server
|
||||
swagger: "2.0"
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue