From fc02a173c2cb13318cc7dbca173ad9eb2b9bb6aa Mon Sep 17 00:00:00 2001 From: Logan Cox Date: Sat, 25 May 2024 18:46:09 +0100 Subject: [PATCH] feat: base web server with healthz endpoint and tests --- .gitignore | 7 +++++++ Dockerfile | 29 +++++++++++++++++++++++++++++ Makefile | 29 +++++++++++++++++++++++++++++ docker-compose.yaml | 9 +++++++++ go.mod | 3 +++ go.sum | 0 handlers.go | 15 +++++++++++++++ handlers_test.go | 25 +++++++++++++++++++++++++ httphelper.go | 30 ++++++++++++++++++++++++++++++ main.go | 25 +++++++++++++++++++++++++ 10 files changed, 172 insertions(+) create mode 100644 Dockerfile create mode 100644 Makefile create mode 100644 docker-compose.yaml create mode 100644 go.mod create mode 100644 go.sum create mode 100644 handlers.go create mode 100644 handlers_test.go create mode 100644 httphelper.go create mode 100644 main.go diff --git a/.gitignore b/.gitignore index 3b735ec..83bea1f 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,10 @@ # Go workspace file go.work + + +# env file +.env + +# binary +url-short diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..8b6055c --- /dev/null +++ b/Dockerfile @@ -0,0 +1,29 @@ +FROM alpine:3.20.0 AS base + +RUN apk update +RUN apk upgrade +RUN apk add --update go=1.22.3-r0 + +FROM base AS tester + +WORKDIR /opt/url-short + +ADD . /opt/url-short + +CMD ["go", "test"] + +FROM base AS builder + +WORKDIR /build + +ADD . /build + +RUN go build -o main . + +FROM builder AS production + +WORKDIR /opt/url-short/ + +COPY --from=builder /build/main . + +CMD ["./main"] diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..b5f54ef --- /dev/null +++ b/Makefile @@ -0,0 +1,29 @@ +fmt: + go fmt ./... +.PHONY:fmt + +lint: fmt + golangci-lint run -v +.PHONY:lint + +vet: lint + go vet ./... +.PHONY:vet + +build: + docker build . -t "url-short:latest" +.PHONY:build + +run: + docker compose up -d +.PHONY:run + +stop: + docker compose down +.PHONY:stop + +test: + docker build . -t "url-short:test" --target tester + docker run -t "url-short:test" +.PHONY:test + diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..c2a4566 --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,9 @@ +version: '3.1' + +services: + url-short: + image: url-short:latest + env_file: + - .env + ports: + - 5001:8080 diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..1e12bb0 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module url-short + +go 1.22.3 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..e69de29 diff --git a/handlers.go b/handlers.go new file mode 100644 index 0000000..dc350bf --- /dev/null +++ b/handlers.go @@ -0,0 +1,15 @@ +package main + +import "net/http" + +type apiConfig struct{} + +func (apiCfg *apiConfig) healthz(w http.ResponseWriter, r *http.Request) { + payload := struct { + Status string `json:"status"` + }{ + Status: "ok", + } + + respondWithJSON(w, http.StatusOK, payload) +} diff --git a/handlers_test.go b/handlers_test.go new file mode 100644 index 0000000..b8c33db --- /dev/null +++ b/handlers_test.go @@ -0,0 +1,25 @@ +package main + +import ( + "net/http" + "net/http/httptest" + "testing" +) + +func TestHealthEndpoint(t *testing.T){ + t.Run("test healthz endpoint", func(t *testing.T){ + request, _ := http.NewRequest(http.MethodGet, "/api/v1/healthz", nil) + response := httptest.NewRecorder() + + apiCfg := apiConfig{} + + apiCfg.healthz(response, request) + + got := response.Body.String() + want := `{"status":"ok"}` + + if got != want { + t.Errorf("got %q wanted %q", got, want) + } + }) +} diff --git a/httphelper.go b/httphelper.go new file mode 100644 index 0000000..34f8add --- /dev/null +++ b/httphelper.go @@ -0,0 +1,30 @@ +package main + +import ( + "encoding/json" + "log" + "net/http" +) + +func respondWithJSON(w http.ResponseWriter, status int, payload interface{}) { + data, err := json.Marshal(payload) + + if err != nil { + log.Printf("can not Marshal payload %v", payload) + return + } + + w.Header().Set("content-type", "application/json") + w.WriteHeader(status) + w.Write(data) +} + +func respondWithError(w http.ResponseWriter, code int, msg string) { + errorResponse := struct { + Error string `json:"error"` + }{ + Error: msg, + } + + respondWithJSON(w, code, errorResponse) +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..720189c --- /dev/null +++ b/main.go @@ -0,0 +1,25 @@ +package main + +import ( + "log" + "net/http" + "os" +) + +func main() { + serverPort := os.Getenv("SERVER_PORT") + + mux := http.NewServeMux() + + server := &http.Server{ + Addr: ":" + serverPort, + Handler: mux, + } + + apiCfg := apiConfig{} + + mux.HandleFunc("GET /api/v1/healthz", apiCfg.healthz) + + log.Printf("Serving port : %v \n", serverPort) + log.Fatal(server.ListenAndServe()) +}