From 0bd096c3d120d5a020dce9b36c4df80015e82f4a Mon Sep 17 00:00:00 2001 From: Henri Koski Date: Fri, 3 Jan 2025 18:37:52 +0200 Subject: [PATCH] Initial demo code --- .github/dependabot.yml | 6 +++++ .github/workflows/go.yaml | 39 +++++++++++++++++++++++++++ .gitignore | 1 + CODEOWNERS | 1 + Dockerfile | 3 +++ Makefile | 25 +++++++++++++++++ README.md | 3 ++- cmd/demo-cli/main.go | 23 ++++++++++++++++ cmd/demo-srv/main.go | 34 ++++++++++++++++++++++++ coverage.txt | 31 ++++++++++++++++++++++ db/db.go | 27 +++++++++++++++++++ docker-compose.yaml | 10 +++++++ go.mod | 9 +++++++ go.sum | 22 +++++++++++++++ handler/handler.go | 34 ++++++++++++++++++++++++ server/server.go | 56 +++++++++++++++++++++++++++++++++++++++ 16 files changed, 323 insertions(+), 1 deletion(-) create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/go.yaml create mode 100644 .gitignore create mode 100644 CODEOWNERS create mode 100644 Dockerfile create mode 100644 Makefile create mode 100644 cmd/demo-cli/main.go create mode 100644 cmd/demo-srv/main.go create mode 100644 coverage.txt create mode 100644 db/db.go create mode 100644 docker-compose.yaml create mode 100644 go.mod create mode 100644 go.sum create mode 100644 handler/handler.go create mode 100644 server/server.go diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..3938344 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,6 @@ +version: 2 +updates: + - package-ecosystem: "gomod" + directory: "/" + schedule: + interval: "daily" diff --git a/.github/workflows/go.yaml b/.github/workflows/go.yaml new file mode 100644 index 0000000..9fd51eb --- /dev/null +++ b/.github/workflows/go.yaml @@ -0,0 +1,39 @@ +name: Go + +on: + pull_request: + types: + - opened + - synchronize + - reopened + push: + branches: + - main + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup Go + uses: actions/setup-go@v5 + + - name: Download dependencies + run: go mod download + + - name: Test + env: + GOTESTSUM_JUNITFILE: junit.xml + run: make + + - name: Upload results to Codecov + uses: codecov/codecov-action@v4 + with: + token: ${{ secrets.CODECOV_TOKEN }} + + - name: Upload test results to Codecov + if: ${{ !cancelled() }} + uses: codecov/test-results-action@v1 + with: + token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b83d222 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target/ diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 0000000..9f04ba8 --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1 @@ +*@heppu diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..997f607 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,3 @@ +FROM alpine:3.12 +COPY target/demo-srv /demo-srv +ENTRYPOINT ["/demo-srv"] diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..45ed7c7 --- /dev/null +++ b/Makefile @@ -0,0 +1,25 @@ +BUILD_TARGETS = $(wildcard cmd/*) +TARGET_NAMES = $(BUILD_TARGETS:cmd/%=%) +BIN_DIR = target/bin +COVERAGE_TXT = target/coverage/coverage.txt + +.PHONY: test clean ${TARGET_NAMES} + +all: test ${TARGET_NAMES} + +${TARGET_NAMES}: + @echo "Building ${@}..." + @mkdir -p ${BIN_DIR} + go build -o ${BIN_DIR}/${@} ./cmd/${@}/ + @echo "Building ${@} done" + +test-unit: + mkdir -p target + go run gotest.tools/gotestsum@v1.12.0 -- -coverprofile=coverage.txt ./... + +test-integration: + mkdir -p target + go run gotest.tools/gotestsum@v1.12.0 -- -coverprofile=coverage.txt ./... + +clean: + git clean -Xdf diff --git a/README.md b/README.md index 177898e..9f16454 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,3 @@ # demo -Demo project + +Exmaple projects demontsrating usage of tstr package. diff --git a/cmd/demo-cli/main.go b/cmd/demo-cli/main.go new file mode 100644 index 0000000..378973d --- /dev/null +++ b/cmd/demo-cli/main.go @@ -0,0 +1,23 @@ +package main + +import ( + "fmt" + "log/slog" + "os" + + "github.com/go-tstr/demo/db" + "github.com/go-tstr/demo/handler" +) + +func main() { + dsn := os.Getenv("DSN") + + d, err := db.New(dsn) + if err != nil { + slog.Error("db.New failed", slog.String("error", err.Error())) + os.Exit(1) + } + + h := handler.New(d) + fmt.Println(h.Hello()) +} diff --git a/cmd/demo-srv/main.go b/cmd/demo-srv/main.go new file mode 100644 index 0000000..c19ee8d --- /dev/null +++ b/cmd/demo-srv/main.go @@ -0,0 +1,34 @@ +package main + +import ( + "context" + "log/slog" + "os" + "os/signal" + + "github.com/go-tstr/demo/db" + "github.com/go-tstr/demo/handler" + "github.com/go-tstr/demo/server" +) + +func main() { + addr := os.Getenv("ADDR") + dsn := os.Getenv("DSN") + + ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt) + defer stop() + + d, err := db.New(dsn) + if err != nil { + slog.Error("db.New failed", slog.String("error", err.Error())) + os.Exit(1) + } + h := handler.New(d) + + slog.Info("Starting server", slog.String("addr", addr)) + if err := server.Run(ctx, addr, h); err != nil { + slog.Error("server.Run failed", slog.String("error", err.Error())) + os.Exit(1) + } + slog.Info("Server stopped successfully") +} diff --git a/coverage.txt b/coverage.txt new file mode 100644 index 0000000..9cd67f2 --- /dev/null +++ b/coverage.txt @@ -0,0 +1,31 @@ +mode: set +github.com/go-tstr/demo/cmd/demo-cli/main.go:12.13,16.16 3 0 +github.com/go-tstr/demo/cmd/demo-cli/main.go:16.16,19.3 2 0 +github.com/go-tstr/demo/cmd/demo-cli/main.go:21.2,22.24 2 0 +github.com/go-tstr/demo/server/server.go:16.61,26.21 5 0 +github.com/go-tstr/demo/server/server.go:26.21,29.3 2 0 +github.com/go-tstr/demo/server/server.go:30.2,30.21 1 0 +github.com/go-tstr/demo/server/server.go:30.21,33.3 2 0 +github.com/go-tstr/demo/server/server.go:35.2,35.18 1 0 +github.com/go-tstr/demo/server/server.go:38.57,39.76 1 0 +github.com/go-tstr/demo/server/server.go:39.76,41.17 2 0 +github.com/go-tstr/demo/server/server.go:41.17,44.4 2 0 +github.com/go-tstr/demo/server/server.go:45.3,45.19 1 0 +github.com/go-tstr/demo/server/server.go:47.2,47.12 1 0 +github.com/go-tstr/demo/server/server.go:50.34,52.42 2 0 +github.com/go-tstr/demo/server/server.go:52.42,54.3 1 0 +github.com/go-tstr/demo/server/server.go:55.2,55.12 1 0 +github.com/go-tstr/demo/db/db.go:13.35,15.16 2 0 +github.com/go-tstr/demo/db/db.go:15.16,17.3 1 0 +github.com/go-tstr/demo/db/db.go:19.2,19.25 1 0 +github.com/go-tstr/demo/db/db.go:23.39,27.2 3 0 +github.com/go-tstr/demo/handler/handler.go:20.30,22.2 1 0 +github.com/go-tstr/demo/handler/handler.go:24.43,26.16 2 0 +github.com/go-tstr/demo/handler/handler.go:26.16,28.3 1 0 +github.com/go-tstr/demo/handler/handler.go:29.2,29.22 1 0 +github.com/go-tstr/demo/handler/handler.go:32.32,34.2 1 0 +github.com/go-tstr/demo/cmd/demo-srv/main.go:14.13,22.16 6 0 +github.com/go-tstr/demo/cmd/demo-srv/main.go:22.16,25.3 2 0 +github.com/go-tstr/demo/cmd/demo-srv/main.go:26.2,29.49 3 0 +github.com/go-tstr/demo/cmd/demo-srv/main.go:29.49,32.3 2 0 +github.com/go-tstr/demo/cmd/demo-srv/main.go:33.2,33.42 1 0 diff --git a/db/db.go b/db/db.go new file mode 100644 index 0000000..f3dc96e --- /dev/null +++ b/db/db.go @@ -0,0 +1,27 @@ +package db + +import ( + "time" + + "github.com/jmoiron/sqlx" +) + +type DB struct { + db *sqlx.DB +} + +func New(dsn string) (*DB, error) { + db, err := sqlx.Open("postgres", dsn) + if err != nil { + return nil, err + } + + return &DB{db: db}, nil +} + +// Now returns the current time from the database. +func (d *DB) Now() (time.Time, error) { + var now time.Time + err := d.db.Get(&now, "SELECT NOW()") + return now, err +} diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..94a5c95 --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,10 @@ +services: + db: + image: postgres:16 + container_name: postgres + environment: + POSTGRES_USER: example + POSTGRES_PASSWORD: example + POSTGRES_DB: example + ports: + - "5432:5432" diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..9188205 --- /dev/null +++ b/go.mod @@ -0,0 +1,9 @@ +module github.com/go-tstr/demo + +go 1.23.4 + +require ( + github.com/go-tstr/tstr v0.0.1 + github.com/heppu/errgroup v1.0.0 + github.com/jmoiron/sqlx v1.4.0 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..5d57b1b --- /dev/null +++ b/go.sum @@ -0,0 +1,22 @@ +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= +github.com/go-tstr/tstr v0.0.1 h1:UZVRncSDm5Zh1RvyGwAc2SxJ5Ab2NzbVm/9z8bbo6xs= +github.com/go-tstr/tstr v0.0.1/go.mod h1:Xr8fr9FwDNM602fS/zIGHNDlL22pJAdNgiTHkCQ3OSc= +github.com/heppu/errgroup v1.0.0 h1:Th073WwEpGARMkxWQnybOfcMuvozkBr7Kqvn2tmx7iU= +github.com/heppu/errgroup v1.0.0/go.mod h1:eiBTIbuHZPfUsa978/V4HmR1p1oSqtNTpc8XiqetgIg= +github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= +github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= +github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +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/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/handler/handler.go b/handler/handler.go new file mode 100644 index 0000000..6ce92da --- /dev/null +++ b/handler/handler.go @@ -0,0 +1,34 @@ +package handler + +import ( + "fmt" + "time" + + "github.com/go-tstr/tstr/strerr" +) + +const ErrFailedToGetTime = strerr.Error("failed to get time from storage") + +type Storage interface { + Now() (time.Time, error) +} + +type Handler struct { + s Storage +} + +func New(s Storage) *Handler { + return &Handler{s: s} +} + +func (d *Handler) Hello() (string, error) { + t, err := d.s.Now() + if err != nil { + return "", fmt.Errorf("%w: %w", ErrFailedToGetTime, err) + } + return hello(t), nil +} + +func hello(t time.Time) string { + return fmt.Sprintf("Hello world at %s", t) +} diff --git a/server/server.go b/server/server.go new file mode 100644 index 0000000..7d7d62e --- /dev/null +++ b/server/server.go @@ -0,0 +1,56 @@ +package server + +import ( + "context" + "errors" + "fmt" + "net/http" + + "github.com/heppu/errgroup" +) + +type Handler interface { + Hello() (string, error) +} + +func Run(ctx context.Context, addr string, h Handler) error { + srv := http.Server{ + Addr: addr, + Handler: Bind(http.NewServeMux(), h), + } + + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + eg := &errgroup.ErrGroup{} + eg.Go(func() error { + <-ctx.Done() + return srv.Close() + }) + eg.Go(func() error { + defer cancel() + return serve(&srv) + }) + + return eg.Wait() +} + +func Bind(mux *http.ServeMux, d Handler) *http.ServeMux { + mux.HandleFunc("GET /hello", func(w http.ResponseWriter, r *http.Request) { + h, err := d.Hello() + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + fmt.Fprint(w, h) + }) + return mux +} + +func serve(s *http.Server) error { + err := s.ListenAndServe() + if errors.Is(err, http.ErrServerClosed) { + return nil + } + return err +}