From cd204ebcf5bdbba4931add2eb220a5cc8f65ab2c Mon Sep 17 00:00:00 2001 From: lbenedar Date: Fri, 10 Apr 2026 18:34:01 +0300 Subject: [PATCH] add healthcheck route --- cmd/api/errors.go | 24 ++++++++++++++++++++++++ cmd/api/healthcheck.go | 21 +++++++++++++++++++++ cmd/api/helpers.go | 25 +++++++++++++++++++++++++ cmd/api/main.go | 2 ++ cmd/api/router.go | 1 + internal/foundry/foundry.go | 27 ++++++++++++++------------- internal/foundry/types/status.go | 14 +++++++------- 7 files changed, 94 insertions(+), 20 deletions(-) create mode 100644 cmd/api/errors.go create mode 100644 cmd/api/healthcheck.go create mode 100644 cmd/api/helpers.go diff --git a/cmd/api/errors.go b/cmd/api/errors.go new file mode 100644 index 0000000..dfa566d --- /dev/null +++ b/cmd/api/errors.go @@ -0,0 +1,24 @@ +package main + +import "net/http" + +func (app *application) logError(r *http.Request, err error) { + app.slogger.Error(err.Error(), "request_method", r.Method, "request_url", r.URL.String()) +} + +func (app *application) errorResponse(w http.ResponseWriter, r *http.Request, status int, message any) { + env := envelope{"error": message} + + err := app.writeJSON(w, status, env, nil) + if err != nil { + app.logError(r, err) + w.WriteHeader(500) + } +} + +func (app *application) serverErrorResponse(w http.ResponseWriter, r *http.Request, err error) { + app.logError(r, err) + + message := "the server encountered a problem and could not process your request" + app.errorResponse(w, r, http.StatusInternalServerError, message) +} diff --git a/cmd/api/healthcheck.go b/cmd/api/healthcheck.go new file mode 100644 index 0000000..a334750 --- /dev/null +++ b/cmd/api/healthcheck.go @@ -0,0 +1,21 @@ +package main + +import "net/http" + +func (app *application) healthcheckHandler(w http.ResponseWriter, r *http.Request) { + env := envelope{ + "status": "available", + "system_info": map[string]string{ + "environment": app.cfg.env, + "version": version, + }, + "foundry": map[string]any{ + "status": app.foundryApp.Status, + "is_available": app.foundryApp.IsAvailable, + }, + } + err := app.writeJSON(w, http.StatusOK, env, nil) + if err != nil { + app.serverErrorResponse(w, r, err) + } +} diff --git a/cmd/api/helpers.go b/cmd/api/helpers.go new file mode 100644 index 0000000..90c937b --- /dev/null +++ b/cmd/api/helpers.go @@ -0,0 +1,25 @@ +package main + +import ( + "encoding/json" + "net/http" +) + +type envelope map[string]any + +func (app *application) writeJSON(w http.ResponseWriter, status int, data envelope, headers http.Header) error { + js, err := json.MarshalIndent(data, "", "\t") + if err != nil { + return err + } + js = append(js, '\n') + for key, value := range headers { + w.Header()[key] = value + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(status) + w.Write(js) + + return nil +} diff --git a/cmd/api/main.go b/cmd/api/main.go index 31e3ae7..15cb1d1 100644 --- a/cmd/api/main.go +++ b/cmd/api/main.go @@ -44,6 +44,8 @@ type application struct { logger *log.Logger } +var version = "0.1.0" + func openDB(cfg config) (*sql.DB, error) { db, err := sql.Open("sqlite3", cfg.db.dsn) if err != nil { diff --git a/cmd/api/router.go b/cmd/api/router.go index f2f1b75..eff6295 100644 --- a/cmd/api/router.go +++ b/cmd/api/router.go @@ -11,6 +11,7 @@ func (app *application) routes() http.Handler { router.HandlerFunc(http.MethodGet, "/", app.ShowText) router.HandlerFunc(http.MethodGet, "/v1/when", app.WhenNextSession) + router.HandlerFunc(http.MethodGet, "/v1/healthcheck", app.healthcheckHandler) return router } diff --git a/internal/foundry/foundry.go b/internal/foundry/foundry.go index 51ccc50..a220bdd 100644 --- a/internal/foundry/foundry.go +++ b/internal/foundry/foundry.go @@ -7,6 +7,7 @@ import ( "sync" "time" + "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/actions" "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/transport" "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/types" ) @@ -73,21 +74,21 @@ func (foundry *FoundryApi) ServeWebSocket() error { } case types.RespServerChangeCode: foundry.IsAvailable = true - // data, err := actions.SplitToTypeAndData([]byte(message.MsgJson)) - // if err != nil { - // wsChannels.Err() <- &types.FoundryError{Direction: types.ReaderCode, Err: err, Type: types.WebSocketCode} - // continue - // } + data, err := actions.SplitToTypeAndData([]byte(message.MsgJson)) + if err != nil { + wsChannels.Err() <- &types.FoundryError{Direction: types.ReaderCode, Err: err, Type: types.WebSocketCode} + continue + } - // if data == nil { - // continue - // } + if data == nil { + continue + } - // err = data.Action(foundry.transport, &foundry.Status) - // if err != nil { - // wsChannels.Err() <- &types.FoundryError{Direction: types.ReaderCode, Err: err, Type: types.WebSocketCode} - // continue - // } + err = data.Action(foundry.transport, &foundry.Status) + if err != nil { + wsChannels.Err() <- &types.FoundryError{Direction: types.ReaderCode, Err: err, Type: types.WebSocketCode} + continue + } case types.RespDataCode: foundry.transport.ExchangeChan.Msgs[message.Id] = make(chan []byte, 1) foundry.transport.ExchangeChan.Msgs[message.Id] <- []byte(message.MsgJson) diff --git a/internal/foundry/types/status.go b/internal/foundry/types/status.go index 26cff32..1623578 100644 --- a/internal/foundry/types/status.go +++ b/internal/foundry/types/status.go @@ -3,13 +3,13 @@ package types import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/json" type FoundryStatus struct { - IsActive bool - Version string - World string - System string - SystemVersion string - Users int - Uptime int64 + IsActive bool `json:"on_world"` + Version string `json:"version"` + World string `json:"world,omitempty"` + System string `json:"system,omitempty"` + SystemVersion string `json:"-"` + Users int `json:"-"` + Uptime int64 `json:"-"` } func NewFoundryStatus(status *json.Status) *FoundryStatus {