@nakedev/go-scaffold 0.4.3 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +567 -513
- package/dist/commands/auth.js +12 -1
- package/dist/commands/check.js +281 -0
- package/dist/commands/create.js +2 -1
- package/dist/commands/generate.js +5 -2
- package/dist/commands/method.js +66 -70
- package/dist/commands/observability.js +4 -53
- package/dist/commands/rbac.js +19 -8
- package/dist/commands/undo.js +6 -3
- package/dist/commands/worker.js +14 -4
- package/dist/index.js +13 -1
- package/dist/templates/auth-manifest.js +46 -45
- package/dist/templates/create-manifest.js +4 -0
- package/dist/templates/module-manifest.js +82 -60
- package/dist/templates/rbac-manifest.js +15 -11
- package/dist/templates/worker-manifest.js +4 -1
- package/dist/types.js +2 -0
- package/dist/utils/auth-patcher.js +22 -22
- package/dist/utils/config.js +24 -5
- package/dist/utils/docs-patcher.js +68 -0
- package/dist/utils/hexagonal-method-patcher.js +334 -0
- package/dist/utils/main-patcher.js +3 -3
- package/dist/utils/module-location.js +17 -11
- package/dist/utils/platform-patcher.js +27 -0
- package/dist/utils/rbac-patcher.js +73 -216
- package/package.json +1 -1
- package/templates/add/auth/cmd/seed/main.go.hbs +2 -0
- package/templates/add/auth/internal/app/user/{browser_policy.go.hbs → adapters/inbound/http/browser_policy.go.hbs} +3 -3
- package/templates/add/auth/internal/app/user/adapters/inbound/http/dto.go.hbs +159 -0
- package/templates/add/auth/internal/app/user/{handler.go.hbs → adapters/inbound/http/handler.go.hbs} +103 -8
- package/templates/add/auth/internal/app/user/{handler_local.go.hbs → adapters/inbound/http/handler_local.go.hbs} +8 -7
- package/templates/add/auth/internal/app/user/{handler_mfa.go.hbs → adapters/inbound/http/handler_mfa.go.hbs} +6 -6
- package/templates/add/auth/internal/app/user/{handler_oauth.go.hbs → adapters/inbound/http/handler_oauth.go.hbs} +17 -17
- package/templates/add/auth/internal/app/user/{handler_recovery.go.hbs → adapters/inbound/http/handler_recovery.go.hbs} +4 -4
- package/templates/add/auth/internal/app/user/{handler_test.go.hbs → adapters/inbound/http/handler_test.go.hbs} +52 -31
- package/templates/add/auth/internal/app/user/{handler_user.go.hbs → adapters/inbound/http/handler_user.go.hbs} +4 -4
- package/templates/add/auth/internal/app/user/{session_cookie.go.hbs → adapters/inbound/http/session_cookie.go.hbs} +5 -3
- package/templates/add/auth/internal/app/user/adapters/outbound/password/bcrypt.go.hbs +35 -0
- package/templates/add/auth/internal/app/user/adapters/outbound/password/bcrypt_test.go.hbs +20 -0
- package/templates/add/auth/internal/app/user/{mfa_store.go.hbs → adapters/outbound/postgres/mfa_store.go.hbs} +22 -20
- package/templates/add/auth/internal/app/user/{mfa_store_test.go.hbs → adapters/outbound/postgres/mfa_store_test.go.hbs} +7 -7
- package/templates/add/auth/internal/app/user/adapters/outbound/postgres/model.go.hbs +84 -0
- package/templates/add/auth/internal/app/user/adapters/outbound/postgres/repository.go.hbs +211 -0
- package/templates/add/auth/internal/app/user/{repository_test.go.hbs → adapters/outbound/postgres/repository_test.go.hbs} +18 -19
- package/templates/add/auth/internal/app/user/{tokenstore_pg.go.hbs → adapters/outbound/postgres/tokenstore_pg.go.hbs} +36 -32
- package/templates/add/auth/internal/app/user/{tokenstore_pg_test.go.hbs → adapters/outbound/postgres/tokenstore_pg_test.go.hbs} +10 -3
- package/templates/add/auth/internal/app/user/{tokenstore_recovery.go.hbs → adapters/outbound/postgres/tokenstore_recovery.go.hbs} +29 -3
- package/templates/add/auth/internal/app/user/{tokenstore_redis.go.hbs → adapters/outbound/redis/tokenstore.go.hbs} +43 -36
- package/templates/add/auth/internal/app/user/{tokenstore_redis_test.go.hbs → adapters/outbound/redis/tokenstore_test.go.hbs} +14 -3
- package/templates/add/auth/internal/app/user/application/contracts.go.hbs +52 -0
- package/templates/add/auth/internal/app/user/application/dto.go.hbs +75 -0
- package/templates/add/auth/internal/app/user/application/errors.go.hbs +62 -0
- package/templates/add/auth/internal/app/user/{external_login.go.hbs → application/external_login.go.hbs} +30 -40
- package/templates/add/auth/internal/app/user/{jwt.go.hbs → application/jwt.go.hbs} +6 -3
- package/templates/add/auth/internal/app/user/{local_auth.go.hbs → application/local_auth.go.hbs} +17 -19
- package/templates/add/auth/internal/app/user/{mfa_service.go.hbs → application/mfa_service.go.hbs} +27 -28
- package/templates/add/auth/internal/app/user/{mfa_service_test.go.hbs → application/mfa_service_test.go.hbs} +12 -11
- package/templates/add/auth/internal/app/user/application/oauth.go.hbs +3 -3
- package/templates/add/auth/internal/app/user/{provider_test.go.hbs → application/provider_test.go.hbs} +55 -56
- package/templates/add/auth/internal/app/user/application/recovery.go.hbs +16 -47
- package/templates/add/auth/internal/app/user/{recovery_service.go.hbs → application/recovery_service.go.hbs} +13 -15
- package/templates/add/auth/internal/app/user/application/service.go.hbs +145 -0
- package/templates/add/auth/internal/app/user/{service_test.go.hbs → application/service_test.go.hbs} +94 -75
- package/templates/add/auth/internal/app/user/{sessions.go.hbs → application/sessions.go.hbs} +14 -14
- package/templates/add/auth/internal/app/user/application/tokenstore_ports.go.hbs +14 -0
- package/templates/add/auth/internal/app/user/{user_query.go.hbs → application/user_query.go.hbs} +16 -16
- package/templates/add/auth/internal/app/user/composition.go.hbs +89 -86
- package/templates/add/auth/internal/app/user/domain/entity.go.hbs +41 -0
- package/templates/add/auth/internal/app/user/domain/errors.go.hbs +32 -0
- package/templates/add/auth/internal/app/user/ports/password.go.hbs +9 -0
- package/templates/add/auth/internal/app/user/ports/repository.go.hbs +90 -0
- package/templates/add/auth/migrations/create_auth_tokens.up.sql.hbs +1 -1
- package/templates/add/auth/migrations/create_login_throttle.up.sql.hbs +1 -1
- package/templates/add/auth/migrations/create_users.up.sql.hbs +2 -1
- package/templates/add/rbac/internal/app/role/adapters/inbound/http/handler.go.hbs +142 -0
- package/templates/add/rbac/internal/app/role/adapters/inbound/http/handler_test.go.hbs +19 -0
- package/templates/add/rbac/internal/app/role/adapters/outbound/postgres/model.go.hbs +48 -0
- package/templates/add/rbac/internal/app/role/adapters/outbound/postgres/repository.go.hbs +127 -0
- package/templates/add/rbac/internal/app/role/{repository_test.go.hbs → adapters/outbound/postgres/repository_test.go.hbs} +8 -8
- package/templates/add/rbac/internal/app/role/application/dto.go.hbs +47 -0
- package/templates/add/rbac/internal/app/role/application/errors.go.hbs +19 -0
- package/templates/add/rbac/internal/app/role/application/service.go.hbs +157 -0
- package/templates/add/rbac/internal/app/role/{service_test.go.hbs → application/service_test.go.hbs} +26 -19
- package/templates/add/rbac/internal/app/role/composition.go.hbs +23 -10
- package/templates/add/rbac/internal/app/role/domain/entity.go.hbs +23 -0
- package/templates/add/rbac/internal/app/role/domain/errors.go.hbs +26 -0
- package/templates/add/rbac/internal/app/role/ports/repository.go.hbs +25 -0
- package/templates/add/rbac/migrations/add_roles.down.sql.hbs +3 -11
- package/templates/add/rbac/migrations/add_roles.up.sql.hbs +17 -6
- package/templates/add/worker/internal/platform/queue/river_test.go.hbs +84 -0
- package/templates/create/base/.claude/skills/go-scaffold/SKILL.md.hbs +54 -36
- package/templates/create/base/.golangci.yml.hbs +2 -2
- package/templates/create/base/AGENTS.md.hbs +55 -31
- package/templates/create/base/README.md.hbs +77 -22
- package/templates/create/base/cmd/api/wiring.go.hbs +3 -2
- package/templates/create/base/internal/composition/doc.go.hbs +7 -0
- package/templates/create/features/docs/architecture.md.hbs +64 -26
- package/templates/create/features/docs/patterns.md.hbs +117 -90
- package/templates/create/features/docs/techstack.md.hbs +17 -2
- package/templates/generate/module/hexagonal/adapters/inbound/http/dto.go.hbs +45 -0
- package/templates/generate/module/hexagonal/adapters/inbound/http/dto.minimal.go.hbs +28 -0
- package/templates/generate/module/hexagonal/adapters/inbound/http/handler.go.hbs +182 -0
- package/templates/generate/module/hexagonal/adapters/inbound/http/handler.minimal.go.hbs +83 -0
- package/templates/generate/module/hexagonal/adapters/inbound/http/handler_crud_test.go.hbs +18 -0
- package/templates/generate/module/hexagonal/adapters/inbound/http/handler_test.go.hbs +30 -0
- package/templates/generate/module/hexagonal/adapters/outbound/postgres/model.go.hbs +37 -0
- package/templates/generate/module/hexagonal/adapters/outbound/postgres/repository.go.hbs +95 -0
- package/templates/generate/module/{repository_test.go.hbs → hexagonal/adapters/outbound/postgres/repository_test.go.hbs} +8 -8
- package/templates/generate/module/hexagonal/application/commands.crud.go.hbs +54 -0
- package/templates/generate/module/hexagonal/application/commands.go.hbs +25 -0
- package/templates/generate/module/hexagonal/application/cqrs_test.go.hbs +66 -0
- package/templates/generate/module/hexagonal/application/dto.go.hbs +35 -0
- package/templates/generate/module/hexagonal/application/dto.minimal.go.hbs +25 -0
- package/templates/generate/module/hexagonal/application/queries.crud.go.hbs +33 -0
- package/templates/generate/module/hexagonal/application/queries.go.hbs +25 -0
- package/templates/generate/module/hexagonal/application/service.crud.go.hbs +73 -0
- package/templates/generate/module/hexagonal/application/service.go.hbs +29 -0
- package/templates/generate/module/hexagonal/application/service_test.go.hbs +62 -0
- package/templates/generate/module/hexagonal/composition.go.hbs +27 -0
- package/templates/generate/module/hexagonal/domain/entity.go.hbs +20 -0
- package/templates/generate/module/hexagonal/domain/errors.go.hbs +11 -0
- package/templates/generate/module/hexagonal/ports/repository.go.hbs +38 -0
- package/templates/generate/module/migration.up.sql.hbs +1 -1
- package/dist/utils/method-patcher.js +0 -537
- package/templates/add/auth/internal/app/user/contracts.go.hbs +0 -88
- package/templates/add/auth/internal/app/user/dto.go.hbs +0 -134
- package/templates/add/auth/internal/app/user/errors.go.hbs +0 -68
- package/templates/add/auth/internal/app/user/model/authtoken.go.hbs +0 -45
- package/templates/add/auth/internal/app/user/model/identity.go.hbs +0 -32
- package/templates/add/auth/internal/app/user/model/loginthrottle.go.hbs +0 -26
- package/templates/add/auth/internal/app/user/model/mfa_challenge.go.hbs +0 -17
- package/templates/add/auth/internal/app/user/model/mfa_enrollment.go.hbs +0 -20
- package/templates/add/auth/internal/app/user/model/mfa_recovery_code.go.hbs +0 -17
- package/templates/add/auth/internal/app/user/model/user.go.hbs +0 -31
- package/templates/add/auth/internal/app/user/repository.go.hbs +0 -139
- package/templates/add/auth/internal/app/user/service.go.hbs +0 -135
- package/templates/add/auth/internal/app/user/tokenstore.go.hbs +0 -58
- package/templates/add/rbac/internal/app/role/dto.go.hbs +0 -45
- package/templates/add/rbac/internal/app/role/errors.go.hbs +0 -39
- package/templates/add/rbac/internal/app/role/handler.go.hbs +0 -104
- package/templates/add/rbac/internal/app/role/model/permission.go.hbs +0 -12
- package/templates/add/rbac/internal/app/role/model/role.go.hbs +0 -22
- package/templates/add/rbac/internal/app/role/model/role_permission.go.hbs +0 -11
- package/templates/add/rbac/internal/app/role/repository.go.hbs +0 -97
- package/templates/add/rbac/internal/app/role/service.go.hbs +0 -217
- package/templates/generate/module/commands.go.hbs +0 -95
- package/templates/generate/module/composition.go.hbs +0 -23
- package/templates/generate/module/cqrs_test.go.hbs +0 -7
- package/templates/generate/module/dto.go.hbs +0 -36
- package/templates/generate/module/errors.go.hbs +0 -33
- package/templates/generate/module/handler.go.hbs +0 -179
- package/templates/generate/module/handler_test.go.hbs +0 -174
- package/templates/generate/module/minimal/commands.go.hbs +0 -34
- package/templates/generate/module/minimal/dto.go.hbs +0 -28
- package/templates/generate/module/minimal/handler.go.hbs +0 -82
- package/templates/generate/module/minimal/handler_test.go.hbs +0 -10
- package/templates/generate/module/minimal/queries.go.hbs +0 -45
- package/templates/generate/module/minimal/service.go.hbs +0 -71
- package/templates/generate/module/minimal/service_test.go.hbs +0 -77
- package/templates/generate/module/model/model.go.hbs +0 -36
- package/templates/generate/module/queries.go.hbs +0 -62
- package/templates/generate/module/repository.go.hbs +0 -103
- package/templates/generate/module/service.go.hbs +0 -164
- package/templates/generate/module/service_test.go.hbs +0 -161
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// Package composition contains process-level wiring and cross-feature adapters.
|
|
2
|
+
//
|
|
3
|
+
// Feature-local construction belongs in internal/app/<feature>/composition.go.
|
|
4
|
+
// Keep this package focused on translating caller-owned ports to another
|
|
5
|
+
// feature's public application capabilities. Do not put business rules,
|
|
6
|
+
// persistence adapters, or HTTP handlers here.
|
|
7
|
+
package composition
|
|
@@ -2,15 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
## Composition root
|
|
4
4
|
|
|
5
|
-
`cmd/api/main.go` decides the exit code and nothing else.
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
`cmd/api/main.go` decides the exit code and nothing else. `cmd/api/wiring.go`
|
|
6
|
+
owns process bootstrap and route registration; `internal/composition/` owns
|
|
7
|
+
reusable process-level adapters that connect feature ports.
|
|
8
8
|
|
|
9
9
|
Modules never import one another. Each generated domain owns its local
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
outbound adapter → service or command/query application handlers → inbound
|
|
11
|
+
adapter composition in `composition.go`; `internal/composition/` owns
|
|
12
|
+
cross-feature adapters and `wiring.go` supplies infrastructure, invokes those
|
|
13
|
+
adapters, and registers routes. A domain that needs another's behaviour
|
|
14
|
+
declares a narrow interface for exactly what it needs and receives its
|
|
15
|
+
concrete implementation from the process composition; `.golangci.yml` has a
|
|
14
16
|
depguard rule per domain that fails the build if anyone shortcuts that.
|
|
15
17
|
|
|
16
18
|
Two things the split buys. `run()` returns an error instead of calling
|
|
@@ -21,32 +23,49 @@ you open to understand the binary: `wiring.go` grows with the system, which is
|
|
|
21
23
|
what a composition root is for, while `main.go` stays put.
|
|
22
24
|
|
|
23
25
|
|
|
24
|
-
> Status: generated by `@nakedev/go-scaffold
|
|
25
|
-
> Scope:
|
|
26
|
+
> Status: generated by `@nakedev/go-scaffold`; refreshes as optional features are added
|
|
27
|
+
> Scope: current generated project configuration
|
|
26
28
|
|
|
27
29
|
## 1. Topology
|
|
28
30
|
|
|
29
|
-
**Decision:**
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
**Decision:** `cmd/api` serves REST over Gin. Optional operational binaries
|
|
32
|
+
are separate entry points: `cmd/worker` for queued jobs and `cmd/seed` for
|
|
33
|
+
one-shot bootstrap tasks when those features are installed.
|
|
34
|
+
**Rationale:** The HTTP process stays independently deployable while worker and
|
|
35
|
+
seed lifecycles remain explicit. Adding either binary does not turn an API
|
|
36
|
+
handler or feature composition root into a process-wide god object.
|
|
34
37
|
|
|
35
38
|
## 2. Package Layout
|
|
36
39
|
|
|
37
40
|
```text
|
|
38
41
|
cmd/
|
|
39
|
-
└── api/
|
|
42
|
+
└── api/
|
|
43
|
+
├── main.go # process entrypoint and exit code
|
|
44
|
+
└── wiring.go # config, infrastructure, graceful shutdown, routes
|
|
40
45
|
internal/
|
|
41
|
-
├──
|
|
42
|
-
|
|
46
|
+
├── composition/ # process-level cross-feature adapters
|
|
47
|
+
├── platform/ # talks to real external systems
|
|
48
|
+
│ └── database/ # PostgreSQL connection and pool
|
|
43
49
|
├── shared/ # pure logic/framework glue, no I/O
|
|
44
50
|
│ ├── config/ apperror/ dberr/ httpx/ id/
|
|
45
51
|
│ └── middleware/ pagination/ tx/
|
|
46
52
|
└── app/ # domain packages (one per feature)
|
|
47
53
|
└── <domain>/
|
|
54
|
+
├── domain/ # business entities and errors
|
|
55
|
+
├── ports/ # consumer-owned application dependencies
|
|
56
|
+
├── application/ # service OR commands + queries
|
|
57
|
+
├── adapters/inbound/http/ # request/response DTOs + HTTP adapter
|
|
58
|
+
├── adapters/outbound/postgres/
|
|
59
|
+
└── composition.go # feature-local object graph
|
|
48
60
|
```
|
|
49
61
|
|
|
62
|
+
Installed process and platform additions:
|
|
63
|
+
{{#if auth}}- `cmd/seed` and `platform/authprovider/google/` for user bootstrap and Google OAuth/OIDC
|
|
64
|
+
{{/if}}{{#if worker}}- `cmd/worker` and `platform/{mail,queue}/` for queue/SMTP jobs{{#if (eq queue "asynq")}}, plus `platform/cache/` for Redis{{/if}}
|
|
65
|
+
{{/if}}{{#if observability}}- `platform/telemetry/` for the OTLP exporter
|
|
66
|
+
{{/if}}{{#if rbac}}- `shared/middleware/authz.go` and `app/role/` for RBAC policy
|
|
67
|
+
{{/if}}
|
|
68
|
+
|
|
50
69
|
**Decision:** `platform/` vs `shared/` is split by whether the code talks to
|
|
51
70
|
a real external system.
|
|
52
71
|
**Rationale:** Code that does I/O against Postgres/cache/queue/SMTP/S3 →
|
|
@@ -54,12 +73,19 @@ a real external system.
|
|
|
54
73
|
add a `shared/utils` or similarly generic package — name it after what it
|
|
55
74
|
actually does.
|
|
56
75
|
|
|
76
|
+
Optional feature boundaries are explicit: auth owns user authentication and
|
|
77
|
+
provider ports; RBAC owns role policy and authorization; worker owns queue and
|
|
78
|
+
mail adapters; observability owns metrics/tracing setup. They do not create a
|
|
79
|
+
second domain architecture.
|
|
80
|
+
|
|
57
81
|
## 3. API Style
|
|
58
82
|
|
|
59
83
|
**Decision:** REST over Gin, one handler package per domain, every route
|
|
60
84
|
grouped under{{#if apiPrefix}} `/{{apiPrefix}}`{{else}} no prefix{{/if}}.
|
|
61
|
-
**Rationale:** `
|
|
62
|
-
|
|
85
|
+
**Rationale:** `inbound HTTP adapter → application boundary → domain + ports →
|
|
86
|
+
outbound adapter`, one direction,
|
|
87
|
+
with delivery, application, and persistence concerns kept inside the feature
|
|
88
|
+
boundary — no cross-domain imports. The prefix is a
|
|
63
89
|
single project-wide choice made at `create` time (`--api-prefix`) — there is
|
|
64
90
|
no per-domain versioning; a domain that needs a real breaking change gets a
|
|
65
91
|
new domain package (or a new field on the existing DTO), not a duplicated
|
|
@@ -80,10 +106,16 @@ from empty command/query wrappers, so it keeps one service until its business
|
|
|
80
106
|
needs justify the split. CQRS here does not require a second database, broker,
|
|
81
107
|
or event-sourcing runtime.
|
|
82
108
|
|
|
83
|
-
When enabled, `commands.go` owns state-changing application use
|
|
84
|
-
`queries.go` owns read use cases, and `composition.go`
|
|
85
|
-
HTTP
|
|
86
|
-
selects infrastructure
|
|
109
|
+
When enabled, `application/commands.go` owns state-changing application use
|
|
110
|
+
cases, `application/queries.go` owns read use cases, and `composition.go`
|
|
111
|
+
constructs both. The HTTP adapter is still an inbound adapter and
|
|
112
|
+
`cmd/api/wiring.go` still selects infrastructure, invokes process-level
|
|
113
|
+
adapters from `internal/composition/`, and registers the feature. A service
|
|
114
|
+
module instead has one `application/service.go`; these
|
|
115
|
+
styles are exclusive inside a module but can coexist across the monolith.
|
|
116
|
+
|
|
117
|
+
The CQRS inbound adapter is wired through `NewHandler` with separate command
|
|
118
|
+
and query ports. It does not generate an `application/service.go` facade.
|
|
87
119
|
|
|
88
120
|
## 5. Response and Error Model
|
|
89
121
|
|
|
@@ -92,7 +124,7 @@ selects infrastructure and registers the feature.
|
|
|
92
124
|
**Rationale:** Handlers only ever do `c.Error(err); return` — no `c.JSON` per
|
|
93
125
|
call site, no error-shape drift between domains.
|
|
94
126
|
|
|
95
|
-
- Domain-specific errors live in each
|
|
127
|
+
- Domain-specific errors live in each module's `domain/errors.go` (e.g.
|
|
96
128
|
`USER_NOT_FOUND`, not a bare `NOT_FOUND`)
|
|
97
129
|
- DB errors are classified once in `shared/dberr` (`IsDuplicate`,
|
|
98
130
|
`IsForeignKey`) and mapped to the right HTTP status per domain
|
|
@@ -169,5 +201,11 @@ skipped by the `os.Exit` calls already in there.
|
|
|
169
201
|
|
|
170
202
|
- `go-scaffold generate module <name>` adds a new domain package and
|
|
171
203
|
wires it into `cmd/api/wiring.go`
|
|
172
|
-
-
|
|
173
|
-
|
|
204
|
+
- `go-scaffold check` validates the physical split, process-composition
|
|
205
|
+
boundary, layer dependencies, and exclusive service/CQRS choice for every
|
|
206
|
+
discovered module
|
|
207
|
+
- `go-scaffold add auth`, `add worker`, `add rbac`, and `add observability`
|
|
208
|
+
refresh this document while it still matches the generated version. A
|
|
209
|
+
hand-edited document is preserved and must be updated by its owner.
|
|
210
|
+
- This document describes package boundaries, not completed business behavior;
|
|
211
|
+
generated methods remain explicit TODO/501 stubs until implemented.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Coding Patterns: {{projectName}}
|
|
2
2
|
|
|
3
|
-
> Status: generated by `@nakedev/go-scaffold
|
|
4
|
-
> Last
|
|
3
|
+
> Status: generated by `@nakedev/go-scaffold`; current generated-project conventions
|
|
4
|
+
> Last reviewed: generated at project creation and refreshed with feature docs
|
|
5
5
|
|
|
6
6
|
## Domain Package Shape
|
|
7
7
|
|
|
@@ -10,44 +10,56 @@ like this — copy the shape by hand if you ever add one without the CLI:
|
|
|
10
10
|
|
|
11
11
|
```text
|
|
12
12
|
internal/app/<domain>/
|
|
13
|
-
├──
|
|
14
|
-
│
|
|
15
|
-
|
|
16
|
-
├──
|
|
17
|
-
|
|
18
|
-
├──
|
|
19
|
-
├──
|
|
20
|
-
├──
|
|
21
|
-
├──
|
|
22
|
-
├──
|
|
23
|
-
|
|
24
|
-
├──
|
|
25
|
-
|
|
13
|
+
├── domain/
|
|
14
|
+
│ ├── entity.go # business state and invariants; no Gin/GORM/HTTP
|
|
15
|
+
│ └── errors.go # domain error catalog (<DOMAIN>_NOT_FOUND, ...)
|
|
16
|
+
├── ports/
|
|
17
|
+
│ └── repository.go # consumer-owned persistence/application interfaces
|
|
18
|
+
├── application/
|
|
19
|
+
│ ├── dto.go # transport-neutral use-case input/output types
|
|
20
|
+
│ ├── service.go # service-style use-case orchestration
|
|
21
|
+
│ ├── commands.go # CQRS writes, when selected
|
|
22
|
+
│ ├── queries.go # CQRS reads, when selected
|
|
23
|
+
│ └── *_test.go # application unit tests, no DB
|
|
24
|
+
├── adapters/
|
|
25
|
+
│ ├── inbound/http/
|
|
26
|
+
│ │ ├── dto.go # JSON/binding DTOs and application/HTTP mapping
|
|
27
|
+
│ │ ├── handler.go # Gin routing, error/response mapping
|
|
28
|
+
│ │ └── handler_test.go # HTTP adapter tests, no DB
|
|
29
|
+
│ └── outbound/postgres/
|
|
30
|
+
│ ├── model.go # persistence model + explicit domain mapping
|
|
31
|
+
│ ├── repository.go # GORM adapter; the only DB access in the module
|
|
32
|
+
│ └── repository_test.go # Postgres integration tests
|
|
33
|
+
└── composition.go # feature-local adapter → application → handler wiring
|
|
26
34
|
```
|
|
27
35
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
-
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
`service.go` and `commands.go`/`queries.go` are mutually exclusive inside a
|
|
37
|
+
module. Service and CQRS can coexist across different modules in the same
|
|
38
|
+
modular monolith. The root package contains only `composition.go`; other code
|
|
39
|
+
must import the specific boundary package it belongs to.
|
|
40
|
+
|
|
41
|
+
### Domain and persistence model
|
|
42
|
+
|
|
43
|
+
- `domain/entity.go` owns business state and invariants. It must not import
|
|
44
|
+
Gin, GORM, SQL drivers, Redis, HTTP, or environment configuration.
|
|
45
|
+
- `adapters/outbound/postgres/model.go` owns persistence-only fields and
|
|
46
|
+
explicit `toDomain`/`fromDomain` mapping. A database column or GORM tag must
|
|
47
|
+
not silently become part of the business API.
|
|
48
|
+
- A domain with more than one table should add one persistence model per table
|
|
49
|
+
in the outbound adapter, with the corresponding domain type or value object
|
|
50
|
+
kept in `domain/`.
|
|
38
51
|
- `TableName()` returns a schema-qualified name (`order_svc.orders`, not
|
|
39
52
|
`orders`) — every domain gets its own Postgres schema, created by its own
|
|
40
53
|
migration (`CREATE SCHEMA IF NOT EXISTS`) and by `cmd/api/wiring.go` before
|
|
41
|
-
the development table bootstrap runs
|
|
42
|
-
the
|
|
43
|
-
|
|
44
|
-
raw SQL `JOIN` from reaching into a domain it doesn't own without at least
|
|
45
|
-
naming the schema it's crossing into.
|
|
54
|
+
the development table bootstrap runs. A cross-domain FK is still fine — see
|
|
55
|
+
the FK rules below — this only stops one domain's table from silently
|
|
56
|
+
colliding with another's.
|
|
46
57
|
|
|
47
58
|
## Layer Conventions
|
|
48
59
|
|
|
49
60
|
### Handler
|
|
50
|
-
- Owns routing, request binding, and calling the service
|
|
61
|
+
- Owns routing, request binding, and calling the application service or
|
|
62
|
+
command/query port
|
|
51
63
|
- No business logic — `c.Error(err); return` on failure, nothing more
|
|
52
64
|
- Reads pagination via `pagination.Parse(c)`, wraps list responses with
|
|
53
65
|
`p.Response(out)`
|
|
@@ -64,22 +76,26 @@ selects the matching port for each route. They may share the same
|
|
|
64
76
|
repository/database in this modular monolith — separate storage or event
|
|
65
77
|
sourcing is not implied.
|
|
66
78
|
|
|
67
|
-
The generated
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
the
|
|
79
|
+
The generated CQRS application has separate `CommandPort` and `QueryPort`
|
|
80
|
+
interfaces and separate handlers. Its HTTP adapter receives both through
|
|
81
|
+
`NewHandler`; there is no generated `application/service.go` facade to hide the
|
|
82
|
+
split. New feature code should depend on the narrower port it actually needs.
|
|
83
|
+
`generate module --defaults` uses the project default; choose the single service
|
|
84
|
+
path for simple CRUD and CQRS only when the feature earns the extra boundary.
|
|
72
85
|
|
|
73
86
|
### Service
|
|
74
87
|
- Contains the business logic, knows nothing about HTTP
|
|
75
|
-
-
|
|
76
|
-
this is what makes it mockable in `
|
|
88
|
+
- Depends on consumer-owned interfaces from `ports/` for what it needs from
|
|
89
|
+
the data layer — this is what makes it mockable in `application/*_test.go`
|
|
90
|
+
without a DB
|
|
77
91
|
- Generates the ID itself via `id.New()` before calling `repository.Create`
|
|
78
|
-
-
|
|
79
|
-
|
|
92
|
+
- Returns domain errors and never constructs an HTTP response. The outbound
|
|
93
|
+
adapter maps driver errors with `dberr.IsDuplicate` /
|
|
94
|
+
`dberr.IsForeignKey` before they reach the application boundary.
|
|
80
95
|
|
|
81
|
-
### Repository
|
|
82
|
-
-
|
|
96
|
+
### Repository adapter
|
|
97
|
+
- `adapters/outbound/postgres/repository.go` is the only module code that talks
|
|
98
|
+
to GORM
|
|
83
99
|
- Every method takes `ctx context.Context` first, so a cancelled request
|
|
84
100
|
cancels the query
|
|
85
101
|
- Every query starts from `tx.From(ctx, r.db).WithContext(ctx)`, never from
|
|
@@ -95,16 +111,18 @@ the extra boundary.
|
|
|
95
111
|
and GORM can misinterpret a bare struct arg
|
|
96
112
|
|
|
97
113
|
### DTOs
|
|
98
|
-
- `
|
|
99
|
-
|
|
100
|
-
|
|
114
|
+
- `application/dto.go` owns transport-neutral use-case inputs and output read
|
|
115
|
+
models. `adapters/inbound/http/dto.go` owns JSON names, binding/validation
|
|
116
|
+
tags, and explicit request/response mapping at the HTTP boundary.
|
|
117
|
+
- When `generate method` adds a POST input, it adds both the application input
|
|
118
|
+
and an inbound HTTP DTO/mapping seam. Fill both deliberately; do not bind
|
|
119
|
+
Gin JSON directly into an application type.
|
|
101
120
|
|
|
102
121
|
### Error Catalog
|
|
103
|
-
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
`apperror.NewNotFound()` directly from a handler
|
|
122
|
+
- Domain sentinels live in `domain/errors.go`; they are values used with
|
|
123
|
+
`errors.Is`, not HTTP errors or mutable request state.
|
|
124
|
+
- The inbound adapter maps those sentinels to domain-specific HTTP codes
|
|
125
|
+
(`ORDER_NOT_FOUND`); domain/application code never constructs an HTTP error.
|
|
108
126
|
|
|
109
127
|
## Optimistic Locking
|
|
110
128
|
|
|
@@ -123,13 +141,13 @@ and `response` echoes it back: the client round-trips the value, and an
|
|
|
123
141
|
update that arrives without one is rejected rather than treated as a blind
|
|
124
142
|
overwrite.
|
|
125
143
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
`
|
|
132
|
-
|
|
144
|
+
The inbound adapter maps `ErrStaleVersion` to the domain's `<DOMAIN>_STALE`
|
|
145
|
+
HTTP 409 response — a lost update is a client problem to retry, not a 500. A
|
|
146
|
+
minimal module has the same `Repository.Update` guard waiting for its first
|
|
147
|
+
update use case. Keep the chain intact when you add one by hand:
|
|
148
|
+
`errors.Is(err, domain.ErrStaleVersion)` → adapter HTTP mapping, and never set
|
|
149
|
+
`Version` on the persistence model yourself — `Repository.Update` owns the
|
|
150
|
+
bump.
|
|
133
151
|
|
|
134
152
|
## Domains With a Foreign Key (Relations) — 3 Rules
|
|
135
153
|
|
|
@@ -143,18 +161,19 @@ if this project has one):
|
|
|
143
161
|
2. **Declare the FK constraint in migration SQL**
|
|
144
162
|
(`REFERENCES ... ON DELETE ...`), not a GORM tag — the development bootstrap
|
|
145
163
|
doesn't create the constraint, which would make dev and prod schemas diverge.
|
|
146
|
-
3. **Map the FK error to the right status**
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
catch the error — a pre-check
|
|
164
|
+
3. **Map the FK error to the right status** in the outbound adapter with
|
|
165
|
+
`dberr.IsForeignKey`, then let the inbound adapter render it — inserting a
|
|
166
|
+
reference to a missing parent, or deleting a parent that still has children,
|
|
167
|
+
is a client error (409/422), not a 500. Don't pre-check existence before
|
|
168
|
+
insert; let the DB enforce it atomically and catch the error — a pre-check
|
|
169
|
+
has a TOCTOU race.
|
|
151
170
|
|
|
152
171
|
## Calling Another Domain's Logic
|
|
153
172
|
|
|
154
173
|
For behavior, not just a data reference (e.g. `order` needs `user`'s email
|
|
155
|
-
to put on a receipt) — the caller's `service.go
|
|
156
|
-
|
|
157
|
-
`repository` interface.
|
|
174
|
+
to put on a receipt) — the caller's application boundary (`application/service.go`,
|
|
175
|
+
`application/commands.go`, or `application/queries.go`) declares its own narrow interface for exactly
|
|
176
|
+
what it needs, the same way it already declares a `repository` interface.
|
|
158
177
|
|
|
159
178
|
**The interface speaks in the caller's own terms.** No domain package ever
|
|
160
179
|
imports another domain package — `golangci-lint`'s `depguard` rules enforce
|
|
@@ -162,15 +181,16 @@ this, and it is the same rule that makes rule 1 above work. So the interface
|
|
|
162
181
|
names primitives, or types the caller owns, never `user.Response`:
|
|
163
182
|
|
|
164
183
|
```go
|
|
165
|
-
// order/service.go
|
|
184
|
+
// order/application/service.go
|
|
166
185
|
type userLookup interface {
|
|
167
186
|
EmailOf(ctx context.Context, id uuid.UUID) (string, error)
|
|
168
187
|
}
|
|
169
188
|
```
|
|
170
189
|
|
|
171
|
-
The feature keeps its own repository/
|
|
172
|
-
`composition.go`; `cmd/api/wiring.go` supplies the DB and
|
|
173
|
-
|
|
190
|
+
The feature keeps its own repository/application/handler composition in
|
|
191
|
+
`composition.go`; `cmd/api/wiring.go` supplies the DB and registers routes.
|
|
192
|
+
Process-level adapters for cross-feature capabilities live in
|
|
193
|
+
`internal/composition/`. For example, auth and RBAC are composed locally and
|
|
174
194
|
the root passes only the role feature's public capabilities:
|
|
175
195
|
|
|
176
196
|
```go
|
|
@@ -181,12 +201,13 @@ user.NewHandlerFromDB(
|
|
|
181
201
|
```
|
|
182
202
|
|
|
183
203
|
For ordinary cross-domain behaviour, the caller still declares a narrow
|
|
184
|
-
interface next to its application
|
|
185
|
-
port, while
|
|
186
|
-
callee's repository or handler. A func-to-interface shim is
|
|
204
|
+
interface next to its application boundary. Its local composition accepts that
|
|
205
|
+
port, while `internal/composition/` supplies an adapter; the process root never
|
|
206
|
+
reaches into the callee's repository or handler. A func-to-interface shim is
|
|
207
|
+
one small option:
|
|
187
208
|
|
|
188
209
|
```go
|
|
189
|
-
// order/service.go
|
|
210
|
+
// order/application/service.go
|
|
190
211
|
type UserLookupFunc func(ctx context.Context, id uuid.UUID) (string, error)
|
|
191
212
|
|
|
192
213
|
func (f UserLookupFunc) EmailOf(ctx context.Context, id uuid.UUID) (string, error) {
|
|
@@ -201,6 +222,10 @@ can never silently ripple into `order`. The adapter is also the honest place
|
|
|
201
222
|
to notice you are reaching for something that should have been the caller's
|
|
202
223
|
own data.
|
|
203
224
|
|
|
225
|
+
Cross-feature integration tests belong under `internal/integration`, where
|
|
226
|
+
they may compose real feature services and adapters. A feature's own unit and
|
|
227
|
+
repository tests stay inside its package and must not import another feature.
|
|
228
|
+
|
|
204
229
|
- **One direction only.** If `user` would need to call back into `order`,
|
|
205
230
|
don't wire it both ways — either the two belong in one domain, or the
|
|
206
231
|
callback needs an event/queue, not a direct call.
|
|
@@ -216,31 +241,33 @@ own data.
|
|
|
216
241
|
go-scaffold generate method <domain> <name> --type <get|post|put|patch|delete> [--get-mode all|one] [--field <name>]
|
|
217
242
|
```
|
|
218
243
|
|
|
219
|
-
Patches `handler.go
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
lookup) in place, at the
|
|
223
|
-
each file. **Don't delete
|
|
224
|
-
`generate method` call inserts. The
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
placeholder fields.
|
|
244
|
+
Patches `adapters/inbound/http/handler.go`, its request DTO seam, and the application boundary (or
|
|
245
|
+
`application/commands.go`/`application/queries.go` for a CQRS module; plus
|
|
246
|
+
`ports/repository.go` and the outbound repository adapter/test seam for a
|
|
247
|
+
`get --get-mode one --field` lookup) in place, at the
|
|
248
|
+
`// go-scaffold:*` marker comments near the end of each file. **Don't delete
|
|
249
|
+
those markers** — they're where the next `generate method` call inserts. The
|
|
250
|
+
method body is always left as a compiling `TODO` rather than guessing at
|
|
251
|
+
business logic — same spirit as `generate module`'s placeholder fields.
|
|
228
252
|
|
|
229
253
|
## Testing Conventions
|
|
230
254
|
|
|
231
255
|
- Unit and integration tests live in the same directory as the code under
|
|
232
256
|
test (Go convention) — never a separate `test/` folder. `test/` is only
|
|
233
257
|
for e2e black-box suites or fixtures.
|
|
234
|
-
- `service_test.go` uses a function-backed repository stub so each
|
|
235
|
-
method has independent behavior and argument assertions.
|
|
236
|
-
- `handler_test.go`
|
|
237
|
-
|
|
258
|
+
- `application/service_test.go` uses a function-backed repository stub so each
|
|
259
|
+
dependency method has independent behavior and argument assertions.
|
|
260
|
+
- `adapters/inbound/http/handler_test.go` checks route composition; focused
|
|
261
|
+
HTTP tests can inject an application port and never need Postgres.
|
|
262
|
+
- `adapters/outbound/postgres/repository_test.go` runs against a migrated
|
|
263
|
+
Postgres database inside a
|
|
238
264
|
transaction that's rolled back after each test. Local runs may skip when
|
|
239
265
|
`TEST_DB_DSN` is unset; CI sets `REQUIRE_TEST_DB=true` so it must run.
|
|
240
266
|
|
|
241
267
|
## Docs Maintenance
|
|
242
268
|
|
|
243
|
-
- This document describes the
|
|
244
|
-
|
|
245
|
-
-
|
|
246
|
-
|
|
269
|
+
- This document describes the conventions used by generated domains and the
|
|
270
|
+
canonical split emitted by `go-scaffold`.
|
|
271
|
+
- When a shared generator contract changes, update the templates and add a
|
|
272
|
+
regression test. Generated projects with hand-edited docs are left alone by
|
|
273
|
+
incremental feature refreshes and must be updated by their owners.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Tech Stack: {{projectName}}
|
|
2
2
|
|
|
3
|
-
> Status: generated by `@nakedev/go-scaffold
|
|
3
|
+
> Status: generated by `@nakedev/go-scaffold`; refreshes as optional features are added
|
|
4
4
|
|
|
5
5
|
## Stack Overview
|
|
6
6
|
|
|
@@ -15,6 +15,9 @@
|
|
|
15
15
|
| Logging | `log/slog`, JSON handler |
|
|
16
16
|
| Migrations | [golang-migrate](https://github.com/golang-migrate/migrate) |
|
|
17
17
|
| Testing | stdlib `testing`, real Postgres for integration tests |
|
|
18
|
+
| Authentication | {{#if auth}}JWT access/refresh sessions, password recovery, email verification, provider login, and MFA{{else}}optional via `go-scaffold add auth`{{/if}} |
|
|
19
|
+
| Authorization | {{#if rbac}}RBAC role/permission middleware{{else}}optional RBAC via `go-scaffold add rbac`{{/if}} |
|
|
20
|
+
| Background jobs | {{#if worker}}{{queue}} queue backend with SMTP mail and `cmd/worker`{{else}}optional via `go-scaffold add worker`{{/if}} |
|
|
18
21
|
|
|
19
22
|
## Scaffolded Capabilities
|
|
20
23
|
|
|
@@ -24,6 +27,9 @@
|
|
|
24
27
|
- Docker Compose (local Postgres): `{{#if docker}}enabled{{else}}disabled{{/if}}`
|
|
25
28
|
- OpenAPI docs (`docs/openapi.yaml`, working-copy only — never served over HTTP): `{{#if openapiDocs}}enabled{{else}}disabled{{/if}}`
|
|
26
29
|
- Metrics + tracing (Prometheus `/metrics`, OpenTelemetry for Gin + GORM, `cmd/api` only): `{{#if observability}}enabled{{else}}disabled{{/if}}`
|
|
30
|
+
- Authentication and browser OAuth: `{{#if auth}}enabled{{else}}disabled{{/if}}`
|
|
31
|
+
- Background worker and mail: `{{#if worker}}enabled{{else}}disabled{{/if}}`
|
|
32
|
+
- RBAC roles, permissions, and authorization: `{{#if rbac}}enabled{{else}}disabled{{/if}}`
|
|
27
33
|
- API route prefix: `{{#if apiPrefix}}/{{apiPrefix}}{{else}}(none){{/if}}`
|
|
28
34
|
- CI (`.github/workflows/ci.yml` — build, vet, gofmt check, golangci-lint, `go test` with a real Postgres service): always enabled
|
|
29
35
|
|
|
@@ -31,8 +37,14 @@
|
|
|
31
37
|
|
|
32
38
|
{{#if docker}}- Local Postgres comes from `docker-compose.yml`
|
|
33
39
|
{{else}}- No Docker scaffolding included — bring your own Postgres and point `DB_DSN` at it
|
|
34
|
-
{{/if}}- `
|
|
40
|
+
{{/if}}{{#if worker}}{{#if (eq queue "river")}}- Run `make river-migrate` before relying on queued jobs in a fresh database;
|
|
41
|
+
run `make river-migrate-test` as well when `REQUIRE_TEST_DB=true` runs the
|
|
42
|
+
River worker round-trip test.{{/if}}{{/if}}
|
|
43
|
+
- `APP_ENV=development` enables the convenience table bootstrap;
|
|
35
44
|
`APP_ENV=production` requires `migrate up` as a deploy step instead
|
|
45
|
+
{{#if worker}}{{#if (eq queue "river")}}- River uses the database/sql driver and polls for jobs instead of receiving
|
|
46
|
+
PostgreSQL LISTEN/NOTIFY wakeups; this is deliberate for a shared GORM pool.
|
|
47
|
+
{{/if}}{{/if}}
|
|
36
48
|
{{#if openapiDocs}}- `docs/openapi.yaml` is hand-written, not generated — update it whenever an
|
|
37
49
|
endpoint or DTO changes
|
|
38
50
|
{{/if}}{{#if observability}}- `OTEL_EXPORTER_OTLP_ENDPOINT` unset (dev default) means tracing exports
|
|
@@ -44,3 +56,6 @@
|
|
|
44
56
|
resolves them to the versions that feature was built against instead of
|
|
45
57
|
whatever happened to be newest that day. The full list is `go.mod`; this
|
|
46
58
|
table only covers what `create` itself brings in
|
|
59
|
+
- Auth, worker, and RBAC are opt-in. Their `add` commands update this snapshot
|
|
60
|
+
when the generated file is untouched; hand-edited docs are left alone and
|
|
61
|
+
require a manual update.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
package httpadapter
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"time"
|
|
5
|
+
|
|
6
|
+
"{{goModule}}/internal/app/{{modulePath}}/application"
|
|
7
|
+
|
|
8
|
+
"github.com/google/uuid"
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
// response is the HTTP representation of the application output. Keep JSON
|
|
12
|
+
// names and protocol-only fields at this inbound boundary.
|
|
13
|
+
type response struct {
|
|
14
|
+
ID uuid.UUID `json:"id"`
|
|
15
|
+
CreatedAt time.Time `json:"created_at"`
|
|
16
|
+
Version int `json:"version"`
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
func toResponse(out application.Response) response {
|
|
20
|
+
return response{ID: out.ID, CreatedAt: out.CreatedAt, Version: out.Version}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Request DTOs belong to the inbound adapter. Keep JSON names, validation
|
|
24
|
+
// tags, and request-to-command mapping here; application inputs stay usable
|
|
25
|
+
// from jobs, tests, and other non-HTTP callers.
|
|
26
|
+
type createInput struct {
|
|
27
|
+
// TODO: mirror the request fields added to application.CreateInput.
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
type updateInput struct {
|
|
31
|
+
Version int `json:"version" binding:"required,min=1"`
|
|
32
|
+
// TODO: mirror the update fields added to application.UpdateInput.
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
func toCreateInput(in createInput) application.CreateInput {
|
|
36
|
+
// TODO: map request fields explicitly as the application contract evolves.
|
|
37
|
+
_ = in
|
|
38
|
+
return application.CreateInput{}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
func toUpdateInput(in updateInput) application.UpdateInput {
|
|
42
|
+
return application.UpdateInput{Version: in.Version}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// go-scaffold:dto
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
package httpadapter
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"time"
|
|
5
|
+
|
|
6
|
+
"{{goModule}}/internal/app/{{modulePath}}/application"
|
|
7
|
+
|
|
8
|
+
"github.com/google/uuid"
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
// response is the HTTP representation of the application output. Keep JSON
|
|
12
|
+
// names and protocol-only fields at this inbound boundary.
|
|
13
|
+
//nolint:unused // generate method uses this seam when the first endpoint is added.
|
|
14
|
+
type response struct {
|
|
15
|
+
ID uuid.UUID `json:"id"`
|
|
16
|
+
CreatedAt time.Time `json:"created_at"`
|
|
17
|
+
Version int `json:"version"`
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
//nolint:unused // generate method uses this seam when the first endpoint is added.
|
|
21
|
+
func toResponse(out application.Response) response {
|
|
22
|
+
return response{ID: out.ID, CreatedAt: out.CreatedAt, Version: out.Version}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// A lean module has no HTTP body endpoints yet. The marker gives
|
|
26
|
+
// `generate method` a stable place to add request DTOs when a POST is earned.
|
|
27
|
+
|
|
28
|
+
// go-scaffold:dto
|