@nakedev/go-scaffold 0.3.3 → 0.4.3

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.
Files changed (119) hide show
  1. package/README.md +288 -50
  2. package/dist/commands/auth.js +53 -22
  3. package/dist/commands/config.js +50 -0
  4. package/dist/commands/create.js +32 -2
  5. package/dist/commands/generate.js +25 -2
  6. package/dist/commands/method.js +22 -7
  7. package/dist/commands/migration.js +2 -2
  8. package/dist/commands/observability.js +3 -3
  9. package/dist/commands/rbac.js +3 -3
  10. package/dist/commands/undo.js +5 -0
  11. package/dist/commands/worker.js +1 -1
  12. package/dist/index.js +186 -59
  13. package/dist/prompts/auth-wizard.js +40 -6
  14. package/dist/prompts/create-wizard.js +43 -2
  15. package/dist/prompts/generate-wizard.js +89 -9
  16. package/dist/templates/auth-manifest.js +31 -1
  17. package/dist/templates/create-manifest.js +4 -0
  18. package/dist/templates/module-manifest.js +37 -1
  19. package/dist/templates/rbac-manifest.js +1 -0
  20. package/dist/types.js +6 -0
  21. package/dist/utils/auth-patcher.js +115 -24
  22. package/dist/utils/config.js +147 -3
  23. package/dist/utils/main-patcher.js +29 -27
  24. package/dist/utils/marker-patch.js +7 -1
  25. package/dist/utils/method-patcher.js +261 -81
  26. package/dist/utils/module-profile.js +32 -0
  27. package/dist/utils/observability-patcher.js +2 -2
  28. package/dist/utils/platform-patcher.js +29 -7
  29. package/dist/utils/rbac-patcher.js +97 -75
  30. package/package.json +7 -2
  31. package/templates/add/auth/cmd/seed/main.go.hbs +13 -3
  32. package/templates/add/auth/docs/login.yaml.hbs +11 -1
  33. package/templates/add/auth/docs/mfa-verify.yaml.hbs +19 -0
  34. package/templates/add/auth/docs/provider-exchange.yaml.hbs +40 -0
  35. package/templates/add/auth/docs/provider-login.yaml.hbs +31 -0
  36. package/templates/add/auth/docs/refresh.yaml.hbs +7 -0
  37. package/templates/add/auth/docs/register.yaml.hbs +7 -0
  38. package/templates/add/auth/docs/reset-password.yaml.hbs +1 -1
  39. package/templates/add/auth/docs/schemas.yaml.hbs +59 -1
  40. package/templates/add/auth/docs/users-me-mfa-confirm.yaml.hbs +19 -0
  41. package/templates/add/auth/docs/users-me-mfa-disable.yaml.hbs +15 -0
  42. package/templates/add/auth/docs/users-me-mfa-setup.yaml.hbs +14 -0
  43. package/templates/add/auth/docs/users-me-mfa.yaml.hbs +12 -0
  44. package/templates/add/auth/internal/app/user/application/oauth.go.hbs +132 -0
  45. package/templates/add/auth/internal/app/user/application/recovery.go.hbs +113 -0
  46. package/templates/add/auth/internal/app/user/browser_policy.go.hbs +98 -0
  47. package/templates/add/auth/internal/app/user/composition.go.hbs +165 -0
  48. package/templates/add/auth/internal/app/user/contracts.go.hbs +88 -0
  49. package/templates/add/auth/internal/app/user/dto.go.hbs +57 -0
  50. package/templates/add/auth/internal/app/user/errors.go.hbs +25 -0
  51. package/templates/add/auth/internal/app/user/external_login.go.hbs +208 -0
  52. package/templates/add/auth/internal/app/user/handler.go.hbs +60 -203
  53. package/templates/add/auth/internal/app/user/handler_local.go.hbs +75 -0
  54. package/templates/add/auth/internal/app/user/handler_mfa.go.hbs +83 -0
  55. package/templates/add/auth/internal/app/user/handler_oauth.go.hbs +70 -0
  56. package/templates/add/auth/internal/app/user/handler_recovery.go.hbs +49 -0
  57. package/templates/add/auth/internal/app/user/handler_test.go.hbs +290 -0
  58. package/templates/add/auth/internal/app/user/handler_user.go.hbs +41 -0
  59. package/templates/add/auth/internal/app/user/jwt.go.hbs +6 -59
  60. package/templates/add/auth/internal/app/user/local_auth.go.hbs +98 -0
  61. package/templates/add/auth/internal/app/user/mfa_service.go.hbs +450 -0
  62. package/templates/add/auth/internal/app/user/mfa_service_test.go.hbs +199 -0
  63. package/templates/add/auth/internal/app/user/mfa_store.go.hbs +127 -0
  64. package/templates/add/auth/internal/app/user/mfa_store_test.go.hbs +174 -0
  65. package/templates/add/auth/internal/app/user/model/authtoken.go.hbs +8 -2
  66. package/templates/add/auth/internal/app/user/model/identity.go.hbs +4 -3
  67. package/templates/add/auth/internal/app/user/model/mfa_challenge.go.hbs +17 -0
  68. package/templates/add/auth/internal/app/user/model/mfa_enrollment.go.hbs +20 -0
  69. package/templates/add/auth/internal/app/user/model/mfa_recovery_code.go.hbs +17 -0
  70. package/templates/add/auth/internal/app/user/model/user.go.hbs +3 -2
  71. package/templates/add/auth/internal/app/user/provider_test.go.hbs +286 -0
  72. package/templates/add/auth/internal/app/user/recovery_service.go.hbs +114 -0
  73. package/templates/add/auth/internal/app/user/repository.go.hbs +2 -0
  74. package/templates/add/auth/internal/app/user/service.go.hbs +82 -478
  75. package/templates/add/auth/internal/app/user/service_test.go.hbs +601 -45
  76. package/templates/add/auth/internal/app/user/session_cookie.go.hbs +33 -0
  77. package/templates/add/auth/internal/app/user/sessions.go.hbs +99 -0
  78. package/templates/add/auth/internal/app/user/tokenstore.go.hbs +42 -14
  79. package/templates/add/auth/internal/app/user/tokenstore_pg.go.hbs +105 -40
  80. package/templates/add/auth/internal/app/user/tokenstore_pg_test.go.hbs +96 -0
  81. package/templates/add/auth/internal/app/user/tokenstore_recovery.go.hbs +58 -0
  82. package/templates/add/auth/internal/app/user/tokenstore_redis.go.hbs +144 -70
  83. package/templates/add/auth/internal/app/user/tokenstore_redis_test.go.hbs +185 -0
  84. package/templates/add/auth/internal/app/user/user_query.go.hbs +65 -0
  85. package/templates/add/auth/internal/platform/authprovider/google/google.go.hbs +389 -0
  86. package/templates/add/auth/internal/platform/authprovider/google/google_test.go.hbs +312 -0
  87. package/templates/add/auth/migrations/create_auth_tokens.up.sql.hbs +9 -4
  88. package/templates/add/auth/migrations/create_identities.up.sql.hbs +1 -1
  89. package/templates/add/auth/migrations/create_mfa.down.sql.hbs +3 -0
  90. package/templates/add/auth/migrations/create_mfa.up.sql.hbs +29 -0
  91. package/templates/add/auth/migrations/create_users.up.sql.hbs +2 -2
  92. package/templates/add/rbac/internal/app/role/composition.go.hbs +35 -0
  93. package/templates/add/rbac/internal/app/role/service.go.hbs +12 -12
  94. package/templates/create/base/.claude/skills/go-scaffold/SKILL.md.hbs +340 -121
  95. package/templates/create/base/.env.example.hbs +0 -1
  96. package/templates/create/base/AGENTS.md.hbs +255 -67
  97. package/templates/create/base/Makefile.hbs +2 -1
  98. package/templates/create/base/README.md.hbs +45 -17
  99. package/templates/create/base/cmd/api/wiring.go.hbs +18 -25
  100. package/templates/create/base/internal/platform/database/database.go.hbs +3 -3
  101. package/templates/create/base/internal/shared/apperror/apperror.go.hbs +15 -2
  102. package/templates/create/base/internal/shared/config/config.go.hbs +0 -8
  103. package/templates/create/base/internal/shared/middleware/cors_test.go.hbs +40 -0
  104. package/templates/create/base/internal/shared/middleware/error.go.hbs +15 -5
  105. package/templates/create/features/docs/architecture.md.hbs +38 -16
  106. package/templates/create/features/docs/patterns.md.hbs +40 -21
  107. package/templates/create/features/docs/techstack.md.hbs +3 -3
  108. package/templates/generate/module/commands.go.hbs +95 -0
  109. package/templates/generate/module/composition.go.hbs +23 -0
  110. package/templates/generate/module/cqrs_test.go.hbs +7 -0
  111. package/templates/generate/module/handler.go.hbs +50 -5
  112. package/templates/generate/module/minimal/commands.go.hbs +34 -0
  113. package/templates/generate/module/minimal/handler.go.hbs +34 -0
  114. package/templates/generate/module/minimal/queries.go.hbs +45 -0
  115. package/templates/generate/module/minimal/service.go.hbs +27 -1
  116. package/templates/generate/module/queries.go.hbs +62 -0
  117. package/templates/generate/module/service.go.hbs +61 -5
  118. package/templates/add/auth/docs/google-callback.yaml.hbs +0 -22
  119. package/templates/add/auth/docs/google-login.yaml.hbs +0 -7
@@ -3,140 +3,359 @@ name: go-scaffold
3
3
  description: >
4
4
  Use whenever the user asks to add, create, scaffold, wire up, or generate a
5
5
  new domain/feature/resource OR a new endpoint/method in THIS project —
6
- including indirect or paraphrased requests that imply new backend surface
7
- area without saying "module"/"method" literally, e.g. "add a products
8
- feature", "let admins approve orders", "we need an endpoint for X", "look
9
- up a user by email". This project was scaffolded by go-scaffold and has a
10
- go-scaffold.config.json at its root check for that file before assuming
11
- this applies. Do NOT use for: business logic inside an already-generated
12
- method body, model/DTO field edits, foreign keys/relations between
13
- domains, bug fixes, refactors of existing code, or any project that lacks
14
- go-scaffold.config.json at its root.
6
+ including indirect requests such as "add products", "let admins approve
7
+ orders", "add an endpoint for X", or "look up a user by email". This project
8
+ was scaffolded by go-scaffold and has a go-scaffold.config.json at its root;
9
+ check for that file before assuming this applies. Use the generated
10
+ modular/Hexagonal boundaries and runtime safety contract described below.
11
+ Do NOT use for business logic inside an existing method, model/DTO field
12
+ edits, foreign keys/relations, bug fixes, refactors, or a project without
13
+ go-scaffold.config.json.
15
14
  ---
16
15
 
17
16
  # go-scaffold
18
17
 
19
- This project's domain modules (model, dto, errors, repository, service,
20
- handler) and their endpoints are generated with the `go-scaffold` CLI — do
21
- not hand-write a new one. Hand-writing a new `internal/app/<name>/` package,
22
- or adding a method by editing the handler/service directly, produces a shape
23
- that doesn't match the rest of the codebase (missing route registration,
24
- missing AutoMigrate wiring, a `repository` interface out of sync with its
25
- `repositoryStub` test mock, inconsistent error-catalog naming, etc.).
18
+ Use this skill for generated backend surface only. A new module or endpoint
19
+ must be created with the CLI so route registration, feature-local composition,
20
+ application seams, test stubs, OpenAPI TODOs, and migration markers remain
21
+ consistent. After generation, the product's business logic and domain rules
22
+ are still the engineer's responsibility.
23
+
24
+ This file is emitted at `.claude/skills/go-scaffold/SKILL.md` for Claude Code.
25
+ Agents that do not load `.claude/skills` should follow the root `AGENTS.md`,
26
+ which carries the same project-wide safety contract.
26
27
 
27
28
  ## When to use this skill
28
29
 
29
- - Any request for a new feature/resource/domain, even named informally
30
- ("add invoices", "we need a way to track X")
31
- - Any request for a new endpoint on an existing feature, even phrased as a
32
- capability ("let users do X", "admins should be able to Y", "add a lookup
33
- by Z")
34
- - The user explicitly says "generate", "scaffold", or names the CLI
30
+ - A new feature, resource, or domain is requested, even if the user does not
31
+ say "module".
32
+ - A new endpoint or capability is requested on an existing feature, even if
33
+ the user does not say "method".
34
+ - The user explicitly asks to scaffold, generate, or use go-scaffold.
35
+
36
+ Examples that all use this skill:
37
+
38
+ - “add invoices” → a new module
39
+ - “let admins approve orders” → a new method on orders
40
+ - “list overdue invoices” → a GET-all method
41
+ - “look up a user by email” → a GET-one lookup with --field email
42
+
43
+ ## When not to use it
44
+
45
+ - Filling in business logic in a method the CLI already generated
46
+ - Editing model fields, DTO validation, or existing method behavior
47
+ - Adding a foreign key or relation between domains
48
+ - Bug fixes, refactors, or architecture work unrelated to generated surface
49
+ - Any repository without go-scaffold.config.json at its root
50
+
51
+ Those tasks still follow AGENTS.md and docs/architect/; this skill does
52
+ not authorize schema, auth/security, dependency, architecture, or deployment
53
+ changes without the project's required owner approval.
35
54
 
36
- ## When NOT to use this skill
55
+ ## Before running the CLI
37
56
 
38
- - Filling in business logic inside a method the CLI already generated
39
- - Editing model fields, DTO validation, or existing method bodies
40
- - Wiring a foreign key / relation between two domains
41
- - Bug fixes and refactors
42
- - The project has no `go-scaffold.config.json` at its root
57
+ 1. Read AGENTS.md, the relevant docs/architect/ notes, and the target
58
+ module's existing handler/service/repository.
59
+ 2. Run git status --short --branch and confirm the work is on a dedicated,
60
+ non-protected branch. Preserve unrelated local changes.
61
+ 3. Search for an existing module, endpoint, route, and migration before
62
+ generating anything. Do not generate a duplicate name.
63
+ 4. Decide which module profile matches the domain. The interactive wizard
64
+ offers Lean (`minimal + service`), CRUD (`crud + service`), CQRS
65
+ (`minimal + cqrs`), and Advanced for an explicit custom combination. Lean
66
+ is the safe starting point when the product only needs specific endpoints;
67
+ CRUD is for an intentional CRUD starter, and CQRS is for genuinely
68
+ different command/query needs. A profile is a generator preset, not a
69
+ claim about the domain's DDD maturity.
43
70
 
44
- ## How to use it
71
+ ## Commands
45
72
 
46
- Run from the project root:
73
+ Run from the project root and pass every value as a flag:
47
74
 
48
- ```bash
49
- go-scaffold generate module <name> --defaults [--full] [--auth] [--permission <code>]
75
+ ~~~bash
76
+ go-scaffold generate module <name> [--profile <lean|crud|cqrs>] [--defaults] [--auth] [--permission <code>]
50
77
  go-scaffold generate method <module> <name> --type <get|post|put|patch|delete> [--get-mode all|one] [--field <name>]
51
78
  go-scaffold generate migration <name>
79
+ go-scaffold config
80
+ go-scaffold add auth --store <postgres|redis> --browser-topology <same-origin|same-site|cross-site> [--defaults] [--yes]
81
+ go-scaffold add worker --queue <postgres|redis> [--defaults]
52
82
  go-scaffold undo module <name> -y
53
- ```
54
-
55
- **Every value has to be a flag.** These commands prompt for whatever you
56
- leave out, and a prompt in a non-interactive shell exits 1 having written
57
- nothing so a bare `generate module products` fails rather than taking the
58
- default. `--defaults` supplies the unasked-for answers (minimal, no auth);
59
- combine it with `--full`/`--auth`/`--permission` when you want those. The
60
- same applies to `--get-mode` (required with `--type get`) and `-y` on
61
- `undo module`. Every `add` command summarises what it will write and asks
62
- before writing: `-y` skips that confirmation, but `add auth` and `add worker`
63
- *also* ask which backing store to use so those two need `--defaults`
64
- (or their own `--store`/`--queue` flag plus `-y`), while `add rbac -y` and
65
- `add observability -y` are enough.
66
-
67
- `<name>` for a module is a domain noun in whatever form reads naturally —
68
- singular or plural, any case, hyphens or underscores are all accepted and
69
- normalized (`products` `product`, `Orders` `order`, `order-item`
70
- `orderitem`). From it the CLI derives the Go package name, the pluralized
71
- REST route (`/orders`, `/order-items`), the table, and the error-code prefix
72
- (`ORDER_ITEM_NOT_FOUND`). The only names it refuses are the ones that
73
- wouldn't compile: starting with a digit, or a Go keyword / predeclared type
74
- (`type`, `string`, `error`, ...).
75
-
76
- ### `generate module <name>`
77
-
78
- **Minimal is the default** — the module is created with no endpoints, and
79
- what lands in `cmd/api/wiring.go` is an *empty route group* under{{#if apiPrefix}} `/{{apiPrefix}}`{{else}} no prefix{{/if}}
80
- waiting for `generate method`. You still get every file:
81
- `internal/app/<pkg>/{model/,dto,errors,repository,service,handler,service_test,handler_test,repository_test}.go`
82
- the full data-access surface exists (the repository and its interface are
83
- complete) so `generate method` has something to call. Plus the model added
84
- to the `AutoMigrate` call and a new `migrations/<version>_create_<plural>.{up,down}.sql`.
85
-
86
- `--full` swaps in a CRUD skeleton instead: list/get/create/update/delete
87
- already routed and wired, with DTO fields and business rules left as TODOs.
88
- With OpenAPI enabled it also writes `docs/<plural>/{collection,item,schemas}.yaml`
89
- and wires them into `docs/openapi.yaml`. Use it only when a full CRUD surface
90
- is actually intended otherwise prefer minimal plus explicit `generate method`
91
- calls.
92
-
93
- `--auth` puts the module's routes behind a valid access token (requires
94
- `go-scaffold add auth -y` in this project first). `--permission <code>` also
95
- requires that permission via `authz.Require` and seeds it in its own
96
- migration it needs `add rbac`, and `--auth` must be passed alongside it.
97
-
98
- There's no per-domain versioning — the route prefix is a single
99
- project-wide choice made at `create` time.
100
-
101
- ### `generate method <module> <name>`
102
-
103
- Patches the existing module in place — never overwrites a method with the
104
- same name; if the name collides it asks for a different one instead. Route
105
- and shape depend on `--type`:
106
-
107
- | `--type` | Route | Notes |
83
+ ~~~
84
+
85
+ Omitted values prompt; in a non-interactive shell that exits 1 without
86
+ writing. For `generate module`, `--defaults` uses the recorded project
87
+ defaults and keeps the module public; fresh and legacy projects resolve to
88
+ Lean (`minimal + service`). `--profile` is the non-interactive equivalent of
89
+ choosing a named architecture preset. The older `--full` and `--cqrs` flags
90
+ remain supported for existing scripts, but cannot be combined with
91
+ `--profile`; `--full` means CRUD surface and `--cqrs` means separate command /
92
+ query handlers. Advanced is a wizard-only choice that asks the two axes
93
+ separately. Add `--auth` and `--permission <code>` only when they are wanted;
94
+ permission also requires `--auth`. `--get-mode` is required with `--type get`;
95
+ `--field` is required for GET-one and cannot be id. `undo` requires
96
+ `-y`. `add auth` accepts `--store postgres|redis` and
97
+ `--browser-topology same-origin|same-site|cross-site`; `--defaults` selects
98
+ Postgres and same-site. Postgres keeps refresh, recovery, and MFA state in
99
+ Postgres and uses an in-process rate limiter. Redis keeps refresh tokens and
100
+ rate-limit counters in Redis, while recovery and MFA state remain durable in
101
+ Postgres. Without `add worker`, auth mail is sent inline; with a worker it uses
102
+ the queue backend chosen by `add worker`.
103
+
104
+ Module names may be singular/plural, mixed case, hyphenated, or underscored.
105
+ The CLI normalizes the Go package, REST path, SQL table, and error-code prefix.
106
+ Reject names that would not compile, such as a leading digit, Go keyword, or
107
+ predeclared type.
108
+
109
+ ## Wizard and profile contract
110
+
111
+ `go-scaffold generate module <name>` asks for the module profile even when the
112
+ name is already supplied. If the name is omitted, it asks for the name first.
113
+ Choosing Advanced then asks for module surface and application style
114
+ separately. Auth and permission prompts appear only when the project has the
115
+ corresponding features installed. There is no final confirmation for module
116
+ generation; review the profile and flags before running it.
117
+
118
+ `go-scaffold config` changes defaults for future modules only. Existing modules
119
+ are not rewritten. `config show` and `config validate` are intentionally
120
+ read-only and do not open a wizard. The bare `generate` command first asks
121
+ whether to generate a module, method, or migration; the bare `add` command
122
+ first asks which infrastructure feature to add.
123
+
124
+ ## Generated module contract
125
+
126
+ `generate module <name> --defaults` creates a module using the resolved project
127
+ defaults. On a fresh project that is a Lean module with:
128
+
129
+ ~~~text
130
+ internal/app/<pkg>/
131
+ model/model.go
132
+ dto.go
133
+ errors.go
134
+ repository.go
135
+ service.go
136
+ composition.go
137
+ handler.go
138
+ service_test.go
139
+ handler_test.go
140
+ repository_test.go
141
+ migrations/<version>_create_<plural>.{up,down}.sql
142
+ ~~~
143
+
144
+ The Lean/minimal module has no endpoint yet, but includes the complete
145
+ data-access surface and test seams needed by generate method. The CRUD profile
146
+ adds a CRUD starter and routes list/get/create/update/delete. The CQRS profile
147
+ adds separate commands.go and queries.go application handlers; Advanced can
148
+ combine CRUD + CQRS, or use CQRS alone before adding methods one at a time.
149
+ With OpenAPI enabled it also creates the per-domain documents and updates
150
+ docs/openapi.yaml.
151
+
152
+ Feature-local composition.go constructs repository → application handlers →
153
+ handler. A module generated with `--cqrs` constructs separate command/query
154
+ handlers there; a regular module constructs its service there.
155
+ cmd/api/wiring.go is the process composition root: it selects shared
156
+ infrastructure, supplies explicit cross-feature ports/security dependencies,
157
+ and registers routes. Do not move business rules or feature-internal
158
+ constructors into wiring, or hand-edit root route markers for a new endpoint.
159
+
160
+ ## Method shapes and stub safety
161
+
162
+ generate method patches the existing module without overwriting a same-named
163
+ method:
164
+
165
+ | Type | Shape | Generated behavior |
108
166
  |---|---|---|
109
- | `get --get-mode all` | `GET /<plural>/<kebab-name>` | list-style, reuses `FindAll` — TODO to add real filtering |
110
- | `get --get-mode one --field <f>` | `GET /<plural>/<f>/:<f>` | adds a real `FindBy<F>` query to the repository (and its `repository` interface + `repositoryStub` test stub); `--field` can't be `id` |
111
- | `post` | `POST /<plural>/<kebab-name>` | adds a body DTO; service body is a TODO stub (`apperror.NewInternal()` until implemented) |
112
- | `put` / `patch` | `<VERB> /<plural>/:id/<kebab-name>` | finds the record by id, TODO before saving (safe no-op until implemented) |
113
- | `delete` | `DELETE /<plural>/:id/<kebab-name>` | TODO stub (`apperror.NewInternal()` until implemented) |
114
-
115
- ### `generate migration <name>`
116
-
117
- Reserves a timestamped `migrations/<version>_<name>.{up,down}.sql` pair,
118
- both TODO stubs the CLI doesn't guess at columns, you write the SQL. This
119
- is the way to make any schema change that isn't a new module: adding a
120
- column, an index, a foreign key, a backfill, a drop.
121
-
122
- ### `undo module <name>`
123
-
124
- For a `generate module` that shouldn't have happened — a typo'd name, a
125
- domain decided against. Deletes `internal/app/<pkg>/`, the module's
126
- `migrations/<version>_create_<plural>.{up,down}.sql` pair, and reverses
127
- everything `generate module` wired up in `cmd/api/wiring.go` (and in
128
- `docs/openapi.yaml` + `docs/<plural>/` when OpenAPI is enabled). `-y` skips
129
- the confirmation prompt, and is required in a non-interactive shell.
130
-
131
- The migration files go because `migrations/embed.go` is a `//go:embed *`: a
132
- typo's migration left behind runs on every database created from then on.
133
- That's only safe while those files exist nowhere else, so `undo` refuses —
134
- deleting nothing — when they're committed to git, or when the database is
135
- already at or past that version. Neither is a bug to work around: a domain
136
- that has shipped is retired with `generate migration drop_<table>` and a
137
- reviewed data removal, not with this command. The table is never dropped.
138
-
139
- What you still do by hand: real field names on the model/DTOs (the generated
140
- ones are placeholders), any foreign key to another domain (see
141
- `docs/architect/patterns.md` for the 3 rules), and the actual business logic
142
- behind every `TODO` the CLI leaves.
167
+ | get --get-mode all | GET /<plural>/<name> | Reuses FindAll; add real filtering |
168
+ | get --get-mode one --field <f> | GET /<plural>/<f>/:<f> | Adds a repository lookup and test seam |
169
+ | post | POST /<plural>/<name> | Body DTO plus internal-error TODO |
170
+ | put / patch | <VERB> /<plural>/:id/<name> | Safe 501 Not Implemented; no repository read/write |
171
+ | delete | DELETE /<plural>/:id/<name> | Internal-error TODO until implemented |
172
+
173
+ The 501 contract is intentional: a generated PUT/PATCH action must not
174
+ pretend to update a record. Implement the use case, validation, authorization,
175
+ repository operation, tests, and OpenAPI response together before changing
176
+ that behavior. Replace all generated TODOs before treating an endpoint as
177
+ production-ready.
178
+
179
+ ## Architecture guidance for generated code
180
+
181
+ This project is modular plus Hexagonal/DDD-friendly, with CQRS available via
182
+ the `cqrs` profile or the backwards-compatible
183
+ `go-scaffold generate module <name> --cqrs` flag:
184
+
185
+ - handlers are inbound adapters and know HTTP/Gin only;
186
+ - services or commands.go/queries.go (and, for complex flows,
187
+ internal/app/<feature>/application/) are application/use-case boundaries and
188
+ depend on narrow ports;
189
+ - models and domain invariants stay independent of Gin, GORM, Redis, and HTTP;
190
+ - repositories/token stores/mail/queue clients are outbound adapters;
191
+ - composition.go wires one feature locally, while cmd/api/wiring.go wires
192
+ the process.
193
+
194
+ DDD does not require every CRUD row to become a large aggregate. CQRS optional:
195
+ use the CQRS profile when command and query models, consistency, or
196
+ read scaling differ; it creates separate command/query handlers while keeping
197
+ one modular monolith and database by default. For simple CRUD, a single
198
+ service and repository port are clearer. Do not add a broker, a second
199
+ database, or empty command/query layers just for naming.
200
+
201
+ Never import another feature's private model, repository, or handler. For
202
+ cross-feature behavior, define a narrow public application port, use a
203
+ documented feature API, or publish an explicit event. Keep dependencies
204
+ flowing inward:
205
+
206
+ ~~~text
207
+ handler -> application/use case -> domain + ports -> adapters
208
+ ^ |
209
+ +--- composition -+
210
+ ~~~
211
+
212
+ The generated auth facade is a pragmatic compatibility boundary: its service
213
+ contracts currently use the feature's `model` values and shared `apperror`,
214
+ while handlers own Gin and provider adapters own SDK configuration. Do not
215
+ broaden that coupling by importing Gin, provider SDKs, or HTTP handlers into
216
+ use cases. New complex flows should prefer transport-neutral DTOs and narrow
217
+ ports; a deeper separation of the existing auth facade is an explicit
218
+ architecture refactor and needs focused tests.
219
+
220
+ ## Authentication and browser OAuth contract
221
+
222
+ `add auth` provides email/password authentication and a generic provider
223
+ boundary. For browser OAuth, preserve this client-owned callback contract:
224
+
225
+ - The browser frontend owns one provider callback route. The API exposes
226
+ `GET /auth/{provider}/login` to start authorization and
227
+ `POST /auth/{provider}/exchange` to finish it; do not add an API callback
228
+ route or a server-side browser handoff after the exchange.
229
+ - The frontend creates `state` and an S256 PKCE verifier/challenge. It sends
230
+ `state`, `code_challenge`, and `code_challenge_method=S256` to the login
231
+ endpoint, handles provider success/cancel/error in its callback route, then
232
+ sends `code`, `state`, and `code_verifier` to the exchange endpoint.
233
+ - Provider adapters own OAuth configuration, code exchange, and claims/userinfo
234
+ validation. The application service persists a one-time server-side
235
+ transaction binding provider, state, S256 challenge, and OIDC nonce, then
236
+ consumes it before completion. Application services depend on the
237
+ `LoginProvider` port, provider registry, and normalized identity DTOs; they
238
+ must not branch on Google or import a provider's `oauth2.Config`.
239
+ - The server uses the exact provider-registered redirect URI from environment
240
+ configuration (`GOOGLE_OAUTH_REDIRECT_URI` for the Google adapter), validates
241
+ the provider response plus state, PKCE, and nonce, resolves the local
242
+ identity, creates the local session, sets an HttpOnly refresh cookie, and
243
+ returns JSON. Google is OIDC: validate the signed ID token's issuer,
244
+ audience/authorized party, time claims, nonce, and subject, and reconcile its
245
+ subject with UserInfo. Token responses must use `Cache-Control: no-store` and
246
+ `Pragma: no-cache`.
247
+ Never accept a request-supplied redirect destination or place an access token,
248
+ refresh token, authorization code, or state in a URI.
249
+ - Map provider and validation failures to controlled public codes only:
250
+ `oauth_denied`, `oauth_state_invalid`, `oauth_provider_unavailable`, and
251
+ `oauth_failed`. Do not expose raw provider descriptions or technical causes.
252
+ - `AUTH_BROWSER_TOPOLOGY` is the cookie/CORS deployment policy, not the
253
+ provider redirect configuration. Keep `CORS_ALLOWED_ORIGINS` as an exact
254
+ origin allowlist. Cross-site browser deployments require HTTPS,
255
+ `COOKIE_SAMESITE=none`, and `COOKIE_SECURE=true`; production browser cookies
256
+ always require `COOKIE_SECURE=true`. Because CORS is not CSRF protection,
257
+ cookie-authenticated state-changing endpoints also require an exact allowed
258
+ `Origin` (or equivalent validated `Referer`) when SameSite=None is used.
259
+ - Native/mobile OAuth is out of scope for this scaffold phase. Do not infer a
260
+ native callback or token handoff contract from the browser flow.
261
+
262
+ ### MFA contract
263
+
264
+ MFA has two independent switches: an operator capability and per-user
265
+ enrollment. Preserve both:
266
+
267
+ - `AUTH_MFA_ENABLED=false` is the default. When it is false, MFA endpoints
268
+ report unavailable and login does not require a challenge. Turning the
269
+ capability off does not delete stored enrollments; re-enabling it restores
270
+ the policy for users who had already enrolled.
271
+ - When the capability is enabled, the composition root must validate
272
+ `MFA_ENCRYPTION_KEY` as a base64-encoded 32-byte key before serving traffic.
273
+ Keep `MFA_ISSUER`, `MFA_CHALLENGE_TTL_MIN`, `MFA_TOTP_WINDOW`, and
274
+ `MFA_RECOVERY_CODE_COUNT` explicit in configuration; do not silently weaken
275
+ their safety limits.
276
+ - An authenticated user opts in with `GET /users/me/mfa`,
277
+ `POST /users/me/mfa/setup`, and `POST /users/me/mfa/confirm`; disabling it
278
+ requires the current TOTP code at `POST /users/me/mfa/disable`. Setup returns
279
+ the secret/otpauth URI only while pending, and confirmation returns recovery
280
+ codes only once.
281
+ - Password login and provider exchange may return
282
+ `{ "mfa_required": true, "challenge": "..." }` for an enrolled user. This
283
+ is a pre-session response: do not issue an access token or refresh cookie
284
+ until `POST /auth/mfa/verify` succeeds with a TOTP or recovery code.
285
+ - Store only an encrypted TOTP secret, hashed challenges, and hashed recovery
286
+ codes. Challenges are short-lived and one-use; a failed verification also
287
+ consumes its challenge. Recovery codes are atomic one-use login factors and
288
+ must not be accepted for disabling MFA. MFA state is durable in Postgres for
289
+ both refresh-token store choices, so `add auth`'s `create_mfa` migration must
290
+ be applied and included in real-store/concurrency tests.
291
+
292
+ Keep auth construction explicit: `NewService(deps Dependencies, cfg
293
+ AuthConfig)`. `Dependencies` owns narrow ports such as `MFA`, `Providers`,
294
+ `RefreshTokens`, `RecoveryTokens`, `Mailer`, `Roles`, and `Clock`; `AuthConfig`
295
+ owns JWT, OAuth, recovery, and `MFASettings` policy. Do not pass the generated
296
+ shared config package into the application service or replace these contracts
297
+ with a positional constructor.
298
+
299
+ When adding auth from a script, pass the choices explicitly, for example:
300
+
301
+ ~~~bash
302
+ go-scaffold add auth --store postgres --browser-topology same-site --defaults --yes
303
+ ~~~
304
+
305
+ ## Runtime, migration, and security guardrails
306
+
307
+ - APP_ENV is the only environment gate and accepts development or production.
308
+ Do not reintroduce an AUTO_MIGRATE setting.
309
+ - Development may use the convenience db.AutoMigrate(...) bootstrap.
310
+ Production must never bootstrap or mutate schema at API startup. Apply
311
+ versioned SQL with make migrate-up; the startup version check must fail
312
+ fast for a behind or dirty database.
313
+ - Use generate migration for schema changes outside a new module, write and
314
+ review both migration directions, and test them against a disposable DB.
315
+ - Keep SQL, Redis, queue, mail, and other external resources under the
316
+ composition root and close them on every exit path. Preserve
317
+ SIGINT/SIGTERM graceful shutdown and do not call os.Exit from a goroutine.
318
+ - Never log passwords, raw tokens, JWT secrets, OAuth secrets, or auth
319
+ headers. Client responses must not expose technical error causes.
320
+ - Refresh-token rotation is an atomic consume: Postgres uses one SQL unit and
321
+ Redis uses one Lua operation. Preserve active-session cleanup and the
322
+ reuse-detection tombstone; never replace it with read-then-delete.
323
+ - Password reset and email verification consume one-time tokens in the same
324
+ retry-safe transaction as the identity/user update. Preserve recovery on a
325
+ post-commit session-revocation failure.
326
+ - Wrap technical causes with %w or apperror.NewInternal(cause). Middleware
327
+ logs the cause with request_id; production strips it from the response.
328
+
329
+ ## Verification
330
+
331
+ After changing generated Go code or templates, run the relevant checks:
332
+
333
+ ~~~bash
334
+ gofmt -w <changed-go-files>
335
+ go test ./...
336
+ go test -race ./... # required for concurrency/auth/token changes
337
+ go vet ./...
338
+ golangci-lint run
339
+ ~~~
340
+
341
+ For repository, schema, or auth adapter changes, use a migrated disposable
342
+ PostgreSQL/Redis instance and set REQUIRE_TEST_DB=true or
343
+ REQUIRE_TEST_REDIS=true; a skipped integration test is not proof. MFA state
344
+ still requires PostgreSQL even when refresh tokens use Redis. When routes or
345
+ OpenAPI templates change, inspect the updated `docs/openapi.yaml` and run the
346
+ configured OpenAPI linter.
347
+
348
+ Only in the `go-scaffold` source repository, run `pnpm run verify` for CLI or
349
+ template changes, then generate a fresh sample project and inspect both its
350
+ source and guidance files. A generated Go project does not contain the
351
+ generator's `package.json` or pnpm scripts; from that project run the Go checks
352
+ above and use the installed/local CLI for generation checks.
353
+ Review git diff --check, git diff, and git status --short before handoff,
354
+ and report exact output plus known limitations.
355
+
356
+ ## Migration and undo
357
+
358
+ generate migration <name> reserves a timestamped .up.sql/.down.sql pair; the
359
+ engineer writes the actual SQL. undo module <name> -y is only for an unshipped
360
+ generated module and refuses if its migration is committed or applied. Retire
361
+ a shipped domain with a reviewed drop migration and data plan, not undo.
@@ -6,7 +6,6 @@ APP_ENV=development # prod: production
6
6
  PORT=8080
7
7
  DB_DSN=postgres://postgres:postgres@localhost:5432/{{dbName}}?sslmode=disable
8
8
  LOG_LEVEL=info
9
- AUTO_MIGRATE=true
10
9
  DB_MAX_OPEN_CONNS=10
11
10
  DB_MAX_IDLE_CONNS=10
12
11
  DB_CONN_MAX_LIFETIME_MIN=5