From 4f108e1b059b497474177b7eeb03666b84fee757 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20P=C3=A9rez?= Date: Wed, 20 Nov 2024 23:09:25 +0100 Subject: [PATCH] working on ron-example --- .env.example | 1 + .gitignore | 3 +- Makefile | 145 ++++++++++++++++++++++ cmd/database/migrations/001_schema.up.sql | 5 + cmd/database/queries/queries.sql | 4 + cmd/main.go | 74 +++++++++++ cmd/middleware.go | 38 ++++++ cmd/router.go | 19 +++ go.mod | 17 ++- go.sum | 79 ++++++++++++ internal/config/config.go | 67 +++++++++- internal/config/db.go | 23 ++++ internal/handlers/handlers.go | 38 ++++-- internal/handlers/token.go | 6 +- internal/repository/pgxrepo.go | 18 +++ internal/repository/querier.go | 7 ++ internal/sqlc/db.go | 32 +++++ internal/sqlc/models.go | 10 ++ internal/sqlc/payloads.go | 6 + internal/sqlc/querier.go | 15 +++ internal/sqlc/queries.sql.go | 23 ++++ main.go | 94 -------------- middleware.go | 20 --- router.go | 17 --- sqlc.yaml | 13 ++ 25 files changed, 627 insertions(+), 147 deletions(-) create mode 100644 .env.example create mode 100644 Makefile create mode 100644 cmd/database/migrations/001_schema.up.sql create mode 100644 cmd/database/queries/queries.sql create mode 100644 cmd/main.go create mode 100644 cmd/middleware.go create mode 100644 cmd/router.go create mode 100644 internal/config/db.go create mode 100644 internal/repository/pgxrepo.go create mode 100644 internal/repository/querier.go create mode 100644 internal/sqlc/db.go create mode 100644 internal/sqlc/models.go create mode 100644 internal/sqlc/payloads.go create mode 100644 internal/sqlc/querier.go create mode 100644 internal/sqlc/queries.sql.go delete mode 100644 main.go delete mode 100644 middleware.go delete mode 100644 router.go create mode 100644 sqlc.yaml diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..553016c --- /dev/null +++ b/.env.example @@ -0,0 +1 @@ +DATASOURCE="postgresql://developer:secret@localhost:5432/ron?sslmode=disable" \ No newline at end of file diff --git a/.gitignore b/.gitignore index 977a572..5a8d03f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .idea/ -logs/ \ No newline at end of file +logs/ +.env \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..2fb61e6 --- /dev/null +++ b/Makefile @@ -0,0 +1,145 @@ +GO ?= go +GOFMT ?= gofmt "-s" +GO_VERSION=$(shell $(GO) version | cut -c 14- | cut -d' ' -f1 | cut -d'.' -f2) +PACKAGES ?= $(shell $(GO) list ./...) +VETPACKAGES ?= $(shell $(GO) list ./...) +GOFILES := $(shell find . -name "*.go") + +.PHONY: sayhello +# Print Hello World +sayhello: + @echo "Hello World" + +.PHONY: dockerize +# Creates a development database. +dockerize: + docker run --name ron-db-dev -e POSTGRES_PASSWORD=secret -e POSTGRES_USER=developer -e POSTGRES_DB=ron -p 5432:5432 -d postgres:16.3-alpine3.20 + +.PHONY: undockerize +# Destroy a development database. +undockerize: + docker rm -f ron-db-dev + +.PHONY: migrateup +# Migrate all schemas, triggers and data located in cmd/database/migrations. +migrateup: + migrate -path cmd/database/migrations -database "postgresql://developer:secret@localhost:5432/ron?sslmode=disable" -verbose up + +.PHONY: sqlc +# Generate or recreate SQLC queries. +sqlc: + sqlc generate + +.PHONY: test +# Test all files and generate coverage file. +test: + $(GO) test -v -covermode=count -coverprofile=coverage.out $(PACKAGES) + +.PHONY: gomock +# Generate mock files. +gomock: + mockgen -package mock -destination internal/repository/mock/querier.go arena-coins/internal/repository ExtendedQuerier + +.PHONY: run +# Run project. +run: + $(GO) run ./cmd/. + +.PHONY: recreate +# Destroy development DB and generate ones. +recreate: + make undockerize + make dockerize + sleep 2 + make migrateup + +.PHONY: tidy +# Runs a go mod tidy +tidy: + $(GO) mod tidy + +.PHONY: build-linux +# Build and generate linux executable. +build-linux: + make tidy + make remove-debug + CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o ./tmp/arena ./cmd/. + +.PHONY: pack-docker +# Run docker build for pack binary and assets to Docker container. +pack-docker: + make test + make build-linux + docker build -t arena-coins:${version} -t arena-coins:latest . + +.PHONY: remove-debug +# Remove all debug entries for reduce size binary. +remove-debug: + find . -name "*.go" -type f -exec sed -i '/slog\.Debug/d' {} + + +.PHONY: fmt +# Ensure consistent code formatting. +fmt: + $(GOFMT) -w $(GOFILES) + +.PHONY: fmt-check +# format (check only). +fmt-check: + @diff=$$($(GOFMT) -d $(GOFILES)); \ + if [ -n "$$diff" ]; then \ + echo "Please run 'make fmt' and commit the result:"; \ + echo "$${diff}"; \ + exit 1; \ + fi; + +.PHONY: vet +# Examine packages and report suspicious constructs if any. +vet: + $(GO) vet $(VETPACKAGES) + +.PHONY: tools +# Install tools (migrate and sqlc). +tools: + @if [ $(GO_VERSION) -gt 16 ]; then \ + $(GO) install -tags 'postgres' github.com/golang-migrate/migrate/v4/cmd/migrate@latest; \ + $(GO) install github.com/sqlc-dev/sqlc/cmd/sqlc@latest; \ + fi + +.PHONY: env +# Copy .env.example to .env if .env does not already exist +env: + @if [ ! -f .env ]; then \ + cp .env.example .env; \ + echo ".env file created from .env.example"; \ + else \ + echo ".env file already exists"; \ + fi + + +.PHONY: first-run +# Runs for the first time +first-run: + make tools + make env + make recreate + make run + +.PHONY: help +# Help. +help: + @echo '' + @echo 'Usage:' + @echo ' make [target]' + @echo '' + @echo 'Targets:' + @awk '/^[a-zA-Z\-\0-9]+:/ { \ + helpMessage = match(lastLine, /^# (.*)/); \ + if (helpMessage) { \ + helpCommand = substr($$1, 0, index($$1, ":")-1); \ + helpMessage = substr(lastLine, RSTART + 2, RLENGTH); \ + printf " - \033[36m%-20s\033[0m %s\n", helpCommand, helpMessage; \ + } \ + } \ + { lastLine = $$0 }' $(MAKEFILE_LIST) + +.DEFAULT_GOAL := help \ No newline at end of file diff --git a/cmd/database/migrations/001_schema.up.sql b/cmd/database/migrations/001_schema.up.sql new file mode 100644 index 0000000..73afdb2 --- /dev/null +++ b/cmd/database/migrations/001_schema.up.sql @@ -0,0 +1,5 @@ +create table pet +( + id serial unique not null, + name varchar(20) unique not null +); \ No newline at end of file diff --git a/cmd/database/queries/queries.sql b/cmd/database/queries/queries.sql new file mode 100644 index 0000000..7766fda --- /dev/null +++ b/cmd/database/queries/queries.sql @@ -0,0 +1,4 @@ +-- name: CreatePet :one +insert into "pet" (name) +values ($1) +returning id; diff --git a/cmd/main.go b/cmd/main.go new file mode 100644 index 0000000..08a145d --- /dev/null +++ b/cmd/main.go @@ -0,0 +1,74 @@ +package main + +import ( + "database/sql" + "embed" + "errors" + "fmt" + "github.com/golang-migrate/migrate/v4" + _ "github.com/golang-migrate/migrate/v4/database/postgres" + "github.com/golang-migrate/migrate/v4/source/iofs" + _ "github.com/lib/pq" + "log/slog" + "os" + "ron" + "ron-pets/internal/config" + "ron-pets/internal/handlers" + "ron-pets/internal/repository" +) + +//go:embed database/migrations +var database embed.FS + +func migrateDB() { + dbConn, err := sql.Open("postgres", os.Getenv("DATASOURCE")) + if err != nil { + fmt.Println(err) + return + } + defer dbConn.Close() + + d, err := iofs.New(database, "database/migrations") + if err != nil { + fmt.Println(err) + return + } + + m, err := migrate.NewWithSourceInstance("iofs", d, os.Getenv("DATASOURCE")) + if err != nil { + fmt.Println(err) + return + } + + err = m.Up() + if err != nil && !errors.Is(err, migrate.ErrNoChange) { + slog.Error("cannot migrate", "error", err) + panic(err) + } + if errors.Is(err, migrate.ErrNoChange) { + slog.Info("migration has no changes") + } + + slog.Info("migration done") +} + +func main() { + app := config.New() + migrateDB() + r := ron.New(func(e *ron.Engine) { + e.Render = ron.NewHTMLRender() + e.LogLevel = slog.LevelDebug + }) + + dbPool := config.NewPostgresPool(app.DataSource) + defer dbPool.Close() + + q := repository.NewPGXRepo(dbPool) + h := handlers.New(app, q) + router(h, r) + + err := r.Run(":8080") + if err != nil { + slog.Error(err.Error()) + } +} diff --git a/cmd/middleware.go b/cmd/middleware.go new file mode 100644 index 0000000..b1fc180 --- /dev/null +++ b/cmd/middleware.go @@ -0,0 +1,38 @@ +package main + +import ( + "context" + "log/slog" + "net/http" + "ron-pets/internal/sqlc" +) + +func someMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + slog.Info("triggered middleware") + next.ServeHTTP(w, r) + }) +} + +func anotherMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + slog.Info("triggered another middleware") + next.ServeHTTP(w, r) + }) +} + +func UserSessionMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Simula la extracción de la sesión (en un caso real sería de cookies o tokens) + session := &sqlc.SessionData{ + UserID: "12345", + Role: "admin", + } + + // Almacena los datos de sesión en el contexto + ctx := context.WithValue(r.Context(), "session", session) + + // Pasa el contexto actualizado al siguiente handler + next.ServeHTTP(w, r.WithContext(ctx)) + }) +} diff --git a/cmd/router.go b/cmd/router.go new file mode 100644 index 0000000..9ce74d6 --- /dev/null +++ b/cmd/router.go @@ -0,0 +1,19 @@ +package main + +import ( + "ron" + "ron-pets/internal/handlers" +) + +func router(h *handlers.Handlers, r *ron.Engine) { + r.Static("static", "static") + + r.USE(UserSessionMiddleware) + + r.GET("/put", h.HelloWorld) + //r.GET("/get", h.AnotherHelloWorld) + // + //r.GET("/create", h.CreateToken) + //r.GET("/validate", h.ValidateTokenAuthorization) + //r.GET("/cookie", h.ValidateTokenCookie) +} diff --git a/go.mod b/go.mod index 9457f8f..e5339f9 100644 --- a/go.mod +++ b/go.mod @@ -4,11 +4,24 @@ go 1.23.2 replace ron => ./../ron-gola -require ron v0.0.0-00010101000000-000000000000 +require ( + aidanwoods.dev/go-paseto v1.5.2 + github.com/golang-migrate/migrate/v4 v4.18.1 + github.com/jackc/pgx/v5 v5.7.1 + github.com/lib/pq v1.10.9 + ron v0.0.0-00010101000000-000000000000 +) require ( - aidanwoods.dev/go-paseto v1.5.2 // indirect aidanwoods.dev/go-result v0.1.0 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/jackc/puddle/v2 v2.2.2 // indirect + go.uber.org/atomic v1.7.0 // indirect golang.org/x/crypto v0.29.0 // indirect + golang.org/x/sync v0.9.0 // indirect golang.org/x/sys v0.27.0 // indirect + golang.org/x/text v0.20.0 // indirect ) diff --git a/go.sum b/go.sum index fa9fb54..495532b 100644 --- a/go.sum +++ b/go.sum @@ -2,7 +2,86 @@ aidanwoods.dev/go-paseto v1.5.2 h1:9aKbCQQUeHCqis9Y6WPpJpM9MhEOEI5XBmfTkFMSF/o= aidanwoods.dev/go-paseto v1.5.2/go.mod h1:7eEJZ98h2wFi5mavCcbKfv9h86oQwut4fLVeL/UBFnw= aidanwoods.dev/go-result v0.1.0 h1:y/BMIRX6q3HwaorX1Wzrjo3WUdiYeyWbvGe18hKS3K8= aidanwoods.dev/go-result v0.1.0/go.mod h1:yridkWghM7AXSFA6wzx0IbsurIm1Lhuro3rYef8FBHM= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +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/dhui/dktest v0.4.3 h1:wquqUxAFdcUgabAVLvSCOKOlag5cIZuaOjYIBOWdsR0= +github.com/dhui/dktest v0.4.3/go.mod h1:zNK8IwktWzQRm6I/l2Wjp7MakiyaFWv4G1hjmodmMTs= +github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= +github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= +github.com/docker/docker v27.2.0+incompatible h1:Rk9nIVdfH3+Vz4cyI/uhbINhEZ/oLmc+CBXmH6fbNk4= +github.com/docker/docker v27.2.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= +github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-migrate/migrate/v4 v4.18.1 h1:JML/k+t4tpHCpQTCAD62Nu43NUFzHY4CV3uAuvHGC+Y= +github.com/golang-migrate/migrate/v4 v4.18.1/go.mod h1:HAX6m3sQgcdO81tdjn5exv20+3Kb13cmGli1hrD6hks= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.7.1 h1:x7SYsPBYDkHDksogeSmZZ5xzThcTgRz++I5E+ePFUcs= +github.com/jackc/pgx/v5 v5.7.1/go.mod h1:e7O26IywZZ+naJtWWos6i6fvWK+29etgITqrqHLfoZA= +github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= +github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= +github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= +github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= +github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= +github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= +github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +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.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8= +go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= +go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= +go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc= +go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= +go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= +go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= +go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= +golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= +golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= +golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= +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= diff --git a/internal/config/config.go b/internal/config/config.go index 74d065a..ca340e0 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -2,11 +2,52 @@ package config import ( "aidanwoods.dev/go-paseto" + "bufio" + "log/slog" + "os" + "strings" "time" ) +func New() *App { + var err error + + err = loadEnvFile() + if err != nil { + slog.Error("error loading env file", "error", err) + panic(err) + } + + var durationTime time.Duration + var ak paseto.V4AsymmetricSecretKey + + ak, err = paseto.NewV4AsymmetricSecretKeyFromHex(os.Getenv("ASYMMETRICKEY")) + if err != nil { + ak = paseto.NewV4AsymmetricSecretKey() + } + pk := ak.Public() + + duration := os.Getenv("DURATION") + if duration != "" { + durationTime, err = time.ParseDuration(duration) + if err != nil { + durationTime = time.Hour * 24 * 7 + } + } + + return &App{ + DataSource: os.Getenv("DATASOURCE"), + Security: Security{ + AsymmetricKey: ak, + PublicKey: pk, + Duration: durationTime, + }, + } +} + type App struct { - Security Security + DataSource string + Security Security } type Security struct { @@ -14,3 +55,27 @@ type Security struct { PublicKey paseto.V4AsymmetricPublicKey Duration time.Duration } + +func loadEnvFile() error { + file, err := os.Open(".env") + if err != nil { + return err + } + defer file.Close() + + scanner := bufio.NewScanner(file) + for scanner.Scan() { + line := scanner.Text() + if len(line) == 0 || strings.HasPrefix(line, "#") { + continue + } + parts := strings.SplitN(line, "=", 2) + if len(parts) != 2 { + continue + } + key := strings.TrimSpace(parts[0]) + value := strings.TrimSpace(parts[1]) + os.Setenv(key, value) + } + return scanner.Err() +} diff --git a/internal/config/db.go b/internal/config/db.go new file mode 100644 index 0000000..699835f --- /dev/null +++ b/internal/config/db.go @@ -0,0 +1,23 @@ +package config + +import ( + "context" + "github.com/jackc/pgx/v5/pgxpool" + "log/slog" +) + +func NewPostgresPool(dataSource string) *pgxpool.Pool { + dbPool, err := pgxpool.New(context.Background(), dataSource) + if err != nil { + slog.Error("error connecting to database", "error", err) + panic(err) + } + + if err := dbPool.Ping(context.Background()); err != nil { + slog.Error("error pinging database, maybe incorrect datasource", "error", err) + panic(err) + } + + slog.Info("connected to database") + return dbPool +} diff --git a/internal/handlers/handlers.go b/internal/handlers/handlers.go index 4e691ba..ed21717 100644 --- a/internal/handlers/handlers.go +++ b/internal/handlers/handlers.go @@ -1,38 +1,58 @@ package handlers import ( + "context" + "fmt" "log/slog" + "net/http" "ron" "ron-pets/internal/config" + "ron-pets/internal/repository" + "ron-pets/internal/sqlc" ) type Handlers struct { - app *config.App + app *config.App + queries repository.ExtendedQuerier } -func New(app *config.App) *Handlers { +func New(app *config.App, q repository.ExtendedQuerier) *Handlers { return &Handlers{ - app: app, + app: app, + queries: q, } } -func (hq *Handlers) HelloWorld(c *ron.Context) { - slog.Info("Dummy info message") +func (hq *Handlers) HelloWorld(c *ron.CTX, ctx context.Context) { + + session, ok := ctx.Value("session").(*sqlc.SessionData) + if !ok || session == nil { + http.Error(c.W, "Unauthorized", http.StatusUnauthorized) + return + } + + fmt.Fprintf(c.W, "User ID: %s, Role: %s", session.UserID, session.Role) + c.W.Write([]byte("hello world")) } -func (hq *Handlers) AnotherHelloWorld(c *ron.Context) { +func (hq *Handlers) AnotherHelloWorld(c *ron.CTX) { + + val := c.R.Context().Value("key") + //val := context.Background().Value("key") + slog.Info("context value", "value", val) + c.W.Write([]byte("another hello world")) } -func (hq *Handlers) HelloWorldJSON(c *ron.Context) { +func (hq *Handlers) HelloWorldJSON(c *ron.CTX) { id := c.R.PathValue("id") slog.Info("path value", "id", id) c.JSON(200, ron.Data{"message": "hello world"}) } -func (hq *Handlers) HelloWorldHTML(c *ron.Context) { +func (hq *Handlers) HelloWorldHTML(c *ron.CTX) { //pages := ron.Pages{ // TotalElements: len(elements), @@ -50,6 +70,6 @@ func (hq *Handlers) HelloWorldHTML(c *ron.Context) { //c.HTML(200, "page.index.gohtml", td) } -func (hq *Handlers) ComponentHTML(c *ron.Context) { +func (hq *Handlers) ComponentHTML(c *ron.CTX) { c.HTML(200, "component.list.gohtml", nil) } diff --git a/internal/handlers/token.go b/internal/handlers/token.go index 860dd37..8a031e1 100644 --- a/internal/handlers/token.go +++ b/internal/handlers/token.go @@ -14,7 +14,7 @@ type UserPayload struct { Role string `json:"role"` } -func (hq *Handlers) CreateToken(c *ron.Context) { +func (hq *Handlers) CreateToken(c *ron.CTX) { token := paseto.NewToken() token.Set("userPayload", UserPayload{User: "pedro", Role: "admin"}) token.SetExpiration(time.Now().Add(hq.app.Security.Duration)) @@ -35,7 +35,7 @@ func (hq *Handlers) CreateToken(c *ron.Context) { c.JSON(http.StatusOK, ron.Data{"token": signed}) } -func (hq *Handlers) ValidateTokenAuthorization(c *ron.Context) { +func (hq *Handlers) ValidateTokenAuthorization(c *ron.CTX) { signed := c.R.Header.Get("Authorization") split := strings.Split(signed, "Bearer ") slog.Info("signed", "signed", split[1]) @@ -56,7 +56,7 @@ func (hq *Handlers) ValidateTokenAuthorization(c *ron.Context) { }) } -func (hq *Handlers) ValidateTokenCookie(c *ron.Context) { +func (hq *Handlers) ValidateTokenCookie(c *ron.CTX) { cookie, err := c.R.Cookie("token") if err != nil { slog.Error("error", "err", err) diff --git a/internal/repository/pgxrepo.go b/internal/repository/pgxrepo.go new file mode 100644 index 0000000..d65d457 --- /dev/null +++ b/internal/repository/pgxrepo.go @@ -0,0 +1,18 @@ +package repository + +import ( + "github.com/jackc/pgx/v5/pgxpool" + "ron-pets/internal/sqlc" +) + +type pgxRepository struct { + *sqlc.Queries + db *pgxpool.Pool +} + +func NewPGXRepo(db *pgxpool.Pool) ExtendedQuerier { + return &pgxRepository{ + Queries: sqlc.New(db), + db: db, + } +} diff --git a/internal/repository/querier.go b/internal/repository/querier.go new file mode 100644 index 0000000..d9731d1 --- /dev/null +++ b/internal/repository/querier.go @@ -0,0 +1,7 @@ +package repository + +import "ron-pets/internal/sqlc" + +type ExtendedQuerier interface { + sqlc.Querier +} diff --git a/internal/sqlc/db.go b/internal/sqlc/db.go new file mode 100644 index 0000000..b931bc5 --- /dev/null +++ b/internal/sqlc/db.go @@ -0,0 +1,32 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.27.0 + +package sqlc + +import ( + "context" + + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgconn" +) + +type DBTX interface { + Exec(context.Context, string, ...interface{}) (pgconn.CommandTag, error) + Query(context.Context, string, ...interface{}) (pgx.Rows, error) + QueryRow(context.Context, string, ...interface{}) pgx.Row +} + +func New(db DBTX) *Queries { + return &Queries{db: db} +} + +type Queries struct { + db DBTX +} + +func (q *Queries) WithTx(tx pgx.Tx) *Queries { + return &Queries{ + db: tx, + } +} diff --git a/internal/sqlc/models.go b/internal/sqlc/models.go new file mode 100644 index 0000000..4edb7ec --- /dev/null +++ b/internal/sqlc/models.go @@ -0,0 +1,10 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.27.0 + +package sqlc + +type Pet struct { + ID int32 + Name string +} diff --git a/internal/sqlc/payloads.go b/internal/sqlc/payloads.go new file mode 100644 index 0000000..d02dc47 --- /dev/null +++ b/internal/sqlc/payloads.go @@ -0,0 +1,6 @@ +package sqlc + +type SessionData struct { + UserID string + Role string +} diff --git a/internal/sqlc/querier.go b/internal/sqlc/querier.go new file mode 100644 index 0000000..d39180c --- /dev/null +++ b/internal/sqlc/querier.go @@ -0,0 +1,15 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.27.0 + +package sqlc + +import ( + "context" +) + +type Querier interface { + CreatePet(ctx context.Context, name string) (int32, error) +} + +var _ Querier = (*Queries)(nil) diff --git a/internal/sqlc/queries.sql.go b/internal/sqlc/queries.sql.go new file mode 100644 index 0000000..daf581e --- /dev/null +++ b/internal/sqlc/queries.sql.go @@ -0,0 +1,23 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.27.0 +// source: queries.sql + +package sqlc + +import ( + "context" +) + +const createPet = `-- name: CreatePet :one +insert into "pet" (name) +values ($1) +returning id +` + +func (q *Queries) CreatePet(ctx context.Context, name string) (int32, error) { + row := q.db.QueryRow(ctx, createPet, name) + var id int32 + err := row.Scan(&id) + return id, err +} diff --git a/main.go b/main.go deleted file mode 100644 index 9343665..0000000 --- a/main.go +++ /dev/null @@ -1,94 +0,0 @@ -package main - -import ( - "aidanwoods.dev/go-paseto" - "log/slog" - "ron" - "ron-pets/internal/config" - "ron-pets/internal/handlers" - "time" -) - -type SomethingElements struct { - Name string - Description string -} - -var elements = []SomethingElements{ - {"element 1", "description 1"}, - {"element 2", "description 2"}, - {"element 3", "description 3"}, - {"element 4", "description 4"}, - {"element 5", "description 5"}, - {"element 6", "description 6"}, - {"element 7", "description 7"}, - {"element 8", "description 8"}, - {"element 9", "description 9"}, - {"element 10", "description 10"}, - {"element 11", "description 11"}, - {"element 12", "description 12"}, - {"element 13", "description 13"}, - {"element 14", "description 14"}, - {"element 15", "description 15"}, - {"element 16", "description 16"}, - {"element 17", "description 17"}, - {"element 18", "description 18"}, - {"element 19", "description 19"}, - {"element 20", "description 20"}, - {"element 21", "description 21"}, - {"element 22", "description 22"}, - {"element 23", "description 23"}, - {"element 24", "description 24"}, - {"element 25", "description 25"}, - {"element 26", "description 26"}, - {"element 27", "description 27"}, - {"element 28", "description 28"}, - {"element 29", "description 29"}, - {"element 30", "description 30"}, - {"element 31", "description 31"}, - {"element 32", "description 32"}, - {"element 33", "description 33"}, - {"element 34", "description 34"}, - {"element 35", "description 35"}, - {"element 36", "description 36"}, - {"element 37", "description 37"}, - {"element 38", "description 38"}, - {"element 39", "description 39"}, - {"element 40", "description 40"}, - {"element 41", "description 41"}, - {"element 42", "description 42"}, - {"element 43", "description 43"}, - {"element 44", "description 44"}, - {"element 45", "description 45"}, - {"element 46", "description 46"}, - {"element 47", "description 47"}, - {"element 48", "description 48"}, - {"element 49", "description 49"}, -} - -var app *config.App - -func main() { - r := ron.New(func(e *ron.Engine) { - e.LogLevel = slog.LevelDebug - }) - - asymmetricKey, _ := paseto.NewV4AsymmetricSecretKeyFromHex("c3e9d207e752bd506a89e4ab09210f4b0100ddd31d3c815c0ab671f6885e9eae4def9afa5e982684329746a0718ea0a534fd9ce64813efee08c89ad6700a045d") - app = &config.App{ - Security: config.Security{ - AsymmetricKey: asymmetricKey, - PublicKey: asymmetricKey.Public(), - Duration: time.Second * 20, - }, - } - - slog.Info("asymmetric key", "key", asymmetricKey.ExportHex()) - - h := handlers.New(app) - router(h, r) - - err := r.Run(":8080") - if err != nil { - slog.Error(err.Error()) - } -} diff --git a/middleware.go b/middleware.go deleted file mode 100644 index 904c3a1..0000000 --- a/middleware.go +++ /dev/null @@ -1,20 +0,0 @@ -package main - -import ( - "log/slog" - "net/http" -) - -func someMiddleware(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - slog.Info("triggered middleware") - next.ServeHTTP(w, r) - }) -} - -func anotherMiddleware(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - slog.Info("triggered another middleware") - next.ServeHTTP(w, r) - }) -} diff --git a/router.go b/router.go deleted file mode 100644 index dc86d70..0000000 --- a/router.go +++ /dev/null @@ -1,17 +0,0 @@ -package main - -import ( - "ron" - "ron-pets/internal/handlers" -) - -func router(h *handlers.Handlers, r *ron.Engine) { - htmlRender := ron.NewHTMLRender() - r.Render = htmlRender - - r.Static("static", "static") - - r.GET("/create", h.CreateToken) - r.GET("/validate", h.ValidateTokenAuthorization) - r.GET("/cookie", h.ValidateTokenCookie) -} diff --git a/sqlc.yaml b/sqlc.yaml new file mode 100644 index 0000000..914281f --- /dev/null +++ b/sqlc.yaml @@ -0,0 +1,13 @@ +version: "2" +sql: + - engine: "postgresql" + schema: "./cmd/database/migrations/*" + queries: "./cmd/database/queries/*" + gen: + go: + package: "sqlc" + out: "./internal/sqlc" + sql_package: "pgx/v5" + emit_interface: true + emit_empty_slices: true + emit_json_tags: false