From b8ed290f61453dccbec782e4e938932aa23e9939 Mon Sep 17 00:00:00 2001 From: muhfaris Date: Thu, 25 Jun 2026 12:54:01 +0700 Subject: [PATCH] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20example=20project=20with?= =?UTF-8?q?=20pgsql?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- builder/hexagonal/hexagonal.go | 15 +- examples/books-api/Makefile | 6 + examples/books-api/README.md | 155 +++++++-------- .../books-api/cmd/bootstrap/app_repository.go | 35 +++- examples/books-api/config/config.go | 27 +++ examples/books-api/config/config.yaml | 12 ++ examples/books-api/docker-compose.yml | 27 +++ examples/books-api/go.mod | 15 +- examples/books-api/go.sum | 188 +++++++++++++++++- .../adapter/inbound/rest/router/group/v1.go | 14 +- .../rest/router/v1/presenter/borrow_book.go | 2 +- .../rest/router/v1/presenter/create_book.go | 4 +- .../rest/router/v1/presenter/get_book.go | 4 +- .../adapter/outbound/cache/redis/command.go | 28 +++ .../adapter/outbound/cache/redis/redis.go | 46 +++++ .../outbound/datastore/psql/command.go | 43 ++++ .../adapter/outbound/datastore/psql/psql.go | 53 +++++ .../datastore/psql/repository/book.go | 32 +++ .../books-api/internal/core/domain/books.go | 26 +-- .../core/port/inbound/registry/registry.go | 1 + .../core/port/inbound/service/booksvc.go | 6 +- .../core/port/outbound/repository/cache.go | 13 ++ .../core/port/outbound/repository/psql.go | 9 + .../internal/core/service/booksvc.go | 18 +- examples/books-api/rocket.yaml | 6 +- rocket-books-api.yaml | 4 +- shared/templates/hexagonal/README.tmpl | 170 ++++++++-------- 27 files changed, 746 insertions(+), 213 deletions(-) create mode 100644 examples/books-api/docker-compose.yml create mode 100644 examples/books-api/internal/adapter/outbound/cache/redis/command.go create mode 100644 examples/books-api/internal/adapter/outbound/cache/redis/redis.go create mode 100644 examples/books-api/internal/adapter/outbound/datastore/psql/command.go create mode 100644 examples/books-api/internal/adapter/outbound/datastore/psql/psql.go create mode 100644 examples/books-api/internal/adapter/outbound/datastore/psql/repository/book.go create mode 100644 examples/books-api/internal/core/port/outbound/repository/cache.go create mode 100644 examples/books-api/internal/core/port/outbound/repository/psql.go diff --git a/builder/hexagonal/hexagonal.go b/builder/hexagonal/hexagonal.go index c7509a3..ae84756 100644 --- a/builder/hexagonal/hexagonal.go +++ b/builder/hexagonal/hexagonal.go @@ -1765,10 +1765,17 @@ func (p *Project) GenerateMakefile() error { // Readme func (p *Project) GenerateReadmeFile() error { - data := map[string]string{ - "ProjectName": p.based.Project.ProjectName, - "PackagePath": p.based.Project.PackagePath, - "AppName": p.based.Project.AppName, + data := map[string]any{ + "ProjectName": p.based.Project.ProjectName, + "PackagePath": p.based.Project.PackagePath, + "AppName": p.based.Project.AppName, + "HasEndpoints": len(p.RoutesGroup) > 0, + "RouteGroups": p.RoutesGroup, + "Services": p.Service.Services, + "HasCache": p.cacheType != "", + "CacheType": p.cacheType, + "HasDatabase": p.dbType != "", + "DatabaseType": p.dbType, } raw, err := libos.ExecuteTemplate(p.ReadmeFile.template, data) diff --git a/examples/books-api/Makefile b/examples/books-api/Makefile index 810c0b6..3b2a4a7 100644 --- a/examples/books-api/Makefile +++ b/examples/books-api/Makefile @@ -28,6 +28,12 @@ start: services init check-db run # Target: Check if the database is up and running check-db: + @echo "Checking if PostgreSQL is up..." + @until docker exec -it postgres_dev psql -U myapp -d myapp -c "\q" > /dev/null 2>&1; do \ + echo "Waiting for PostgreSQL..."; \ + sleep 2; \ + done + @echo "PostgreSQL is up and ready." # Declare these targets as phony (not associated with real files) .PHONY: services init run build start check-db diff --git a/examples/books-api/README.md b/examples/books-api/README.md index d81b70d..4c08de6 100644 --- a/examples/books-api/README.md +++ b/examples/books-api/README.md @@ -1,93 +1,99 @@ # books-api -A brief description of the project, what it does, and its purpose. +API generated by [Rocket](https://github.com/muhfaris/rocket) from an OpenAPI 3.0 specification. --- -## Table of Contents - -1. [Getting Started](#getting-started) -2. [Prerequisites](#prerequisites) -3. [Installation](#installation) -4. [Usage](#usage) -5. [Configuration](#configuration) -6. [Testing](#testing) -7. [Contributing](#contributing) -8. [License](#license) - ---- - -## Getting Started - -These instructions will help you get a copy of the project up and running on your local machine for development and testing purposes. - ---- - -## Prerequisites - -- Go (minimum version 1.22) installed on your system. -- [Optional] Docker for containerized deployment. - ---- - -## Installation - -### Clone the Repository +## Quick Start ```bash -git clone github.com/muhfaris/rocket/examples/books-api -cd repository +cd books-api +make start ``` -### Initialize the Project +The server starts on `http://localhost:7000` (configurable in `config/config.yaml`). -Run the following command to prepare the project: +--- -```bash -make init -``` +## API Endpoints -This command will: -- Run `go mod tidy` to clean up dependencies. -- Run `go mod vendor` to vendor dependencies. +### bookGroup (prefix `/api/v1`) -### Quick Start +| Method | Path | Handler | +|--------|------|---------| +| `Get` | `/books/:bookId` | `GetBook` | +| `Get` | `/books` | `ListBooks` | +| `Post` | `/books` | `CreateBook` | +### borrowGroup (prefix `/api/v1`) -Run the following command to start the services, gomod application and run the application: +| Method | Path | Handler | +|--------|------|---------| +| `Post` | `/books/:bookId/borrow` | `BorrowBook` | +| `Patch` | `/books/:bookId/return` | `ReturnBook` | +### routeGroup (prefix `/`) + +| Method | Path | Handler | +|--------|------|---------| +| `Get` | `/health` | `HealthCheck` | -```bash -make start -``` --- -## Usage +## Architecture -### Running the Application +This project follows **hexagonal architecture** (ports & adapters): -```bash -go run main.go rest +``` +HTTP Request + │ + ▼ +┌─ INBOUND ADAPTER ────────────────────────┐ +│ Router → Handler → Presenter │ auto-generated +│ (internal/adapter/inbound/rest/) │ +└──────────────────┬───────────────────────┘ + │ calls interface + ▼ +┌─ CORE ───────────────────────────────────┐ +│ Service Interface (port) │ contract +│ Service Impl (domain) ← YOUR CODE│ business logic +│ Repository Interface (port) │ contract +└──────────────────┬───────────────────────┘ + │ implements + ▼ +┌─ OUTBOUND ADAPTER ───────────────────────┐ +│ DB Adapter → PostgreSQL / MySQL /… │ auto-generated +│ Cache Adapter → Redis / in-memory │ +└──────────────────────────────────────────┘ ``` -or +### Key directories -```bash -make run -``` +| Directory | Purpose | +|-----------|---------| +| `internal/adapter/inbound/rest/router/v1/handler/` | HTTP handlers — parse requests, call services (auto-generated) | +| `internal/adapter/inbound/rest/router/v1/presenter/` | Map domain models → API responses (TODO stubs — update these) | +| `internal/core/port/inbound/service/` | Service interfaces | +| `internal/core/service/` | **Business logic goes here** — each method is a stub returning an error | +| `internal/core/domain/` | Domain models | +| `internal/core/port/outbound/` | Repository interfaces | +| `internal/adapter/outbound/` | Database/cache implementations +| `internal/adapter/outbound/datastore/postgresql/repository/` | Query implementations (TODO stubs) +| `internal/adapter/outbound/cache/redis/` | Cache adapter -### Build the Application +### Development workflow -```bash -make build -``` +1. **Add business logic** → edit `internal/core/service/*.go` +2. **Add queries** → edit `internal/adapter/outbound/datastore/*/repository/*.go` +3. **Map responses** → edit `internal/adapter/inbound/rest/router/v1/presenter/*.go` +4. **Regenerate from spec** → run `rocket new` again (commit changes first — regeneration overwrites files) --- ## Configuration -Configuration is handled through `config/config.yaml`: +Edit `config/config.yaml`: -```env +```yaml app: name: books-api port: 7000 @@ -113,28 +119,19 @@ app: --- -### Shortcuts Commands - -The project comes with a set of shortcuts commands to make development easier: - -- `make start` - Run the services, gomod application and run the application. -- `make services` - Start necessary services and remove old volume. -- `make run` - Run the application. -- `make build` - Build the application for deployment and store it in the `$(GOPATH)/bin` directory. -- `make init` - Run `go mod tidy` and `go mod vendor` commands. - -## Contributing - -Contributions are welcome! Please follow these steps: +## Makefile -1. Fork the repository. -2. Create a new branch (`git checkout -b feature-name`). -3. Commit your changes (`git commit -m 'Add new feature'`). -4. Push to the branch (`git push origin feature-name`). -5. Open a pull request. +| Command | Description | +|---------|-------------| +| `make start` | Start Docker services + run the application | +| `make services` | Start Docker services only (clean volumes) | +| `make run` | Run the application | +| `make build` | Build binary to `$(GOPATH)/bin` | +| `make init` | Run `go mod tidy` and `go mod vendor` | +| `make test` | Run tests | --- -## License +## Spec -This project is licensed under the [MIT License](LICENSE). +The OpenAPI spec used to generate this project is at `spec/openapi.yaml`. Edit it and regenerate to update the API. diff --git a/examples/books-api/cmd/bootstrap/app_repository.go b/examples/books-api/cmd/bootstrap/app_repository.go index 29fbf0c..d4eeafc 100644 --- a/examples/books-api/cmd/bootstrap/app_repository.go +++ b/examples/books-api/cmd/bootstrap/app_repository.go @@ -1,18 +1,49 @@ package bootstrap import ( + "github.com/muhfaris/rocket/examples/books-api/config" + redisadapter "github.com/muhfaris/rocket/examples/books-api/internal/adapter/outbound/cache/redis" + psqladapter "github.com/muhfaris/rocket/examples/books-api/internal/adapter/outbound/datastore/psql" + pgsqlrepository "github.com/muhfaris/rocket/examples/books-api/internal/adapter/outbound/datastore/psql/repository" portregistry "github.com/muhfaris/rocket/examples/books-api/internal/core/port/inbound/registry" "github.com/muhfaris/rocket/examples/books-api/internal/core/port/outbound/repository" ) func InitializeRepository() portregistry.Repository { - return &AppRepository{} + return &AppRepository{ + + cacheRepository: redisadapter.New( + redisadapter.RedisOptions{ + Addr: config.App.Cache.Redis.Addr, + Username: config.App.Cache.Redis.Username, + Password: config.App.Cache.Redis.Password, + DB: config.App.Cache.Redis.DB, + }, + ), + + psqlRepository: psqladapter.New(psqladapter.PSQLConfig{ + Host: config.App.Datastore.PSQL.Host, + Port: config.App.Datastore.PSQL.Port, + Username: config.App.Datastore.PSQL.Username, + Password: config.App.Datastore.PSQL.Password, + DB: config.App.Datastore.PSQL.DB, + }), + } } // AppRepository struct would need to be updated to include new repository types type AppRepository struct { + cacheRepository repository.CacheRepository + psqlRepository repository.PSQLRepository } +func (a *AppRepository) CacheRepository() repository.CacheRepository { + return a.cacheRepository +} + +func (a *AppRepository) PSQLRepository() repository.PSQLRepository { + return a.psqlRepository +} func (a *AppRepository) GetBookRepository() repository.BookRepository { - return nil + return pgsqlrepository.NewBookRepository(a.psqlRepository) } diff --git a/examples/books-api/config/config.go b/examples/books-api/config/config.go index 8534f44..baa2801 100644 --- a/examples/books-api/config/config.go +++ b/examples/books-api/config/config.go @@ -18,6 +18,10 @@ type Config struct { Port int `mapstructure:"port"` Fiber FiberConfig `mapstructure:"fiber"` Debug Debugging `mapstructure:"debug"` + + Cache Cache `mapstructure:"cache"` + + Datastore Datastore `mapstructure:"datastore"` } type Debugging struct { @@ -29,6 +33,29 @@ type FiberConfig struct { EnableSplittingOnParsers bool `mapstructure:"enable_splitting_on_parsers"` } +type Cache struct { + Redis RedisConfig `mapstructure:"redis"` +} + +type RedisConfig struct { + Addr string `mapstructure:"addr"` + Username string `mapstructure:"username"` + Password string `mapstructure:"password"` + DB int `mapstructure:"db"` +} + +type Datastore struct { + PSQL PSQLConfig `mapstructure:"psql"` +} + +type PSQLConfig struct { + Host string `mapstructure:"host"` + Port int `mapstructure:"port"` + Username string `mapstructure:"username"` + Password string `mapstructure:"password"` + DB string `mapstructure:"db"` +} + func LoadConfig() error { viper.SetConfigName("config") viper.AddConfigPath(".") diff --git a/examples/books-api/config/config.yaml b/examples/books-api/config/config.yaml index b559adc..11a0b36 100644 --- a/examples/books-api/config/config.yaml +++ b/examples/books-api/config/config.yaml @@ -6,4 +6,16 @@ app: fiber: enable_print_routes: true enable_splitting_on_parsers: true + cache: + redis: + addr: localhost:6379 + username: + password: + db: 0 datastore: + psql: + host: localhost + port: 5432 + username: myapp + password: myapp + db: myapp diff --git a/examples/books-api/docker-compose.yml b/examples/books-api/docker-compose.yml new file mode 100644 index 0000000..157b8c4 --- /dev/null +++ b/examples/books-api/docker-compose.yml @@ -0,0 +1,27 @@ +version: "3.9" +services: + redis: + image: redis:7-alpine3.20 + container_name: redis_dev + ports: + - "6379:6379" + command: ["redis-server"] + networks: + - dev-network + postgres: + image: postgres:15-alpine + container_name: postgres_dev + environment: + POSTGRES_USER: myapp + POSTGRES_PASSWORD: myapp + POSTGRES_DB: myapp + ports: + - "5432:5432" + networks: + - dev-network + volumes: + - postgres_data:/var/lib/postgresql/data +volumes: + postgres_data: +networks: + dev-network: diff --git a/examples/books-api/go.mod b/examples/books-api/go.mod index e014cda..e4f031d 100644 --- a/examples/books-api/go.mod +++ b/examples/books-api/go.mod @@ -5,15 +5,26 @@ go 1.25.0 require ( github.com/fsnotify/fsnotify v1.10.1 github.com/gofiber/fiber/v2 v2.52.13 + github.com/jackc/pgx/v4 v4.18.3 + github.com/redis/go-redis/v9 v9.21.0 github.com/spf13/cobra v1.10.2 github.com/spf13/viper v1.21.0 ) require ( github.com/andybalholm/brotli v1.1.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/go-viper/mapstructure/v2 v2.4.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/jackc/chunkreader/v2 v2.0.1 // indirect + github.com/jackc/pgconn v1.14.3 // indirect + github.com/jackc/pgio v1.0.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgproto3/v2 v2.3.3 // indirect + github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect + github.com/jackc/pgtype v1.14.0 // indirect + github.com/jackc/puddle v1.3.0 // indirect github.com/klauspost/compress v1.17.9 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect @@ -29,7 +40,9 @@ require ( github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasthttp v1.51.0 // indirect github.com/valyala/tcplisten v1.0.0 // indirect + go.uber.org/atomic v1.11.0 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/sys v0.29.0 // indirect + golang.org/x/crypto v0.20.0 // indirect + golang.org/x/sys v0.30.0 // indirect golang.org/x/text v0.28.0 // indirect ) diff --git a/examples/books-api/go.sum b/examples/books-api/go.sum index 8745bf1..44ffdc1 100644 --- a/examples/books-api/go.sum +++ b/examples/books-api/go.sum @@ -1,30 +1,118 @@ +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= +github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= +github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= +github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= +github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= +github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +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/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho= github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/gofiber/fiber/v2 v2.52.13 h1:TOKP64iqC9b5P49VrBW5tHhUOvDyrtJ0xePEfzJbCbk= github.com/gofiber/fiber/v2 v2.52.13/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw= +github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= +github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= +github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= +github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= +github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= +github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= +github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= +github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= +github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= +github.com/jackc/pgconn v1.14.3 h1:bVoTr12EGANZz66nZPkMInAV/KHD2TxH9npjXXgiB3w= +github.com/jackc/pgconn v1.14.3/go.mod h1:RZbme4uasqzybK2RK5c65VsHxoyaml09lx3tXOcO/VM= +github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= +github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= +github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= +github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c= +github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc= +github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= +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/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= +github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.3.3 h1:1HLSx5H+tXR9pW3in3zaztoEwQYRC9SQaYUHjTSUOag= +github.com/jackc/pgproto3/v2 v2.3.3/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= +github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= +github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= +github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= +github.com/jackc/pgtype v1.14.0 h1:y+xUdabmyMkJLyApYuPj38mW+aAIqCe5uuBB51rH3Vw= +github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= +github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= +github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= +github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= +github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= +github.com/jackc/pgx/v4 v4.18.3 h1:dE2/TrEsGX3RBprb3qryqSV9Y60iZN1C6i8IrmW9/BA= +github.com/jackc/pgx/v4 v4.18.3/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw= +github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.3.0 h1:eHK/5clGOatcjX3oWGBO/MpxpbHzSwud5EWTSCI+MX0= +github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= +github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8= +github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= @@ -32,15 +120,29 @@ github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6T github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/pkg/errors v0.8.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/redis/go-redis/v9 v9.21.0 h1:FPBE4hhbAke+TLmcY3WkpbDffJEomdqPn3HYiqAtL9E= +github.com/redis/go-redis/v9 v9.21.0/go.mod h1:v/M13XI1PVCDcm01VtPFOADfZtHf8YW3baQf57KlIkA= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= +github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= +github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc= github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= +github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= +github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw= github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U= github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= @@ -54,6 +156,14 @@ github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU= github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= @@ -64,16 +174,90 @@ github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1S github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g= github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= +github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs= +github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s= +github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.20.0 h1:jmAMJJZXr5KiCw05dfYK9QnqaqKLYXijU23lsEdcQqg= +golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= -golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +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= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= diff --git a/examples/books-api/internal/adapter/inbound/rest/router/group/v1.go b/examples/books-api/internal/adapter/inbound/rest/router/group/v1.go index 647d21e..de2fb61 100644 --- a/examples/books-api/internal/adapter/inbound/rest/router/group/v1.go +++ b/examples/books-api/internal/adapter/inbound/rest/router/group/v1.go @@ -10,23 +10,18 @@ func V1(r *fiber.App, h *handlerv1.Handler) { // publicGroup := r.Group("/") // publicGroup.Get("/health", handlerv1.Health()) - routeGroup(r, h) - bookGroup(r, h) borrowGroup(r, h) -} -func routeGroup(r *fiber.App, h *handlerv1.Handler) { - routeGroup := r.Group("/") - routeGroup.Get("/health", h.HealthCheck()).Name("HealthCheck") + routeGroup(r, h) } func bookGroup(r *fiber.App, h *handlerv1.Handler) { bookGroup := r.Group("/api/v1") + bookGroup.Get("/books/:bookId", h.GetBook()).Name("GetBook") bookGroup.Get("/books", h.ListBooks()).Name("ListBooks") bookGroup.Post("/books", h.CreateBook()).Name("CreateBook") - bookGroup.Get("/books/:bookId", h.GetBook()).Name("GetBook") } func borrowGroup(r *fiber.App, h *handlerv1.Handler) { @@ -34,3 +29,8 @@ func borrowGroup(r *fiber.App, h *handlerv1.Handler) { borrowGroup.Post("/books/:bookId/borrow", h.BorrowBook()).Name("BorrowBook") borrowGroup.Patch("/books/:bookId/return", h.ReturnBook()).Name("ReturnBook") } + +func routeGroup(r *fiber.App, h *handlerv1.Handler) { + routeGroup := r.Group("/") + routeGroup.Get("/health", h.HealthCheck()).Name("HealthCheck") +} diff --git a/examples/books-api/internal/adapter/inbound/rest/router/v1/presenter/borrow_book.go b/examples/books-api/internal/adapter/inbound/rest/router/v1/presenter/borrow_book.go index 596f3eb..188657d 100644 --- a/examples/books-api/internal/adapter/inbound/rest/router/v1/presenter/borrow_book.go +++ b/examples/books-api/internal/adapter/inbound/rest/router/v1/presenter/borrow_book.go @@ -16,8 +16,8 @@ type BorrowBookRequest struct { } type BorrowBookResponse struct { - DueDate string `json:"due_date"` ID string `json:"id"` + DueDate string `json:"due_date"` } func (req *BorrowBook) In(c *fiber.Ctx) (domain.BorrowBook, error) { diff --git a/examples/books-api/internal/adapter/inbound/rest/router/v1/presenter/create_book.go b/examples/books-api/internal/adapter/inbound/rest/router/v1/presenter/create_book.go index c97947f..ce10716 100644 --- a/examples/books-api/internal/adapter/inbound/rest/router/v1/presenter/create_book.go +++ b/examples/books-api/internal/adapter/inbound/rest/router/v1/presenter/create_book.go @@ -9,9 +9,9 @@ import ( type CreateBook struct{} type CreateBookRequest struct { + Author string `json:"author"` Isbn string `json:"isbn"` Title string `json:"title"` - Author string `json:"author"` } type CreateBookResponse struct { @@ -27,9 +27,9 @@ func (req *CreateBook) In(c *fiber.Ctx) (domain.CreateBook, error) { return domain.CreateBook{ + Author: bodyRequest.Author, Isbn: bodyRequest.Isbn, Title: bodyRequest.Title, - Author: bodyRequest.Author, }, nil } diff --git a/examples/books-api/internal/adapter/inbound/rest/router/v1/presenter/get_book.go b/examples/books-api/internal/adapter/inbound/rest/router/v1/presenter/get_book.go index 2137236..e015a3f 100644 --- a/examples/books-api/internal/adapter/inbound/rest/router/v1/presenter/get_book.go +++ b/examples/books-api/internal/adapter/inbound/rest/router/v1/presenter/get_book.go @@ -13,11 +13,11 @@ type GetBookParams struct { } type GetBookResponse struct { - Isbn string `json:"isbn"` - Status string `json:"status"` Title string `json:"title"` Author string `json:"author"` ID string `json:"id"` + Isbn string `json:"isbn"` + Status string `json:"status"` } func (req *GetBook) In(c *fiber.Ctx) (domain.GetBook, error) { diff --git a/examples/books-api/internal/adapter/outbound/cache/redis/command.go b/examples/books-api/internal/adapter/outbound/cache/redis/command.go new file mode 100644 index 0000000..45b6e00 --- /dev/null +++ b/examples/books-api/internal/adapter/outbound/cache/redis/command.go @@ -0,0 +1,28 @@ +package redisadapter + +import ( + "context" + "time" + + "github.com/redis/go-redis/v9" +) + +type Client struct { + conn *redis.Client +} + +func (c *Client) Exist(ctx context.Context, key string) bool { + return c.conn.Exists(context.Background(), key).Val() == 1 +} + +func (c *Client) SetEx(ctx context.Context, key string, value any, expiry time.Duration) error { + return c.conn.Set(context.Background(), key, value, expiry).Err() +} + +func (c *Client) Set(ctx context.Context, key string, value any) error { + return c.conn.Set(context.Background(), key, value, 0).Err() +} + +func (c *Client) GetString(ctx context.Context, key string) (string, error) { + return c.conn.Get(context.Background(), key).Result() +} diff --git a/examples/books-api/internal/adapter/outbound/cache/redis/redis.go b/examples/books-api/internal/adapter/outbound/cache/redis/redis.go new file mode 100644 index 0000000..6343de6 --- /dev/null +++ b/examples/books-api/internal/adapter/outbound/cache/redis/redis.go @@ -0,0 +1,46 @@ +package redisadapter + +import ( + "context" + "sync" + + "github.com/muhfaris/rocket/examples/books-api/internal/core/port/outbound/repository" + "github.com/redis/go-redis/v9" +) + +type RedisOptions struct { + Addr string + Username string + Password string + DB int +} + +var ( + cache *redis.Client + once sync.Once +) + +func New(opt RedisOptions) repository.CacheRepository { + if cache != nil { + return nil + } + + once.Do(func() { + rdb := redis.NewClient(&redis.Options{ + Addr: opt.Addr, + Password: opt.Password, // no password set + Username: opt.Username, + DB: opt.DB, // use default DB + }) + + if err := rdb.Ping(context.Background()).Err(); err != nil { + panic(err) + } + + cache = rdb + }) + + return &Client{ + conn: cache, + } +} diff --git a/examples/books-api/internal/adapter/outbound/datastore/psql/command.go b/examples/books-api/internal/adapter/outbound/datastore/psql/command.go new file mode 100644 index 0000000..8d3f9a4 --- /dev/null +++ b/examples/books-api/internal/adapter/outbound/datastore/psql/command.go @@ -0,0 +1,43 @@ +package psqladapter + +import ( + "context" + + "github.com/jackc/pgx/v4" + "github.com/jackc/pgx/v4/pgxpool" +) + +type Client struct { + conn *pgxpool.Pool +} + +func (c *Client) WithTx(ctx context.Context, funcs ...func(tx pgx.Tx) (interface{}, error)) (interface{}, error) { + conn, err := c.conn.Acquire(ctx) + if err != nil { + return nil, err + } + + defer conn.Release() + tx, err := conn.Begin(ctx) + if err != nil { + return nil, err + } + + for _, fn := range funcs { + _, err := fn(tx) + if err != nil { + err = tx.Rollback(ctx) + return nil, err + } + } + + if err := tx.Commit(ctx); err != nil { + return nil, err + } + + return nil, nil +} + +func (c *Client) GetConnection() *pgxpool.Pool { + return c.conn +} diff --git a/examples/books-api/internal/adapter/outbound/datastore/psql/psql.go b/examples/books-api/internal/adapter/outbound/datastore/psql/psql.go new file mode 100644 index 0000000..961cbdc --- /dev/null +++ b/examples/books-api/internal/adapter/outbound/datastore/psql/psql.go @@ -0,0 +1,53 @@ +package psqladapter + +import ( + "context" + "fmt" + "sync" + + "github.com/jackc/pgx/v4/pgxpool" + "github.com/muhfaris/rocket/examples/books-api/internal/core/port/outbound/repository" +) + +var ( + psqlPool *pgxpool.Pool + once sync.Once +) + +type PSQLConfig struct { + Host string + Port int + Username string + Password string + DB string +} + +func New(opt PSQLConfig) repository.PSQLRepository { + var ( + ctx = context.Background() + dsn = fmt.Sprintf("postgres://%s:%s@%s:%d/%s", opt.Username, opt.Password, opt.Host, opt.Port, opt.DB) + ) + + if psqlPool != nil { + return &Client{ + conn: psqlPool, + } + } + + once.Do(func() { + pool, err := pgxpool.Connect(ctx, dsn) + if err != nil { + panic(err) + } + + if err := pool.Ping(context.Background()); err != nil { + panic(err) + } + + psqlPool = pool + }) + + return &Client{ + conn: psqlPool, + } +} diff --git a/examples/books-api/internal/adapter/outbound/datastore/psql/repository/book.go b/examples/books-api/internal/adapter/outbound/datastore/psql/repository/book.go new file mode 100644 index 0000000..dad616a --- /dev/null +++ b/examples/books-api/internal/adapter/outbound/datastore/psql/repository/book.go @@ -0,0 +1,32 @@ +package pgsqlrepository + +import ( + "context" + + "github.com/jackc/pgx/v4/pgxpool" + "github.com/muhfaris/rocket/examples/books-api/internal/core/port/outbound/repository" +) + +type BookRepository struct { + conn *pgxpool.Pool +} + +func NewBookRepository(conn repository.PSQLRepository) repository.BookRepository { + return &BookRepository{conn: conn.GetConnection()} +} + +func (r *BookRepository) Create(ctx context.Context) error { + return nil +} + +func (r *BookRepository) Update(ctx context.Context) error { + return nil +} + +func (r *BookRepository) FindByID(id string) error { + return nil +} + +func (r *BookRepository) Find() error { + return nil +} diff --git a/examples/books-api/internal/core/domain/books.go b/examples/books-api/internal/core/domain/books.go index ce3c045..76c1ab9 100644 --- a/examples/books-api/internal/core/domain/books.go +++ b/examples/books-api/internal/core/domain/books.go @@ -1,18 +1,5 @@ package domain -type HealthCheck struct { -} -type ListBooks struct { - Status string `query:"status"` - Limit int `query:"limit"` - Offset int `query:"offset"` -} -type CreateBook struct { - ID string `json:"id"` - Isbn string `json:"isbn"` - Title string `json:"title"` - Author string `json:"author"` -} type GetBook struct { Bookid string `params:"bookId"` } @@ -25,3 +12,16 @@ type ReturnBook struct { ID string `json:"id"` Bookid string `params:"bookId"` } +type HealthCheck struct { +} +type ListBooks struct { + Status string `query:"status"` + Limit int `query:"limit"` + Offset int `query:"offset"` +} +type CreateBook struct { + ID string `json:"id"` + Author string `json:"author"` + Isbn string `json:"isbn"` + Title string `json:"title"` +} diff --git a/examples/books-api/internal/core/port/inbound/registry/registry.go b/examples/books-api/internal/core/port/inbound/registry/registry.go index 412beaa..7874cd4 100644 --- a/examples/books-api/internal/core/port/inbound/registry/registry.go +++ b/examples/books-api/internal/core/port/inbound/registry/registry.go @@ -11,5 +11,6 @@ type Service interface { } type Repository interface { + CacheRepository() repository.CacheRepository GetBookRepository() repository.BookRepository } diff --git a/examples/books-api/internal/core/port/inbound/service/booksvc.go b/examples/books-api/internal/core/port/inbound/service/booksvc.go index 8591f0a..a43e8d5 100644 --- a/examples/books-api/internal/core/port/inbound/service/booksvc.go +++ b/examples/books-api/internal/core/port/inbound/service/booksvc.go @@ -8,10 +8,10 @@ import ( //go:generate mockgen -destination=../../../../../shared/mock/service/service.go -package=mockservices -source=service.go type BookSvc interface { - HealthCheck(ctx context.Context, payload domain.HealthCheck) (domain.HealthCheck, error) - ListBooks(ctx context.Context, payload domain.ListBooks) (domain.ListBooks, error) - CreateBook(ctx context.Context, payload domain.CreateBook) (domain.CreateBook, error) GetBook(ctx context.Context, payload domain.GetBook) (domain.GetBook, error) BorrowBook(ctx context.Context, payload domain.BorrowBook) (domain.BorrowBook, error) ReturnBook(ctx context.Context, payload domain.ReturnBook) error + HealthCheck(ctx context.Context, payload domain.HealthCheck) (domain.HealthCheck, error) + ListBooks(ctx context.Context, payload domain.ListBooks) (domain.ListBooks, error) + CreateBook(ctx context.Context, payload domain.CreateBook) (domain.CreateBook, error) } diff --git a/examples/books-api/internal/core/port/outbound/repository/cache.go b/examples/books-api/internal/core/port/outbound/repository/cache.go new file mode 100644 index 0000000..28f970d --- /dev/null +++ b/examples/books-api/internal/core/port/outbound/repository/cache.go @@ -0,0 +1,13 @@ +package repository + +import ( + "context" + "time" +) + +type CacheRepository interface { + Exist(ctx context.Context, key string) bool + SetEx(ctx context.Context, key string, data any, expiry time.Duration) error + Set(ctx context.Context, key string, data any) error + GetString(ctx context.Context, key string) (string, error) +} diff --git a/examples/books-api/internal/core/port/outbound/repository/psql.go b/examples/books-api/internal/core/port/outbound/repository/psql.go new file mode 100644 index 0000000..7415573 --- /dev/null +++ b/examples/books-api/internal/core/port/outbound/repository/psql.go @@ -0,0 +1,9 @@ +package repository + +import ( + "github.com/jackc/pgx/v4/pgxpool" +) + +type PSQLRepository interface { + GetConnection() *pgxpool.Pool +} diff --git a/examples/books-api/internal/core/service/booksvc.go b/examples/books-api/internal/core/service/booksvc.go index 90e2b15..bc0e3ad 100644 --- a/examples/books-api/internal/core/service/booksvc.go +++ b/examples/books-api/internal/core/service/booksvc.go @@ -15,15 +15,6 @@ type BookSvc struct { func NewBookSvc(reg portregistry.Repository) portservice.BookSvc { return &BookSvc{reg: reg} } -func (s *BookSvc) HealthCheck(ctx context.Context, payload domain.HealthCheck) (domain.HealthCheck, error) { - return domain.HealthCheck{}, nil -} -func (s *BookSvc) ListBooks(ctx context.Context, payload domain.ListBooks) (domain.ListBooks, error) { - return domain.ListBooks{}, nil -} -func (s *BookSvc) CreateBook(ctx context.Context, payload domain.CreateBook) (domain.CreateBook, error) { - return domain.CreateBook{}, nil -} func (s *BookSvc) GetBook(ctx context.Context, payload domain.GetBook) (domain.GetBook, error) { return domain.GetBook{}, nil } @@ -33,3 +24,12 @@ func (s *BookSvc) BorrowBook(ctx context.Context, payload domain.BorrowBook) (do func (s *BookSvc) ReturnBook(ctx context.Context, payload domain.ReturnBook) error { return nil } +func (s *BookSvc) HealthCheck(ctx context.Context, payload domain.HealthCheck) (domain.HealthCheck, error) { + return domain.HealthCheck{}, nil +} +func (s *BookSvc) ListBooks(ctx context.Context, payload domain.ListBooks) (domain.ListBooks, error) { + return domain.ListBooks{}, nil +} +func (s *BookSvc) CreateBook(ctx context.Context, payload domain.CreateBook) (domain.CreateBook, error) { + return domain.CreateBook{}, nil +} diff --git a/examples/books-api/rocket.yaml b/examples/books-api/rocket.yaml index c94b516..c55c9dc 100644 --- a/examples/books-api/rocket.yaml +++ b/examples/books-api/rocket.yaml @@ -3,7 +3,7 @@ app: package: github.com/muhfaris/rocket/examples/books-api project: books-api arch: hexagonal - docket: false - database: "" - cache: "" + docket: true + database: postgresql + cache: redis ignoredataresponse: "" diff --git a/rocket-books-api.yaml b/rocket-books-api.yaml index 4b63809..b2dec05 100644 --- a/rocket-books-api.yaml +++ b/rocket-books-api.yaml @@ -3,4 +3,6 @@ app: package: "github.com/muhfaris/rocket/examples/books-api" project: "books-api" arch: "hexagonal" - docker: false + docker: true + database: "postgresql" + cache: "redis" diff --git a/shared/templates/hexagonal/README.tmpl b/shared/templates/hexagonal/README.tmpl index 7a1d2ac..2630703 100644 --- a/shared/templates/hexagonal/README.tmpl +++ b/shared/templates/hexagonal/README.tmpl @@ -1,93 +1,90 @@ # {{.ProjectName}} -A brief description of the project, what it does, and its purpose. +API generated by [Rocket](https://github.com/muhfaris/rocket) from an OpenAPI 3.0 specification. --- -## Table of Contents - -1. [Getting Started](#getting-started) -2. [Prerequisites](#prerequisites) -3. [Installation](#installation) -4. [Usage](#usage) -5. [Configuration](#configuration) -6. [Testing](#testing) -7. [Contributing](#contributing) -8. [License](#license) - ---- - -## Getting Started - -These instructions will help you get a copy of the project up and running on your local machine for development and testing purposes. - ---- - -## Prerequisites - -- Go (minimum version 1.22) installed on your system. -- [Optional] Docker for containerized deployment. - ---- - -## Installation - -### Clone the Repository +## Quick Start ```bash -git clone {{.PackagePath}} -cd repository +cd {{.ProjectName}} +make start ``` -### Initialize the Project - -Run the following command to prepare the project: +The server starts on `http://localhost:7000` (configurable in `config/config.yaml`). -```bash -make init -``` - -This command will: +--- -- Run `go mod tidy` to clean up dependencies. -- Run `go mod vendor` to vendor dependencies. +## API Endpoints -### Quick Start +{{if .HasEndpoints}}{{range .RouteGroups}} +### {{.GroupName}} (prefix `{{.GroupPath}}`) -Run the following command to start the services, gomod application and run the application: +| Method | Path | Handler | +|--------|------|---------| +{{- range .Routes}} +| `{{.Method}}` | `{{.Path}}` | `{{.Handler}}` | +{{- end}}{{end}}{{else}} +No endpoints were generated. Verify your OpenAPI spec has at least one path with `operationId` and `tags`. +{{end}} -```bash -make start -``` --- -## Usage +## Architecture -### Running the Application +This project follows **hexagonal architecture** (ports & adapters): -```bash -go run main.go rest +``` +HTTP Request + │ + ▼ +┌─ INBOUND ADAPTER ────────────────────────┐ +│ Router → Handler → Presenter │ auto-generated +│ (internal/adapter/inbound/rest/) │ +└──────────────────┬───────────────────────┘ + │ calls interface + ▼ +┌─ CORE ───────────────────────────────────┐ +│ Service Interface (port) │ contract +│ Service Impl (domain) ← YOUR CODE│ business logic +│ Repository Interface (port) │ contract +└──────────────────┬───────────────────────┘ + │ implements + ▼ +┌─ OUTBOUND ADAPTER ───────────────────────┐ +│ DB Adapter → PostgreSQL / MySQL /… │ auto-generated +│ Cache Adapter → Redis / in-memory │ +└──────────────────────────────────────────┘ ``` -or +### Key directories -```bash -make run -``` +| Directory | Purpose | +|-----------|---------| +| `internal/adapter/inbound/rest/router/v1/handler/` | HTTP handlers — parse requests, call services (auto-generated) | +| `internal/adapter/inbound/rest/router/v1/presenter/` | Map domain models → API responses (TODO stubs — update these) | +| `internal/core/port/inbound/service/` | Service interfaces | +| `internal/core/service/` | **Business logic goes here** — each method is a stub returning an error | +| `internal/core/domain/` | Domain models | +| `internal/core/port/outbound/` | Repository interfaces | +| `internal/adapter/outbound/` | Database/cache implementations{{if .HasDatabase}} +| `internal/adapter/outbound/datastore/{{.DatabaseType}}/repository/` | Query implementations (TODO stubs){{end}}{{if .HasCache}} +| `internal/adapter/outbound/cache/{{.CacheType}}/` | Cache adapter{{end}} -### Build the Application +### Development workflow -```bash -make build -``` +1. **Add business logic** → edit `internal/core/service/*.go` +2. **Add queries** → edit `internal/adapter/outbound/datastore/*/repository/*.go` +3. **Map responses** → edit `internal/adapter/inbound/rest/router/v1/presenter/*.go` +4. **Regenerate from spec** → run `rocket new` again (commit changes first — regeneration overwrites files) --- ## Configuration -Configuration is handled through `config/config.yaml`: +Edit `config/config.yaml`: -```env +```yaml app: name: {{.AppName}} port: 7000 @@ -95,46 +92,51 @@ app: config: false fiber: enable_print_routes: true - enable_splitting_on_parsers: true + enable_splitting_on_parsers: true{{if eq .CacheType "redis"}} cache: redis: addr: localhost:6379 username: password: - db: 0 + db: 0{{end}}{{if eq .DatabaseType "postgresql"}} datastore: psql: host: localhost port: 5432 username: myapp password: myapp - db: myapp + db: myapp{{else if eq .DatabaseType "mysql"}} + datastore: + mysql: + host: localhost + port: 3306 + username: myapp + password: myapp + db: myapp{{else if eq .DatabaseType "mongodb"}} + datastore: + mongo: + uri: mongodb://localhost:27017 + database: myapp{{else if eq .DatabaseType "sqlite"}} + datastore: + sqlite: + path: ./data/myapp.db{{end}} ``` --- -### Shortcuts Commands - -The project comes with a set of shortcuts commands to make development easier: - -- `make start` - Run the services, gomod application and run the application. -- `make services` - Start necessary services and remove old volume. -- `make run` - Run the application. -- `make build` - Build the application for deployment and store it in the `$(GOPATH)/bin` directory. -- `make init` - Run `go mod tidy` and `go mod vendor` commands. - -## Contributing - -Contributions are welcome! Please follow these steps: +## Makefile -1. Fork the repository. -2. Create a new branch (`git checkout -b feature-name`). -3. Commit your changes (`git commit -m 'Add new feature'`). -4. Push to the branch (`git push origin feature-name`). -5. Open a pull request. +| Command | Description | +|---------|-------------| +| `make start` | Start Docker services + run the application | +| `make services` | Start Docker services only (clean volumes) | +| `make run` | Run the application | +| `make build` | Build binary to `$(GOPATH)/bin` | +| `make init` | Run `go mod tidy` and `go mod vendor` | +| `make test` | Run tests | --- -## License +## Spec -This project is licensed under the [MIT License](LICENSE). +The OpenAPI spec used to generate this project is at `spec/openapi.yaml`. Edit it and regenerate to update the API.