Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 7 additions & 18 deletions database/connection.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package database

import (
"database/sql"
"fmt"
"log/slog"

Expand Down Expand Up @@ -47,27 +46,17 @@ func (c *Connection) Close() bool {
return true
}

func (c *Connection) Ping() {
var driver *sql.DB

slog.Info("Database ping started", "separator", "---------")

if conn, err := c.driver.DB(); err != nil {
slog.Error("Error retrieving the db driver", "error", err.Error())

return
} else {
driver = conn
slog.Info("Database driver acquired", "type", fmt.Sprintf("%T", driver))
func (c *Connection) Ping() error {
conn, err := c.driver.DB()
if err != nil {
return fmt.Errorf("error retrieving the db driver: %w", err)
}

if err := driver.Ping(); err != nil {
slog.Error("Error pinging the db driver", "error", err.Error())
if err := conn.Ping(); err != nil {
return fmt.Errorf("error pinging the db driver: %w", err)
}

slog.Info("Database driver is healthy", "stats", driver.Stats())

slog.Info("Database ping completed", "separator", "---------")
return nil
}

func (c *Connection) Sql() *gorm.DB {
Expand Down
14 changes: 7 additions & 7 deletions handler/ping.go → handler/keep_alive.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@ import (
"github.com/oullin/pkg/portal"
)

type PingHandler struct {
type KeepAliveHandler struct {
env *env.Ping
}

func MakePingHandler(e *env.Ping) PingHandler {
return PingHandler{env: e}
func MakeKeepAliveHandler(e *env.Ping) KeepAliveHandler {
return KeepAliveHandler{env: e}
}

func (h PingHandler) Handle(w baseHttp.ResponseWriter, r *baseHttp.Request) *http.ApiError {
func (h KeepAliveHandler) Handle(w baseHttp.ResponseWriter, r *baseHttp.Request) *http.ApiError {
user, pass, ok := r.BasicAuth()

if !ok || h.env.HasInvalidCreds(user, pass) {
Expand All @@ -29,16 +29,16 @@ func (h PingHandler) Handle(w baseHttp.ResponseWriter, r *baseHttp.Request) *htt
)
}

resp := http.MakeResponseFrom("0.0.1", w, r)
resp := http.MakeNoCacheResponse(w, r)
now := time.Now().UTC()

data := payload.PingResponse{
data := payload.KeepAliveResponse{
Message: "pong",
DateTime: now.Format(portal.DatesLayout),
}

if err := resp.RespondOk(data); err != nil {
return http.LogInternalError("could not encode ping response", err)
return http.LogInternalError("could not encode keep-alive response", err)
}

return nil
Expand Down
51 changes: 51 additions & 0 deletions handler/keep_alive_db.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package handler

import (
"fmt"
baseHttp "net/http"
"time"

"github.com/oullin/database"
"github.com/oullin/handler/payload"
"github.com/oullin/metal/env"
"github.com/oullin/pkg/http"
"github.com/oullin/pkg/portal"
)

type KeepAliveDBHandler struct {
env *env.Ping
db *database.Connection
}

func MakeKeepAliveDBHandler(e *env.Ping, db *database.Connection) KeepAliveDBHandler {
return KeepAliveDBHandler{env: e, db: db}
}

func (h KeepAliveDBHandler) Handle(w baseHttp.ResponseWriter, r *baseHttp.Request) *http.ApiError {
user, pass, ok := r.BasicAuth()

if !ok || h.env.HasInvalidCreds(user, pass) {
return http.LogUnauthorisedError(
"invalid credentials",
fmt.Errorf("invalid credentials"),
)
}

if err := h.db.Ping(); err != nil {
return http.LogInternalError("database ping failed", err)
}

resp := http.MakeNoCacheResponse(w, r)
now := time.Now().UTC()

data := payload.KeepAliveResponse{
Message: "pong",
DateTime: now.Format(portal.DatesLayout),
}

if err := resp.RespondOk(data); err != nil {
return http.LogInternalError("could not encode keep-alive response", err)
}

return nil
}
61 changes: 61 additions & 0 deletions handler/keep_alive_db_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package handler

import (
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"time"

"github.com/oullin/handler/payload"
handlertests "github.com/oullin/handler/tests"
"github.com/oullin/metal/env"
"github.com/oullin/pkg/portal"
)

func TestKeepAliveDBHandler(t *testing.T) {
db, _ := handlertests.MakeTestDB(t)
e := env.Ping{Username: "user", Password: "pass"}
h := MakeKeepAliveDBHandler(&e, db)

t.Run("valid credentials", func(t *testing.T) {
req := httptest.NewRequest("GET", "/ping-db", nil)
req.SetBasicAuth("user", "pass")
rec := httptest.NewRecorder()
if err := h.Handle(rec, req); err != nil {
t.Fatalf("handle err: %v", err)
}
if rec.Code != http.StatusOK {
t.Fatalf("status %d", rec.Code)
}
var resp payload.KeepAliveResponse
if err := json.NewDecoder(rec.Body).Decode(&resp); err != nil {
t.Fatalf("decode: %v", err)
}
if resp.Message != "pong" {
t.Fatalf("unexpected message: %s", resp.Message)
}
if _, err := time.Parse(portal.DatesLayout, resp.DateTime); err != nil {
t.Fatalf("invalid datetime: %v", err)
}
})

t.Run("invalid credentials", func(t *testing.T) {
req := httptest.NewRequest("GET", "/ping-db", nil)
req.SetBasicAuth("bad", "creds")
rec := httptest.NewRecorder()
if err := h.Handle(rec, req); err == nil || err.Status != http.StatusUnauthorized {
t.Fatalf("expected unauthorized, got %#v", err)
}
})

t.Run("db ping failure", func(t *testing.T) {
db.Close()
req := httptest.NewRequest("GET", "/ping-db", nil)
req.SetBasicAuth("user", "pass")
rec := httptest.NewRecorder()
if err := h.Handle(rec, req); err == nil || err.Status != http.StatusInternalServerError {
t.Fatalf("expected internal error, got %#v", err)
}
})
}
6 changes: 3 additions & 3 deletions handler/ping_test.go → handler/keep_alive_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ import (
"github.com/oullin/pkg/portal"
)

func TestPingHandler(t *testing.T) {
func TestKeepAliveHandler(t *testing.T) {
e := env.Ping{Username: "user", Password: "pass"}
h := MakePingHandler(&e)
h := MakeKeepAliveHandler(&e)

t.Run("valid credentials", func(t *testing.T) {
req := httptest.NewRequest("GET", "/ping", nil)
Expand All @@ -26,7 +26,7 @@ func TestPingHandler(t *testing.T) {
if rec.Code != http.StatusOK {
t.Fatalf("status %d", rec.Code)
}
var resp payload.PingResponse
var resp payload.KeepAliveResponse
if err := json.NewDecoder(rec.Body).Decode(&resp); err != nil {
t.Fatalf("decode: %v", err)
}
Expand Down
2 changes: 1 addition & 1 deletion handler/payload/ping.go → handler/payload/keep_alive.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package payload

type PingResponse struct {
type KeepAliveResponse struct {
Message string `json:"message"`
DateTime string `json:"date_time"`
}
4 changes: 3 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ func main() {
app.Boot()

// --- Testing
app.GetDB().Ping()
if err := app.GetDB().Ping(); err != nil {
slog.Error("database ping failed", "error", err)
}
slog.Info("Starting new server on :" + app.GetEnv().Network.HttpPort)
// ---

Expand Down
3 changes: 2 additions & 1 deletion metal/kernel/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@ func (a *App) Boot() {

router := *a.router

router.Ping()
router.KeepAlive()
router.KeepAliveDB()
router.Profile()
router.Experience()
router.Projects()
Expand Down
14 changes: 12 additions & 2 deletions metal/kernel/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,8 @@ func (r *Router) Signature() {
r.Mux.HandleFunc("POST /generate-signature", generate)
}

func (r *Router) Ping() {
abstract := handler.MakePingHandler(&r.Env.Ping)
func (r *Router) KeepAlive() {
abstract := handler.MakeKeepAliveHandler(&r.Env.Ping)

apiHandler := http.MakeApiHandler(
r.Pipeline.Chain(abstract.Handle),
Expand All @@ -90,6 +90,16 @@ func (r *Router) Ping() {
r.Mux.HandleFunc("GET /ping", apiHandler)
}

func (r *Router) KeepAliveDB() {
abstract := handler.MakeKeepAliveDBHandler(&r.Env.Ping, r.Db)

apiHandler := http.MakeApiHandler(
r.Pipeline.Chain(abstract.Handle),
)

r.Mux.HandleFunc("GET /ping-db", apiHandler)
}

func (r *Router) Profile() {
addStaticRoute(r, "/profile", "./storage/fixture/profile.json", handler.MakeProfileHandler)
}
Expand Down
42 changes: 42 additions & 0 deletions metal/kernel/router_keep_alive_db_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package kernel

import (
"net/http"
"net/http/httptest"
"testing"

handlertests "github.com/oullin/handler/tests"
"github.com/oullin/metal/env"
"github.com/oullin/pkg/middleware"
)

func TestKeepAliveDBRoute(t *testing.T) {
db, _ := handlertests.MakeTestDB(t)
r := Router{
Env: &env.Environment{Ping: env.Ping{Username: "user", Password: "pass"}},
Db: db,
Mux: http.NewServeMux(),
Pipeline: middleware.Pipeline{PublicMiddleware: middleware.MakePublicMiddleware("", false)},
}
r.KeepAliveDB()

t.Run("valid credentials", func(t *testing.T) {
req := httptest.NewRequest("GET", "/ping-db", nil)
req.SetBasicAuth("user", "pass")
rec := httptest.NewRecorder()
r.Mux.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("expected status %d, got %d", http.StatusOK, rec.Code)
}
})

t.Run("invalid credentials", func(t *testing.T) {
req := httptest.NewRequest("GET", "/ping-db", nil)
req.SetBasicAuth("bad", "creds")
rec := httptest.NewRecorder()
r.Mux.ServeHTTP(rec, req)
if rec.Code != http.StatusUnauthorized {
t.Fatalf("expected status %d, got %d", http.StatusUnauthorized, rec.Code)
}
})
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ import (
"github.com/oullin/pkg/middleware"
)

func TestPingRoute(t *testing.T) {
func TestKeepAliveRoute(t *testing.T) {
r := Router{
Env: &env.Environment{Ping: env.Ping{Username: "user", Password: "pass"}},
Mux: http.NewServeMux(),
Pipeline: middleware.Pipeline{PublicMiddleware: middleware.MakePublicMiddleware("", false)},
}
r.Ping()
r.KeepAlive()

t.Run("valid credentials", func(t *testing.T) {
req := httptest.NewRequest("GET", "/ping", nil)
Expand Down
19 changes: 19 additions & 0 deletions pkg/http/response.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,21 @@ func MakeResponseFrom(salt string, writer baseHttp.ResponseWriter, request *base
}
}

func MakeNoCacheResponse(writer baseHttp.ResponseWriter, request *baseHttp.Request) *Response {
cacheControl := "no-store"

return &Response{
writer: writer,
request: request,
cacheControl: cacheControl,
headers: func(w baseHttp.ResponseWriter) {
w.Header().Set("Content-Type", "application/json")
w.Header().Set("X-Content-Type-Options", "nosniff")
w.Header().Set("Cache-Control", cacheControl)
},
}
}

func (r *Response) WithHeaders(callback func(w baseHttp.ResponseWriter)) {
callback(r.writer)
}
Expand All @@ -53,6 +68,10 @@ func (r *Response) RespondOk(payload any) error {
}

func (r *Response) HasCache() bool {
if r.etag == "" {
return false
}

request := r.request

match := strings.TrimSpace(
Expand Down
Loading
Loading