@nakedev/go-scaffold 0.4.0 → 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 +598 -306
- package/dist/commands/auth.js +65 -23
- package/dist/commands/check.js +281 -0
- package/dist/commands/config.js +50 -0
- package/dist/commands/create.js +33 -2
- package/dist/commands/generate.js +29 -3
- package/dist/commands/method.js +74 -63
- package/dist/commands/migration.js +2 -2
- package/dist/commands/observability.js +4 -53
- package/dist/commands/rbac.js +21 -10
- package/dist/commands/undo.js +11 -3
- package/dist/commands/worker.js +15 -5
- package/dist/index.js +198 -59
- package/dist/prompts/auth-wizard.js +40 -6
- package/dist/prompts/create-wizard.js +42 -1
- package/dist/prompts/generate-wizard.js +89 -9
- package/dist/templates/auth-manifest.js +50 -19
- package/dist/templates/create-manifest.js +8 -0
- package/dist/templates/module-manifest.js +84 -26
- package/dist/templates/rbac-manifest.js +16 -11
- package/dist/templates/worker-manifest.js +4 -1
- package/dist/types.js +8 -0
- package/dist/utils/auth-patcher.js +124 -33
- package/dist/utils/config.js +167 -4
- package/dist/utils/docs-patcher.js +68 -0
- package/dist/utils/hexagonal-method-patcher.js +334 -0
- package/dist/utils/main-patcher.js +32 -30
- package/dist/utils/marker-patch.js +7 -1
- package/dist/utils/module-location.js +17 -11
- package/dist/utils/module-profile.js +32 -0
- package/dist/utils/platform-patcher.js +56 -7
- package/dist/utils/rbac-patcher.js +89 -210
- package/package.json +7 -2
- package/templates/add/auth/cmd/seed/main.go.hbs +15 -3
- package/templates/add/auth/docs/login.yaml.hbs +11 -1
- package/templates/add/auth/docs/mfa-verify.yaml.hbs +19 -0
- package/templates/add/auth/docs/provider-exchange.yaml.hbs +40 -0
- package/templates/add/auth/docs/provider-login.yaml.hbs +31 -0
- package/templates/add/auth/docs/refresh.yaml.hbs +7 -0
- package/templates/add/auth/docs/register.yaml.hbs +7 -0
- package/templates/add/auth/docs/reset-password.yaml.hbs +1 -1
- package/templates/add/auth/docs/schemas.yaml.hbs +59 -1
- package/templates/add/auth/docs/users-me-mfa-confirm.yaml.hbs +19 -0
- package/templates/add/auth/docs/users-me-mfa-disable.yaml.hbs +15 -0
- package/templates/add/auth/docs/users-me-mfa-setup.yaml.hbs +14 -0
- package/templates/add/auth/docs/users-me-mfa.yaml.hbs +12 -0
- package/templates/add/auth/internal/app/user/adapters/inbound/http/browser_policy.go.hbs +98 -0
- package/templates/add/auth/internal/app/user/adapters/inbound/http/dto.go.hbs +159 -0
- package/templates/add/auth/internal/app/user/adapters/inbound/http/handler.go.hbs +228 -0
- package/templates/add/auth/internal/app/user/adapters/inbound/http/handler_local.go.hbs +76 -0
- package/templates/add/auth/internal/app/user/adapters/inbound/http/handler_mfa.go.hbs +83 -0
- package/templates/add/auth/internal/app/user/adapters/inbound/http/handler_oauth.go.hbs +70 -0
- package/templates/add/auth/internal/app/user/adapters/inbound/http/handler_recovery.go.hbs +49 -0
- package/templates/add/auth/internal/app/user/adapters/inbound/http/handler_test.go.hbs +311 -0
- package/templates/add/auth/internal/app/user/adapters/inbound/http/handler_user.go.hbs +41 -0
- package/templates/add/auth/internal/app/user/adapters/inbound/http/session_cookie.go.hbs +35 -0
- 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/adapters/outbound/postgres/mfa_store.go.hbs +129 -0
- package/templates/add/auth/internal/app/user/adapters/outbound/postgres/mfa_store_test.go.hbs +174 -0
- 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/adapters/outbound/postgres/tokenstore_pg.go.hbs +213 -0
- package/templates/add/auth/internal/app/user/adapters/outbound/postgres/tokenstore_pg_test.go.hbs +103 -0
- package/templates/add/auth/internal/app/user/adapters/outbound/postgres/tokenstore_recovery.go.hbs +84 -0
- package/templates/add/auth/internal/app/user/adapters/outbound/redis/tokenstore.go.hbs +228 -0
- package/templates/add/auth/internal/app/user/adapters/outbound/redis/tokenstore_test.go.hbs +196 -0
- 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/application/external_login.go.hbs +198 -0
- package/templates/add/auth/internal/app/user/application/jwt.go.hbs +58 -0
- package/templates/add/auth/internal/app/user/application/local_auth.go.hbs +96 -0
- package/templates/add/auth/internal/app/user/application/mfa_service.go.hbs +449 -0
- package/templates/add/auth/internal/app/user/application/mfa_service_test.go.hbs +200 -0
- package/templates/add/auth/internal/app/user/application/oauth.go.hbs +132 -0
- package/templates/add/auth/internal/app/user/application/provider_test.go.hbs +285 -0
- package/templates/add/auth/internal/app/user/application/recovery.go.hbs +82 -0
- package/templates/add/auth/internal/app/user/application/recovery_service.go.hbs +112 -0
- package/templates/add/auth/internal/app/user/application/service.go.hbs +145 -0
- package/templates/add/auth/internal/app/user/application/service_test.go.hbs +891 -0
- package/templates/add/auth/internal/app/user/application/sessions.go.hbs +99 -0
- package/templates/add/auth/internal/app/user/application/tokenstore_ports.go.hbs +14 -0
- package/templates/add/auth/internal/app/user/application/user_query.go.hbs +65 -0
- package/templates/add/auth/internal/app/user/composition.go.hbs +168 -0
- 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/internal/platform/authprovider/google/google.go.hbs +389 -0
- package/templates/add/auth/internal/platform/authprovider/google/google_test.go.hbs +312 -0
- package/templates/add/auth/migrations/create_auth_tokens.up.sql.hbs +10 -5
- package/templates/add/auth/migrations/create_identities.up.sql.hbs +1 -1
- package/templates/add/auth/migrations/create_login_throttle.up.sql.hbs +1 -1
- package/templates/add/auth/migrations/create_mfa.down.sql.hbs +3 -0
- package/templates/add/auth/migrations/create_mfa.up.sql.hbs +29 -0
- package/templates/add/auth/migrations/create_users.up.sql.hbs +4 -3
- 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 +48 -0
- 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 +358 -121
- package/templates/create/base/.env.example.hbs +0 -1
- package/templates/create/base/.golangci.yml.hbs +2 -2
- package/templates/create/base/AGENTS.md.hbs +279 -67
- package/templates/create/base/Makefile.hbs +2 -1
- package/templates/create/base/README.md.hbs +115 -32
- package/templates/create/base/cmd/api/wiring.go.hbs +13 -9
- package/templates/create/base/internal/composition/doc.go.hbs +7 -0
- package/templates/create/base/internal/platform/database/database.go.hbs +3 -3
- package/templates/create/base/internal/shared/apperror/apperror.go.hbs +15 -2
- package/templates/create/base/internal/shared/config/config.go.hbs +0 -8
- package/templates/create/base/internal/shared/middleware/cors_test.go.hbs +40 -0
- package/templates/create/base/internal/shared/middleware/error.go.hbs +15 -5
- package/templates/create/features/docs/architecture.md.hbs +92 -32
- package/templates/create/features/docs/patterns.md.hbs +137 -91
- package/templates/create/features/docs/techstack.md.hbs +18 -3
- 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 -357
- package/templates/add/auth/docs/google-callback.yaml.hbs +0 -22
- package/templates/add/auth/docs/google-login.yaml.hbs +0 -7
- package/templates/add/auth/internal/app/user/dto.go.hbs +0 -77
- package/templates/add/auth/internal/app/user/errors.go.hbs +0 -43
- package/templates/add/auth/internal/app/user/handler.go.hbs +0 -276
- package/templates/add/auth/internal/app/user/jwt.go.hbs +0 -108
- package/templates/add/auth/internal/app/user/model/authtoken.go.hbs +0 -39
- package/templates/add/auth/internal/app/user/model/identity.go.hbs +0 -31
- package/templates/add/auth/internal/app/user/model/loginthrottle.go.hbs +0 -26
- package/templates/add/auth/internal/app/user/model/user.go.hbs +0 -30
- package/templates/add/auth/internal/app/user/repository.go.hbs +0 -137
- package/templates/add/auth/internal/app/user/service.go.hbs +0 -531
- package/templates/add/auth/internal/app/user/service_test.go.hbs +0 -316
- package/templates/add/auth/internal/app/user/tokenstore.go.hbs +0 -30
- package/templates/add/auth/internal/app/user/tokenstore_pg.go.hbs +0 -144
- package/templates/add/auth/internal/app/user/tokenstore_redis.go.hbs +0 -147
- 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/dto.go.hbs +0 -36
- package/templates/generate/module/errors.go.hbs +0 -33
- package/templates/generate/module/handler.go.hbs +0 -134
- package/templates/generate/module/handler_test.go.hbs +0 -174
- package/templates/generate/module/minimal/dto.go.hbs +0 -28
- package/templates/generate/module/minimal/handler.go.hbs +0 -48
- package/templates/generate/module/minimal/handler_test.go.hbs +0 -10
- package/templates/generate/module/minimal/service.go.hbs +0 -45
- 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/repository.go.hbs +0 -103
- package/templates/generate/module/service.go.hbs +0 -108
- package/templates/generate/module/service_test.go.hbs +0 -161
|
@@ -1,80 +1,292 @@
|
|
|
1
1
|
# Agent Guidance: {{projectName}}
|
|
2
2
|
|
|
3
|
-
This project was scaffolded by `@nakedev/go-scaffold
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
This project was scaffolded by `@nakedev/go-scaffold`. It is a modular
|
|
4
|
+
monolith: each business capability lives in its own feature package, while
|
|
5
|
+
`cmd/api/wiring.go` is the process composition root. This file is the working
|
|
6
|
+
contract for AI coding agents and humans; keep generated structure and the
|
|
7
|
+
runtime safety rules below intact.
|
|
6
8
|
|
|
7
|
-
##
|
|
9
|
+
## Start every task here
|
|
8
10
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
11
|
+
1. Read the relevant code, `docs/architect/`, tests, and this file before
|
|
12
|
+
editing. Search for an existing module or port before creating one.
|
|
13
|
+
2. Check `git status --short --branch`. Work on a dedicated, non-protected
|
|
14
|
+
branch and preserve unrelated changes. Never edit a protected branch just
|
|
15
|
+
to make a quick fix.
|
|
16
|
+
3. Classify the request: generated surface, business behavior, schema, auth /
|
|
17
|
+
security, infrastructure, or documentation. Ask the owner before changing
|
|
18
|
+
schema/migrations, authentication or security behavior, architecture,
|
|
19
|
+
dependencies, or deployment configuration when the project workflow
|
|
20
|
+
requires approval.
|
|
21
|
+
4. Make the smallest complete change, add a focused test, and report exact
|
|
22
|
+
commands and output. Do not claim a check passed if it was skipped or
|
|
23
|
+
failed.
|
|
24
|
+
|
|
25
|
+
## Architecture contract
|
|
26
|
+
|
|
27
|
+
This scaffold combines modular boundaries with Hexagonal Architecture,
|
|
28
|
+
domain-oriented design, and an optional CQRS style. They are compatible:
|
|
29
|
+
apply them inside each feature instead of creating a second architecture or
|
|
30
|
+
an empty layer for every noun.
|
|
31
|
+
|
|
32
|
+
- **Inbound adapter:** `adapters/inbound/http/dto.go` owns JSON/binding request
|
|
33
|
+
DTOs, response DTOs, and mapping at the HTTP boundary; `handler.go` parses
|
|
34
|
+
HTTP and calls the relevant application service or command/query port.
|
|
35
|
+
- **Application boundary:** `application/service.go` (or
|
|
36
|
+
`application/commands.go`/`application/queries.go` when
|
|
37
|
+
the module uses `--cqrs`) orchestrates use cases. It owns
|
|
38
|
+
workflow decisions and depends on narrow consumer-owned ports.
|
|
39
|
+
- **Domain:** entities, value rules, and invariants stay independent of Gin,
|
|
40
|
+
GORM, Redis, HTTP status codes, and environment variables.
|
|
41
|
+
- **Outbound adapter:** `adapters/outbound/postgres/repository.go`, token
|
|
42
|
+
stores, mail/queue clients,
|
|
43
|
+
and other platform integrations translate a port into persistence or I/O.
|
|
44
|
+
- **Composition:** the feature-local `composition.go` constructs its
|
|
45
|
+
repository, application boundary, and handler. `internal/composition/` owns
|
|
46
|
+
process-level cross-feature adapters. `cmd/api/wiring.go` selects shared
|
|
47
|
+
infrastructure, supplies those adapters and security dependencies, and
|
|
48
|
+
registers feature routes; it does not contain cross-feature adapter logic.
|
|
49
|
+
|
|
50
|
+
The project defaults new modules to `{{defaultApplicationStyle}}` application
|
|
51
|
+
handling and a `{{defaultModuleSurface}}` surface. The `generate module` wizard
|
|
52
|
+
starts there through the named Lean/CRUD/CQRS profiles; `Advanced` exposes the
|
|
53
|
+
two axes for unusual combinations. `--profile` is the clear per-module
|
|
54
|
+
override, while `--cqrs`/`--full` remain supported for existing scripts, and
|
|
55
|
+
`go-scaffold config` changes defaults for future modules only.
|
|
56
|
+
|
|
57
|
+
Use CQRS when a feature has genuinely different command and query models,
|
|
58
|
+
consistency needs, or scaling/read-shape pressure: commands mutate state and
|
|
59
|
+
queries read state. For simple CRUD, one application service and one port are
|
|
60
|
+
clearer. Do not introduce a broker, a second database, or a command/query
|
|
61
|
+
folder only to satisfy a label.
|
|
62
|
+
|
|
63
|
+
Keep feature boundaries real. Do not import another feature's private model,
|
|
64
|
+
repository, or handler. Expose a small public application port, call a
|
|
65
|
+
documented feature API, or use an explicit event when cross-feature behavior
|
|
66
|
+
is required. Put synchronous adapters in `internal/composition/`, import only
|
|
67
|
+
the other feature's public package root, and keep one-way dependencies. Keep
|
|
68
|
+
dependency direction flowing inward:
|
|
69
|
+
|
|
70
|
+
```text
|
|
71
|
+
handler -> application/use case -> domain + ports -> adapters
|
|
72
|
+
^ |
|
|
73
|
+
+--- composition -+
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
The generated CRUD shape is an intentionally incremental starter. It gives
|
|
77
|
+
each module a testable application/inbound seam and a feature-local
|
|
78
|
+
composition root; it does not decide the final aggregate, fields, invariants,
|
|
79
|
+
or split between commands and queries for the product.
|
|
80
|
+
|
|
81
|
+
For a CQRS module, `application/commands.go` and
|
|
82
|
+
`application/queries.go` are the primary application boundary and
|
|
83
|
+
`composition.go` passes separate command and query handlers to the inbound
|
|
84
|
+
adapter's `NewHandler`. A CQRS module must not also grow an
|
|
85
|
+
`application/service.go` facade. Service and CQRS may coexist across modules;
|
|
86
|
+
they are mutually exclusive inside one module.
|
|
87
|
+
|
|
88
|
+
Auth and RBAC are security-sensitive modules, but they use the same canonical
|
|
89
|
+
split boundary as every other feature. Auth owns user application use cases
|
|
90
|
+
and ports; RBAC owns role policy and authorization adapters. Their public
|
|
91
|
+
module roots compose those pieces, and one canonical implementation tree is
|
|
92
|
+
generated.
|
|
93
|
+
|
|
94
|
+
## Hard rule: use the CLI for generated surface
|
|
95
|
+
|
|
96
|
+
If you are creating a **new domain** (`internal/app/<name>/` with its own
|
|
97
|
+
domain/application/ports/adapters boundary), or adding a **new endpoint** to
|
|
98
|
+
an existing domain, stop and run the CLI:
|
|
12
99
|
|
|
13
100
|
```bash
|
|
14
101
|
go-scaffold generate module <name> --defaults
|
|
15
102
|
go-scaffold generate method <module> <name> --type <get|post|put|patch|delete>
|
|
16
103
|
```
|
|
17
104
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
`--
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
-
|
|
35
|
-
|
|
36
|
-
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
-
|
|
53
|
-
|
|
54
|
-
|
|
105
|
+
Pass every value as a flag in CI. In an interactive shell, omitted values use
|
|
106
|
+
the project defaults as the initial wizard choices. In a non-interactive
|
|
107
|
+
shell, omitted values prompt and the command exits without writing.
|
|
108
|
+
`--defaults` uses the recorded project defaults and no auth; use `--profile`
|
|
109
|
+
for a named per-module preset, or add `--full`, `--cqrs`, `--auth`, and
|
|
110
|
+
`--permission <code>` deliberately.
|
|
111
|
+
`--get-mode`
|
|
112
|
+
is required for `--type get`, and `undo module` needs `-y`. For `add auth`,
|
|
113
|
+
choose the backing store and browser topology with `--defaults` or explicit
|
|
114
|
+
flags (`--store`, `--browser-topology`) as well as the confirmation flag.
|
|
115
|
+
For `add worker`, choose the backing queue with `--defaults` or an explicit
|
|
116
|
+
flag.
|
|
117
|
+
|
|
118
|
+
Recognize indirect requests as generation work:
|
|
119
|
+
|
|
120
|
+
- “add a products feature” → `generate module products --defaults`
|
|
121
|
+
- “let admins approve orders” → `generate method orders approve --type patch`
|
|
122
|
+
- “list overdue invoices” → `generate method invoices findOverdue --type get --get-mode all`
|
|
123
|
+
- “look up a user by email” → `generate method users findByEmail --type get --get-mode one --field email`
|
|
124
|
+
|
|
125
|
+
Run `go-scaffold generate module --help` or `generate method --help` when the
|
|
126
|
+
desired shape is unclear. Do not hand-edit generated route markers to add a
|
|
127
|
+
new endpoint; the CLI keeps the inbound adapter, application port, test seam,
|
|
128
|
+
and OpenAPI/migration patching in sync.
|
|
129
|
+
|
|
130
|
+
## What the CLI does not decide
|
|
131
|
+
|
|
132
|
+
These remain deliberate engineering work:
|
|
133
|
+
|
|
134
|
+
- business behavior, invariants, authorization policy, and real DTO/model
|
|
135
|
+
fields;
|
|
136
|
+
- validation beyond the transport shape and all domain-specific rules;
|
|
137
|
+
- foreign keys and relations between domains, including their reviewed SQL
|
|
138
|
+
and `dberr.IsForeignKey` mapping;
|
|
139
|
+
- the business-specific command/query contracts and the public port used for
|
|
140
|
+
cross-feature collaboration;
|
|
141
|
+
- bug fixes, refactors, and changes to an existing method;
|
|
142
|
+
- any work in a repository that lacks `go-scaffold.config.json`.
|
|
143
|
+
|
|
144
|
+
Generated methods are explicit stubs. `generate method` GET-all reuses
|
|
145
|
+
`FindAll` until real filtering is added; POST and DELETE return an internal
|
|
146
|
+
error until implemented; PUT/PATCH intentionally return `501 Not Implemented`
|
|
147
|
+
and must not read or write a record before their behavior is designed. Treat
|
|
148
|
+
the generated OpenAPI TODO contract as incomplete, not as a promise that the
|
|
149
|
+
endpoint is production-ready.
|
|
150
|
+
|
|
151
|
+
## Runtime and schema safety
|
|
152
|
+
|
|
153
|
+
- `APP_ENV` is the single environment gate and accepts only `development` or
|
|
154
|
+
`production`. Do not add a parallel flag such as `AUTO_MIGRATE`.
|
|
155
|
+
- Development may use the convenience `db.AutoMigrate(...)` path for the
|
|
156
|
+
models registered by the generator. Production must never bootstrap or
|
|
157
|
+
mutate schema at API startup: apply versioned SQL with
|
|
158
|
+
`make migrate-up`, then let the startup migration-version check fail fast if
|
|
159
|
+
the database is behind or dirty.
|
|
160
|
+
- Any schema change is a reviewed pair of versioned files in `migrations/`.
|
|
161
|
+
Use `go-scaffold generate migration <name>` for changes outside a new
|
|
162
|
+
module, write both directions, and test them against a disposable database.
|
|
163
|
+
- Keep `*sql.DB`, Redis, queue, mail, and other external resources owned by
|
|
164
|
+
their composition root. Close them on every exit path and preserve the
|
|
165
|
+
signal-driven graceful shutdown; do not call `os.Exit` from a goroutine or
|
|
166
|
+
hide a startup error.
|
|
167
|
+
- Keep liveness and readiness semantics intact. Readiness may check external
|
|
168
|
+
dependencies; liveness must not turn a transient dependency failure into a
|
|
169
|
+
process restart.
|
|
170
|
+
|
|
171
|
+
## Auth and error boundaries
|
|
172
|
+
|
|
173
|
+
Treat auth and security changes as high-risk and get the required owner
|
|
174
|
+
approval before changing their contract.
|
|
175
|
+
|
|
176
|
+
- Never log passwords, raw access/refresh/reset/verification tokens, JWT
|
|
177
|
+
secrets, OAuth client secrets, or authorization headers.
|
|
178
|
+
- Refresh rotation is an atomic consume: Postgres uses one SQL unit and Redis
|
|
179
|
+
uses one Lua operation, including active-session cleanup and the reuse
|
|
180
|
+
tombstone. Its inactivity expiry may move on rotation, but its persisted
|
|
181
|
+
absolute expiry never moves. Never replace it with a read-then-delete
|
|
182
|
+
sequence.
|
|
183
|
+
- Password reset and email verification consume a one-time token in the same
|
|
184
|
+
retry-safe transaction as the user/identity update. A failed post-commit
|
|
185
|
+
session revocation must not make a successful reset impossible to retry.
|
|
186
|
+
- The generated auth implementation is canonical: use cases live in
|
|
187
|
+
`application/`, domain rules in `domain/`, contracts in `ports/`, and
|
|
188
|
+
provider/HTTP/Postgres details in their adapters. Do not import Gin, provider
|
|
189
|
+
SDKs, or persistence models into new application use cases; keep the
|
|
190
|
+
token/OAuth/MFA tests as the security gate.
|
|
191
|
+
- Browser provider exchange consumes a one-time server-side transaction bound
|
|
192
|
+
to provider, state, S256 PKCE challenge, and OIDC nonce. Google ID tokens
|
|
193
|
+
must be signature, issuer, audience/azp, time-claim, nonce, and subject
|
|
194
|
+
validated before identity resolution. Token responses use
|
|
195
|
+
`Cache-Control: no-store` and `Pragma: no-cache`; SameSite=None also needs an
|
|
196
|
+
exact allowed Origin guard in addition to CORS.
|
|
197
|
+
|
|
198
|
+
MFA has an operator capability switch and a separate per-user enrollment:
|
|
199
|
+
|
|
200
|
+
- `AUTH_MFA_ENABLED=false` is the default. When enabled, the composition root
|
|
201
|
+
must validate `MFA_ENCRYPTION_KEY` as a base64-encoded 32-byte key before
|
|
202
|
+
serving traffic. Keep the issuer, challenge TTL, TOTP window, and recovery
|
|
203
|
+
code count explicit and within their safety limits.
|
|
204
|
+
- Authenticated users manage enrollment through `GET /users/me/mfa`,
|
|
205
|
+
`POST /users/me/mfa/setup`, `POST /users/me/mfa/confirm`, and
|
|
206
|
+
`POST /users/me/mfa/disable` (the last requires the current TOTP code).
|
|
207
|
+
Setup material is pending-only; recovery codes are returned once.
|
|
208
|
+
- Password login and provider exchange may return a pre-session
|
|
209
|
+
`{ "mfa_required": true, "challenge": "..." }`. Do not issue an access
|
|
210
|
+
token or refresh cookie until `POST /auth/mfa/verify` succeeds. Challenges
|
|
211
|
+
are hashed, short-lived, and one-use; failed verification consumes the
|
|
212
|
+
challenge. Recovery codes are hashed, atomic one-use login factors and are
|
|
213
|
+
not accepted to disable MFA.
|
|
214
|
+
- MFA data is durable in Postgres for both refresh-token store choices. The
|
|
215
|
+
`create_mfa` migration must be applied before production and covered by
|
|
216
|
+
real-store/concurrency tests.
|
|
217
|
+
|
|
218
|
+
Keep auth construction explicit: `NewService(deps Dependencies, cfg
|
|
219
|
+
AuthConfig)`. `Dependencies` owns narrow ports such as `MFA`, `Providers`,
|
|
220
|
+
`RefreshTokens`, `RecoveryTokens`, `Mailer`, `Roles`, and `Clock`; `AuthConfig`
|
|
221
|
+
owns JWT, OAuth, recovery, and `MFASettings` policy. Do not pass the generated
|
|
222
|
+
shared config package into the application service or replace these contracts
|
|
223
|
+
with a positional constructor.
|
|
224
|
+
- Return a stable, generic client error for unexpected failures. Wrap causes
|
|
225
|
+
with `%w` or pass them to `apperror.NewInternal(cause)` so middleware can
|
|
226
|
+
log the server-side cause with `request_id`; production responses must not
|
|
227
|
+
expose that cause or internal field details.
|
|
228
|
+
|
|
229
|
+
## Verification workflow
|
|
230
|
+
|
|
231
|
+
After changing Go templates or generated Go code, run the checks that match
|
|
232
|
+
the change (from the generated project root):
|
|
233
|
+
|
|
234
|
+
```bash
|
|
235
|
+
gofmt -w <changed-go-files>
|
|
236
|
+
go test ./...
|
|
237
|
+
go test -race ./... # required for concurrency/auth/token changes
|
|
238
|
+
go vet ./...
|
|
239
|
+
golangci-lint run
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
For schema/repository/auth adapter work, run the real-store tests with a
|
|
243
|
+
migrated disposable PostgreSQL/Redis instance and `REQUIRE_TEST_DB=true` or
|
|
244
|
+
`REQUIRE_TEST_REDIS=true`; a skipped integration test is not evidence of
|
|
245
|
+
correctness. MFA state still requires PostgreSQL even when refresh tokens use
|
|
246
|
+
Redis. When routes or OpenAPI templates change, inspect the updated
|
|
247
|
+
`docs/openapi.yaml` and run the configured OpenAPI linter.
|
|
248
|
+
|
|
249
|
+
When a River worker is installed, run `make river-migrate-test` in addition to
|
|
250
|
+
`make migrate-up-test`. The queue integration test must enqueue a real job and
|
|
251
|
+
observe the worker handler; a worker process that merely boots is not enough.
|
|
252
|
+
|
|
253
|
+
Only in the `go-scaffold` source repository, run the generator project's
|
|
254
|
+
`pnpm run verify` for CLI/template changes, then generate fresh sample projects
|
|
255
|
+
to inspect the output. A generated Go project does not contain the generator's
|
|
256
|
+
`package.json` or pnpm scripts; from that project run the Go checks above and
|
|
257
|
+
use the installed/local CLI for generation checks.
|
|
258
|
+
|
|
259
|
+
At each checkpoint record: branch and base, files changed, generated output
|
|
260
|
+
shape, tests and their exact result, and any limitation. Before handoff,
|
|
261
|
+
review `git diff --check`, `git diff`, and `git status --short`; leave unrelated
|
|
262
|
+
work untouched.
|
|
55
263
|
|
|
56
264
|
## Command quick reference
|
|
57
265
|
|
|
58
|
-
- `go-scaffold generate module <name> --defaults` —
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
- `go-scaffold
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
-
|
|
80
|
-
`
|
|
266
|
+
- `go-scaffold generate module <name> --defaults` — minimal domain entity,
|
|
267
|
+
errors, ports, application boundary, inbound/outbound adapters,
|
|
268
|
+
feature-local `composition.go`, root route registration, and versioned module
|
|
269
|
+
migrations. Add endpoints with
|
|
270
|
+
`generate method`, use `--profile crud` for a CRUD skeleton, or
|
|
271
|
+
`--profile cqrs` for separate command/query application handlers.
|
|
272
|
+
- `go-scaffold generate method <module> <name> --type <get|post|put|patch|delete> [--get-mode all|one] [--field <name>]` — patches an existing
|
|
273
|
+
module's inbound handler, application service or command/query handler and,
|
|
274
|
+
for a GET-one lookup, its repository port/adapter plus test seam. It never
|
|
275
|
+
overwrites a same-named method.
|
|
276
|
+
- `go-scaffold generate migration <name>` — reserves a timestamped SQL
|
|
277
|
+
migration pair for a reviewed schema change.
|
|
278
|
+
- `go-scaffold config` — interactively change future module defaults;
|
|
279
|
+
`config show` prints the resolved manifest and `config validate` checks it
|
|
280
|
+
without writing.
|
|
281
|
+
- `go-scaffold check` — validates every module's split hexagonal layout,
|
|
282
|
+
forbidden layer dependencies, sibling imports, and the exclusive
|
|
283
|
+
service-versus-CQRS contract.
|
|
284
|
+
- `go-scaffold undo module <name> -y` — removes an unshipped generated module,
|
|
285
|
+
its migration, and its wiring. It refuses once those migrations are
|
|
286
|
+
committed or applied; use a reviewed drop migration for a shipped domain.
|
|
287
|
+
- All routes share the project-wide prefix chosen at `create` time:
|
|
288
|
+
`{{#if apiPrefix}}/{{apiPrefix}}{{else}}(no prefix){{/if}}`. There is no
|
|
289
|
+
per-domain versioning.
|
|
290
|
+
|
|
291
|
+
Read `docs/architect/architecture.md`, `docs/architect/patterns.md`, and
|
|
292
|
+
`docs/architect/techstack.md` for the rationale behind these constraints.
|
|
@@ -68,7 +68,8 @@ tools:
|
|
|
68
68
|
tidy:
|
|
69
69
|
go mod tidy
|
|
70
70
|
|
|
71
|
-
# creates the database itself (not the schema —
|
|
71
|
+
# creates the database itself (not the schema — the app's development path or
|
|
72
|
+
# migrate-up does that).
|
|
72
73
|
# connects to the always-present "postgres" maintenance DB to run CREATE DATABASE,
|
|
73
74
|
# since the target DB may not exist yet. Safe to re-run — skips if it already exists.
|
|
74
75
|
# \gexec only works read from stdin, not through -c, hence the pipe.
|
|
@@ -2,38 +2,75 @@
|
|
|
2
2
|
|
|
3
3
|
Gin + GORM backend, scaffolded by [go-scaffold](https://github.com/nakedev/go-scaffold). Organized by feature (domain), Postgres-backed, schema managed with [golang-migrate](https://github.com/golang-migrate/migrate).
|
|
4
4
|
|
|
5
|
-
This project starts as a bare skeleton.
|
|
5
|
+
This project starts as a bare skeleton. Its defaults for future modules resolve
|
|
6
|
+
to `{{defaultModuleSurface}}` surface + `{{defaultApplicationStyle}}` application
|
|
7
|
+
style. The `generate module` wizard presents this as a named Lean, CRUD, CQRS,
|
|
8
|
+
or Advanced profile so you can choose by intent instead of memorising two
|
|
9
|
+
implementation axes. Add a domain with:
|
|
6
10
|
|
|
7
11
|
```bash
|
|
8
12
|
go-scaffold generate module orders
|
|
9
13
|
```
|
|
10
14
|
|
|
11
|
-
|
|
12
|
-
|
|
15
|
+
The `generate module` wizard starts from those defaults. Add endpoints one at a
|
|
16
|
+
time with `generate method`, pass `--profile crud` for a CRUD skeleton, or use
|
|
17
|
+
`--profile cqrs` when commands and queries deserve separate application
|
|
18
|
+
handlers. DTO fields and business rules remain explicit TODOs. Run
|
|
19
|
+
`go-scaffold config` later to change defaults for future modules; existing
|
|
20
|
+
modules are unchanged. The older `--full` and `--cqrs` flags remain supported
|
|
21
|
+
for scripts.
|
|
13
22
|
|
|
14
23
|
See `docs/architect/` for the conventions every generated module follows, and `AGENTS.md`/`CLAUDE.md` if you're working with an AI coding agent in this repo.
|
|
15
24
|
|
|
25
|
+
## Installed optional features
|
|
26
|
+
|
|
27
|
+
This project records its generated capabilities in `go-scaffold.config.json`:
|
|
28
|
+
|
|
29
|
+
- authentication: {{#if auth}}enabled{{else}}not installed — use `go-scaffold add auth` when needed{{/if}}
|
|
30
|
+
- background jobs: {{#if worker}}enabled{{else}}not installed — use `go-scaffold add worker` when needed{{/if}}
|
|
31
|
+
- RBAC: {{#if rbac}}enabled{{else}}not installed — use `go-scaffold add rbac` after auth when needed{{/if}}
|
|
32
|
+
- metrics and tracing: {{#if observability}}enabled{{else}}not installed — use `go-scaffold add observability` when needed{{/if}}
|
|
33
|
+
|
|
34
|
+
The `add` commands refresh untouched generated architecture docs and this
|
|
35
|
+
section. If a maintainer has edited a document, the command leaves it alone
|
|
36
|
+
and reports that it needs a manual update.
|
|
37
|
+
|
|
16
38
|
## Layout
|
|
17
39
|
|
|
18
40
|
```
|
|
19
41
|
cmd/
|
|
20
|
-
|
|
21
|
-
|
|
42
|
+
├── api/
|
|
43
|
+
│ ├── main.go # process entry point, config, slog, graceful shutdown
|
|
44
|
+
│ └── wiring.go # process composition root and route registration
|
|
22
45
|
internal/
|
|
23
|
-
├──
|
|
24
|
-
|
|
46
|
+
├── composition/ # cross-feature adapters and process-level wiring
|
|
47
|
+
├── platform/ # adapters for external systems
|
|
48
|
+
│ └── database/ # opens the PostgreSQL connection + pool
|
|
25
49
|
├── shared/ # pure logic/framework glue, no I/O
|
|
26
50
|
│ ├── config/ # loads config from env
|
|
27
|
-
│ ├── apperror/
|
|
28
|
-
│ ├── dberr/ # maps DB errors to constraint kind
|
|
29
|
-
│ ├── httpx/ # HTTP helpers shared by every domain
|
|
30
|
-
│ ├── id/ # UUID v7 generation (id.New)
|
|
31
|
-
│ ├── middleware/ # RequestID, Logger
|
|
32
|
-
│ ├── pagination/
|
|
33
|
-
│ └── tx/ # carries a transaction on the
|
|
34
|
-
└── app/ #
|
|
51
|
+
│ ├── apperror/ # central error type (status + payload)
|
|
52
|
+
│ ├── dberr/ # maps DB errors to constraint kind — shared by every domain
|
|
53
|
+
│ ├── httpx/ # HTTP helpers shared by every domain
|
|
54
|
+
│ ├── id/ # UUID v7 generation (id.New)
|
|
55
|
+
│ ├── middleware/ # RequestID, Logger, Error, CORS{{#if auth}}, auth{{/if}}{{#if rbac}}, RBAC{{/if}}{{#if observability}}, metrics, tracing{{/if}}
|
|
56
|
+
│ ├── pagination/ # parses ?limit=&offset=, response envelope
|
|
57
|
+
│ └── tx/ # carries a transaction on the context
|
|
58
|
+
└── app/ # one package per domain, added with `generate module`
|
|
35
59
|
```
|
|
36
60
|
|
|
61
|
+
The tree shows the baseline. Installed optional features add the process and
|
|
62
|
+
platform packages listed in the section above without changing these module
|
|
63
|
+
boundaries.
|
|
64
|
+
|
|
65
|
+
Cross-feature adapters belong in `internal/composition/`, not in `cmd/api/`.
|
|
66
|
+
The API wiring file selects infrastructure, invokes those adapters, and
|
|
67
|
+
registers routes. Keep one-way dependencies and expose only narrow public
|
|
68
|
+
capabilities from each feature.
|
|
69
|
+
|
|
70
|
+
Optional features stay in the same architecture: auth owns user application
|
|
71
|
+
use cases and provider ports, RBAC owns role policy, worker owns queue/mail
|
|
72
|
+
adapters, and observability owns metrics/tracing setup.
|
|
73
|
+
|
|
37
74
|
## Run
|
|
38
75
|
|
|
39
76
|
```bash
|
|
@@ -42,7 +79,10 @@ make docker-up # local Postgres (postgres:5432)
|
|
|
42
79
|
{{/if}}
|
|
43
80
|
make db-create # create the {{dbName}} database itself (once — safe to re-run)
|
|
44
81
|
go mod tidy
|
|
45
|
-
make
|
|
82
|
+
{{#if worker}}{{#if (eq queue "river")}}make river-migrate # create River's job tables before queued mail
|
|
83
|
+
{{else}}# start Redis and set REDIS_URL before running the worker
|
|
84
|
+
{{/if}}{{/if}}
|
|
85
|
+
make run # APP_ENV=development enables the convenience schema bootstrap
|
|
46
86
|
```
|
|
47
87
|
|
|
48
88
|
`make run`/`make test`/`make migrate-up`/`make migrate-down` load `.env` if
|
|
@@ -65,17 +105,27 @@ falls back to `docker exec` into whichever container is publishing
|
|
|
65
105
|
other Postgres container{{else}}any Postgres container{{/if}} you already have running.
|
|
66
106
|
|
|
67
107
|
Server listens on `:8080` (override with `PORT`). Ctrl+C = graceful shutdown.
|
|
108
|
+
{{#if worker}}`make dev` starts both the HTTP API and the worker; `make run` starts only the API.
|
|
109
|
+
{{/if}}
|
|
68
110
|
|
|
69
111
|
## Makefile
|
|
70
112
|
|
|
71
113
|
```bash
|
|
72
114
|
make run # go run ./cmd/api
|
|
73
|
-
make
|
|
115
|
+
{{#if worker}}make dev # run cmd/api and cmd/worker together
|
|
116
|
+
make worker # run cmd/worker separately
|
|
117
|
+
{{#if (eq queue "river")}}make river-migrate # create/update River's Postgres job tables
|
|
118
|
+
make river-migrate-test # same River tables in TEST_DB_DSN
|
|
119
|
+
{{else}}# start Redis before running make worker
|
|
120
|
+
{{/if}}
|
|
121
|
+
{{/if}}{{#if auth}}make seed # run one-shot cmd/seed
|
|
122
|
+
{{/if}}make build # go build -o bin/api ./cmd/api
|
|
74
123
|
make test # go test ./...
|
|
75
124
|
make fmt # gofmt -w .
|
|
76
125
|
make vet # go vet ./...
|
|
77
126
|
make lint # golangci-lint run (see .golangci.yml)
|
|
78
127
|
make tidy # go mod tidy
|
|
128
|
+
make tools # install pinned migrate and golangci-lint tools
|
|
79
129
|
make db-create # create the database itself (safe to re-run)
|
|
80
130
|
make db-drop # drop the database
|
|
81
131
|
make migrate-up # apply migrations (reads DB_DSN from ENV_FILE)
|
|
@@ -103,10 +153,11 @@ migrate -path migrations -database "$DB_DSN" down 1
|
|
|
103
153
|
migrate create -ext sql -dir migrations -seq=false add_something # same as `go-scaffold generate migration`, if you'd rather not use the CLI
|
|
104
154
|
```
|
|
105
155
|
|
|
106
|
-
**dev:**
|
|
107
|
-
**prod:**
|
|
156
|
+
**dev:** `APP_ENV=development` enables the convenience table bootstrap.
|
|
157
|
+
**prod:** `APP_ENV=production` never bootstraps or mutates the schema; run
|
|
158
|
+
`migrate up` as a separate deploy step — versioned and rollback-capable.
|
|
108
159
|
|
|
109
|
-
|
|
160
|
+
In production, the app checks the DB's applied migration version
|
|
110
161
|
against the migration files baked into the binary (embedded at build time)
|
|
111
162
|
before it starts serving traffic — a stale or half-applied schema fails fast
|
|
112
163
|
at boot with a clear message, instead of failing later on whatever query
|
|
@@ -128,12 +179,25 @@ make migrate-verify # up -> down-to-zero -> up, catches a broken down.sql befo
|
|
|
128
179
|
| `PORT` | `8080` | |
|
|
129
180
|
| `DB_DSN` | `postgres://postgres:postgres@localhost:5432/{{dbName}}?sslmode=disable` | used by both GORM and the `migrate` CLI |
|
|
130
181
|
| `LOG_LEVEL` | `info` | debug/info/warn/error |
|
|
131
|
-
| `AUTO_MIGRATE` | `true` | disable in prod and use `migrate up` (see Migrations) |
|
|
132
182
|
| `DB_MAX_OPEN_CONNS` | `10` | |
|
|
133
183
|
| `DB_MAX_IDLE_CONNS` | `10` | |
|
|
134
184
|
| `DB_CONN_MAX_LIFETIME_MIN` | `5` | minutes |
|
|
135
185
|
| `CORS_ALLOWED_ORIGINS` | `http://localhost:3000` | comma-separated frontend origins allowed to call this API with credentials (cookies) |
|
|
136
186
|
|
|
187
|
+
After `go-scaffold add auth`, `.env.example` also contains
|
|
188
|
+
`GOOGLE_OAUTH_REDIRECT_URI` (the exact browser callback URI registered with
|
|
189
|
+
Google), `JWT_REFRESH_MAX_TTL_MIN`, `OAUTH_STATE_TTL_MIN`, and
|
|
190
|
+
`AUTH_BROWSER_TOPOLOGY`. Refresh rotation uses the configured inactivity TTL
|
|
191
|
+
but never moves a token past its absolute lifetime. The backend stores a
|
|
192
|
+
one-time OAuth transaction binding provider, state, S256 challenge, and OIDC
|
|
193
|
+
nonce; the frontend still owns the callback route and sends the code to the
|
|
194
|
+
exchange endpoint. The provider redirect URI is separate from
|
|
195
|
+
`CORS_ALLOWED_ORIGINS`; the latter remains an exact-origin credentialed API
|
|
196
|
+
policy. Token responses use `Cache-Control: no-store` and `Pragma: no-cache`.
|
|
197
|
+
For `cross-site`, configure HTTPS origins, `COOKIE_SAMESITE=none`,
|
|
198
|
+
`COOKIE_SECURE=true`, and an exact allowed Origin; the API applies an Origin
|
|
199
|
+
guard independently of CORS.
|
|
200
|
+
|
|
137
201
|
## Tests
|
|
138
202
|
|
|
139
203
|
Handler and service tests are fast unit tests: handlers depend on a narrow
|
|
@@ -152,9 +216,17 @@ make docker-up # local postgres first
|
|
|
152
216
|
{{/if}}
|
|
153
217
|
make db-create DB_NAME={{dbName}}_test
|
|
154
218
|
make migrate-up-test
|
|
219
|
+
{{#if (eq queue "river")}}make river-migrate-test
|
|
220
|
+
{{/if}}
|
|
155
221
|
TEST_DB_DSN=postgres://postgres:postgres@localhost:5432/{{dbName}}_test?sslmode=disable REQUIRE_TEST_DB=true go test ./...
|
|
156
222
|
```
|
|
157
223
|
|
|
224
|
+
{{#if (eq queue "river")}}The River integration test also starts a real worker,
|
|
225
|
+
enqueues a JSON job, and waits for its backend-neutral handler. River's own
|
|
226
|
+
schema history is separate from this project's application migrations, and the
|
|
227
|
+
`river-migrate` targets use the pinned River version from `go.mod`.
|
|
228
|
+
{{/if}}
|
|
229
|
+
|
|
158
230
|
## Error payload
|
|
159
231
|
|
|
160
232
|
```json
|
|
@@ -183,40 +255,51 @@ Then open `docs/openapi.bundled.yaml` — it needs no network and no running ser
|
|
|
183
255
|
go-scaffold generate module orders
|
|
184
256
|
```
|
|
185
257
|
|
|
186
|
-
Scaffolds
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
258
|
+
Scaffolds an `internal/app/order/` module using the project defaults
|
|
259
|
+
(`{{defaultModuleSurface}}` + `{{defaultApplicationStyle}}`), wires its route
|
|
260
|
+
into `cmd/api/wiring.go`, and appends a migration file. Add endpoints
|
|
261
|
+
with `generate method`; use `--profile crud` for a CRUD skeleton or
|
|
262
|
+
`--profile cqrs` for separate command/query handlers. See
|
|
263
|
+
`docs/architect/patterns.md` for the module shape and foreign-key rules.
|
|
190
264
|
|
|
191
265
|
## Deploying
|
|
192
266
|
|
|
267
|
+
{{#if worker}}
|
|
193
268
|
Two processes, one image, one target each:
|
|
194
269
|
|
|
195
270
|
```bash
|
|
196
271
|
docker build --target api -t {{projectName}}-api .
|
|
197
|
-
docker build --target worker -t {{projectName}}-worker .
|
|
272
|
+
docker build --target worker -t {{projectName}}-worker .
|
|
198
273
|
```
|
|
199
274
|
|
|
200
275
|
`api` is the last stage, so a plain `docker build .` builds it — the worker
|
|
201
|
-
stage has to be asked for by name
|
|
202
|
-
|
|
276
|
+
stage has to be asked for by name.{{#if auth}} `cmd/seed` is a one-shot
|
|
277
|
+
operational binary run through `make seed`, not a long-running target.{{/if}}
|
|
278
|
+
{{else}}
|
|
279
|
+
The base project has one long-running `cmd/api` image. A worker target becomes
|
|
280
|
+
available after `go-scaffold add worker`; do not deploy a worker that has not
|
|
281
|
+
been generated.
|
|
282
|
+
{{/if}}
|
|
203
283
|
|
|
204
284
|
Both stages are distroless and run as `nonroot`, so there is no shell in either
|
|
205
285
|
image. Debug against the `build` stage instead:
|
|
206
286
|
`docker run --rm -it --entrypoint sh $(docker build -q --target build .)`.
|
|
207
287
|
|
|
208
|
-
`cmd/api` serves HTTP. `cmd/worker` consumes the queue and is a
|
|
209
|
-
deployment with **no port and no health endpoint** — scale it
|
|
210
|
-
and remember that not running it means queued mail is never
|
|
288
|
+
`cmd/api` serves HTTP.{{#if worker}} `cmd/worker` consumes the queue and is a
|
|
289
|
+
separate deployment with **no port and no health endpoint** — scale it
|
|
290
|
+
independently, and remember that not running it means queued mail is never
|
|
291
|
+
sent, silently.{{/if}}
|
|
211
292
|
|
|
212
293
|
Before the first deploy of a release:
|
|
213
294
|
|
|
214
295
|
| | |
|
|
215
296
|
|---|---|
|
|
216
|
-
| `
|
|
297
|
+
| `APP_ENV` | `production` | production disables schema bootstrap and requires the applied migration version |
|
|
217
298
|
| migrations | `migrate -path migrations -database "$DB_DSN" up` as its own step, before the new binaries roll |
|
|
218
299
|
| `TRUSTED_PROXIES` | the CIDRs of your ingress, or the auth rate limiter keys on a header anyone can send |
|
|
219
300
|
| `COOKIE_SECURE` | `true` · `COOKIE_SAMESITE=none` as well if your frontend is on a different site |
|
|
301
|
+
| `JWT_REFRESH_MAX_TTL_MIN` | `43200` | absolute refresh-token lifetime; rotation cannot extend beyond it |
|
|
302
|
+
| `OAUTH_STATE_TTL_MIN` | `10` | lifetime of the one-time server-side OAuth state transaction |
|
|
220
303
|
| `/metrics` | reachable in-cluster for Prometheus, blocked at the ingress |
|
|
221
304
|
| `JWT_SECRET`, `SMTP_HOST` | the app refuses to start in production without real values |
|
|
222
305
|
|