@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.
Files changed (164) hide show
  1. package/README.md +567 -513
  2. package/dist/commands/auth.js +12 -1
  3. package/dist/commands/check.js +281 -0
  4. package/dist/commands/create.js +2 -1
  5. package/dist/commands/generate.js +5 -2
  6. package/dist/commands/method.js +66 -70
  7. package/dist/commands/observability.js +4 -53
  8. package/dist/commands/rbac.js +19 -8
  9. package/dist/commands/undo.js +6 -3
  10. package/dist/commands/worker.js +14 -4
  11. package/dist/index.js +13 -1
  12. package/dist/templates/auth-manifest.js +46 -45
  13. package/dist/templates/create-manifest.js +4 -0
  14. package/dist/templates/module-manifest.js +82 -60
  15. package/dist/templates/rbac-manifest.js +15 -11
  16. package/dist/templates/worker-manifest.js +4 -1
  17. package/dist/types.js +2 -0
  18. package/dist/utils/auth-patcher.js +22 -22
  19. package/dist/utils/config.js +24 -5
  20. package/dist/utils/docs-patcher.js +68 -0
  21. package/dist/utils/hexagonal-method-patcher.js +334 -0
  22. package/dist/utils/main-patcher.js +3 -3
  23. package/dist/utils/module-location.js +17 -11
  24. package/dist/utils/platform-patcher.js +27 -0
  25. package/dist/utils/rbac-patcher.js +73 -216
  26. package/package.json +1 -1
  27. package/templates/add/auth/cmd/seed/main.go.hbs +2 -0
  28. package/templates/add/auth/internal/app/user/{browser_policy.go.hbs → adapters/inbound/http/browser_policy.go.hbs} +3 -3
  29. package/templates/add/auth/internal/app/user/adapters/inbound/http/dto.go.hbs +159 -0
  30. package/templates/add/auth/internal/app/user/{handler.go.hbs → adapters/inbound/http/handler.go.hbs} +103 -8
  31. package/templates/add/auth/internal/app/user/{handler_local.go.hbs → adapters/inbound/http/handler_local.go.hbs} +8 -7
  32. package/templates/add/auth/internal/app/user/{handler_mfa.go.hbs → adapters/inbound/http/handler_mfa.go.hbs} +6 -6
  33. package/templates/add/auth/internal/app/user/{handler_oauth.go.hbs → adapters/inbound/http/handler_oauth.go.hbs} +17 -17
  34. package/templates/add/auth/internal/app/user/{handler_recovery.go.hbs → adapters/inbound/http/handler_recovery.go.hbs} +4 -4
  35. package/templates/add/auth/internal/app/user/{handler_test.go.hbs → adapters/inbound/http/handler_test.go.hbs} +52 -31
  36. package/templates/add/auth/internal/app/user/{handler_user.go.hbs → adapters/inbound/http/handler_user.go.hbs} +4 -4
  37. package/templates/add/auth/internal/app/user/{session_cookie.go.hbs → adapters/inbound/http/session_cookie.go.hbs} +5 -3
  38. package/templates/add/auth/internal/app/user/adapters/outbound/password/bcrypt.go.hbs +35 -0
  39. package/templates/add/auth/internal/app/user/adapters/outbound/password/bcrypt_test.go.hbs +20 -0
  40. package/templates/add/auth/internal/app/user/{mfa_store.go.hbs → adapters/outbound/postgres/mfa_store.go.hbs} +22 -20
  41. package/templates/add/auth/internal/app/user/{mfa_store_test.go.hbs → adapters/outbound/postgres/mfa_store_test.go.hbs} +7 -7
  42. package/templates/add/auth/internal/app/user/adapters/outbound/postgres/model.go.hbs +84 -0
  43. package/templates/add/auth/internal/app/user/adapters/outbound/postgres/repository.go.hbs +211 -0
  44. package/templates/add/auth/internal/app/user/{repository_test.go.hbs → adapters/outbound/postgres/repository_test.go.hbs} +18 -19
  45. package/templates/add/auth/internal/app/user/{tokenstore_pg.go.hbs → adapters/outbound/postgres/tokenstore_pg.go.hbs} +36 -32
  46. package/templates/add/auth/internal/app/user/{tokenstore_pg_test.go.hbs → adapters/outbound/postgres/tokenstore_pg_test.go.hbs} +10 -3
  47. package/templates/add/auth/internal/app/user/{tokenstore_recovery.go.hbs → adapters/outbound/postgres/tokenstore_recovery.go.hbs} +29 -3
  48. package/templates/add/auth/internal/app/user/{tokenstore_redis.go.hbs → adapters/outbound/redis/tokenstore.go.hbs} +43 -36
  49. package/templates/add/auth/internal/app/user/{tokenstore_redis_test.go.hbs → adapters/outbound/redis/tokenstore_test.go.hbs} +14 -3
  50. package/templates/add/auth/internal/app/user/application/contracts.go.hbs +52 -0
  51. package/templates/add/auth/internal/app/user/application/dto.go.hbs +75 -0
  52. package/templates/add/auth/internal/app/user/application/errors.go.hbs +62 -0
  53. package/templates/add/auth/internal/app/user/{external_login.go.hbs → application/external_login.go.hbs} +30 -40
  54. package/templates/add/auth/internal/app/user/{jwt.go.hbs → application/jwt.go.hbs} +6 -3
  55. package/templates/add/auth/internal/app/user/{local_auth.go.hbs → application/local_auth.go.hbs} +17 -19
  56. package/templates/add/auth/internal/app/user/{mfa_service.go.hbs → application/mfa_service.go.hbs} +27 -28
  57. package/templates/add/auth/internal/app/user/{mfa_service_test.go.hbs → application/mfa_service_test.go.hbs} +12 -11
  58. package/templates/add/auth/internal/app/user/application/oauth.go.hbs +3 -3
  59. package/templates/add/auth/internal/app/user/{provider_test.go.hbs → application/provider_test.go.hbs} +55 -56
  60. package/templates/add/auth/internal/app/user/application/recovery.go.hbs +16 -47
  61. package/templates/add/auth/internal/app/user/{recovery_service.go.hbs → application/recovery_service.go.hbs} +13 -15
  62. package/templates/add/auth/internal/app/user/application/service.go.hbs +145 -0
  63. package/templates/add/auth/internal/app/user/{service_test.go.hbs → application/service_test.go.hbs} +94 -75
  64. package/templates/add/auth/internal/app/user/{sessions.go.hbs → application/sessions.go.hbs} +14 -14
  65. package/templates/add/auth/internal/app/user/application/tokenstore_ports.go.hbs +14 -0
  66. package/templates/add/auth/internal/app/user/{user_query.go.hbs → application/user_query.go.hbs} +16 -16
  67. package/templates/add/auth/internal/app/user/composition.go.hbs +89 -86
  68. package/templates/add/auth/internal/app/user/domain/entity.go.hbs +41 -0
  69. package/templates/add/auth/internal/app/user/domain/errors.go.hbs +32 -0
  70. package/templates/add/auth/internal/app/user/ports/password.go.hbs +9 -0
  71. package/templates/add/auth/internal/app/user/ports/repository.go.hbs +90 -0
  72. package/templates/add/auth/migrations/create_auth_tokens.up.sql.hbs +1 -1
  73. package/templates/add/auth/migrations/create_login_throttle.up.sql.hbs +1 -1
  74. package/templates/add/auth/migrations/create_users.up.sql.hbs +2 -1
  75. package/templates/add/rbac/internal/app/role/adapters/inbound/http/handler.go.hbs +142 -0
  76. package/templates/add/rbac/internal/app/role/adapters/inbound/http/handler_test.go.hbs +19 -0
  77. package/templates/add/rbac/internal/app/role/adapters/outbound/postgres/model.go.hbs +48 -0
  78. package/templates/add/rbac/internal/app/role/adapters/outbound/postgres/repository.go.hbs +127 -0
  79. package/templates/add/rbac/internal/app/role/{repository_test.go.hbs → adapters/outbound/postgres/repository_test.go.hbs} +8 -8
  80. package/templates/add/rbac/internal/app/role/application/dto.go.hbs +47 -0
  81. package/templates/add/rbac/internal/app/role/application/errors.go.hbs +19 -0
  82. package/templates/add/rbac/internal/app/role/application/service.go.hbs +157 -0
  83. package/templates/add/rbac/internal/app/role/{service_test.go.hbs → application/service_test.go.hbs} +26 -19
  84. package/templates/add/rbac/internal/app/role/composition.go.hbs +23 -10
  85. package/templates/add/rbac/internal/app/role/domain/entity.go.hbs +23 -0
  86. package/templates/add/rbac/internal/app/role/domain/errors.go.hbs +26 -0
  87. package/templates/add/rbac/internal/app/role/ports/repository.go.hbs +25 -0
  88. package/templates/add/rbac/migrations/add_roles.down.sql.hbs +3 -11
  89. package/templates/add/rbac/migrations/add_roles.up.sql.hbs +17 -6
  90. package/templates/add/worker/internal/platform/queue/river_test.go.hbs +84 -0
  91. package/templates/create/base/.claude/skills/go-scaffold/SKILL.md.hbs +54 -36
  92. package/templates/create/base/.golangci.yml.hbs +2 -2
  93. package/templates/create/base/AGENTS.md.hbs +55 -31
  94. package/templates/create/base/README.md.hbs +77 -22
  95. package/templates/create/base/cmd/api/wiring.go.hbs +3 -2
  96. package/templates/create/base/internal/composition/doc.go.hbs +7 -0
  97. package/templates/create/features/docs/architecture.md.hbs +64 -26
  98. package/templates/create/features/docs/patterns.md.hbs +117 -90
  99. package/templates/create/features/docs/techstack.md.hbs +17 -2
  100. package/templates/generate/module/hexagonal/adapters/inbound/http/dto.go.hbs +45 -0
  101. package/templates/generate/module/hexagonal/adapters/inbound/http/dto.minimal.go.hbs +28 -0
  102. package/templates/generate/module/hexagonal/adapters/inbound/http/handler.go.hbs +182 -0
  103. package/templates/generate/module/hexagonal/adapters/inbound/http/handler.minimal.go.hbs +83 -0
  104. package/templates/generate/module/hexagonal/adapters/inbound/http/handler_crud_test.go.hbs +18 -0
  105. package/templates/generate/module/hexagonal/adapters/inbound/http/handler_test.go.hbs +30 -0
  106. package/templates/generate/module/hexagonal/adapters/outbound/postgres/model.go.hbs +37 -0
  107. package/templates/generate/module/hexagonal/adapters/outbound/postgres/repository.go.hbs +95 -0
  108. package/templates/generate/module/{repository_test.go.hbs → hexagonal/adapters/outbound/postgres/repository_test.go.hbs} +8 -8
  109. package/templates/generate/module/hexagonal/application/commands.crud.go.hbs +54 -0
  110. package/templates/generate/module/hexagonal/application/commands.go.hbs +25 -0
  111. package/templates/generate/module/hexagonal/application/cqrs_test.go.hbs +66 -0
  112. package/templates/generate/module/hexagonal/application/dto.go.hbs +35 -0
  113. package/templates/generate/module/hexagonal/application/dto.minimal.go.hbs +25 -0
  114. package/templates/generate/module/hexagonal/application/queries.crud.go.hbs +33 -0
  115. package/templates/generate/module/hexagonal/application/queries.go.hbs +25 -0
  116. package/templates/generate/module/hexagonal/application/service.crud.go.hbs +73 -0
  117. package/templates/generate/module/hexagonal/application/service.go.hbs +29 -0
  118. package/templates/generate/module/hexagonal/application/service_test.go.hbs +62 -0
  119. package/templates/generate/module/hexagonal/composition.go.hbs +27 -0
  120. package/templates/generate/module/hexagonal/domain/entity.go.hbs +20 -0
  121. package/templates/generate/module/hexagonal/domain/errors.go.hbs +11 -0
  122. package/templates/generate/module/hexagonal/ports/repository.go.hbs +38 -0
  123. package/templates/generate/module/migration.up.sql.hbs +1 -1
  124. package/dist/utils/method-patcher.js +0 -537
  125. package/templates/add/auth/internal/app/user/contracts.go.hbs +0 -88
  126. package/templates/add/auth/internal/app/user/dto.go.hbs +0 -134
  127. package/templates/add/auth/internal/app/user/errors.go.hbs +0 -68
  128. package/templates/add/auth/internal/app/user/model/authtoken.go.hbs +0 -45
  129. package/templates/add/auth/internal/app/user/model/identity.go.hbs +0 -32
  130. package/templates/add/auth/internal/app/user/model/loginthrottle.go.hbs +0 -26
  131. package/templates/add/auth/internal/app/user/model/mfa_challenge.go.hbs +0 -17
  132. package/templates/add/auth/internal/app/user/model/mfa_enrollment.go.hbs +0 -20
  133. package/templates/add/auth/internal/app/user/model/mfa_recovery_code.go.hbs +0 -17
  134. package/templates/add/auth/internal/app/user/model/user.go.hbs +0 -31
  135. package/templates/add/auth/internal/app/user/repository.go.hbs +0 -139
  136. package/templates/add/auth/internal/app/user/service.go.hbs +0 -135
  137. package/templates/add/auth/internal/app/user/tokenstore.go.hbs +0 -58
  138. package/templates/add/rbac/internal/app/role/dto.go.hbs +0 -45
  139. package/templates/add/rbac/internal/app/role/errors.go.hbs +0 -39
  140. package/templates/add/rbac/internal/app/role/handler.go.hbs +0 -104
  141. package/templates/add/rbac/internal/app/role/model/permission.go.hbs +0 -12
  142. package/templates/add/rbac/internal/app/role/model/role.go.hbs +0 -22
  143. package/templates/add/rbac/internal/app/role/model/role_permission.go.hbs +0 -11
  144. package/templates/add/rbac/internal/app/role/repository.go.hbs +0 -97
  145. package/templates/add/rbac/internal/app/role/service.go.hbs +0 -217
  146. package/templates/generate/module/commands.go.hbs +0 -95
  147. package/templates/generate/module/composition.go.hbs +0 -23
  148. package/templates/generate/module/cqrs_test.go.hbs +0 -7
  149. package/templates/generate/module/dto.go.hbs +0 -36
  150. package/templates/generate/module/errors.go.hbs +0 -33
  151. package/templates/generate/module/handler.go.hbs +0 -179
  152. package/templates/generate/module/handler_test.go.hbs +0 -174
  153. package/templates/generate/module/minimal/commands.go.hbs +0 -34
  154. package/templates/generate/module/minimal/dto.go.hbs +0 -28
  155. package/templates/generate/module/minimal/handler.go.hbs +0 -82
  156. package/templates/generate/module/minimal/handler_test.go.hbs +0 -10
  157. package/templates/generate/module/minimal/queries.go.hbs +0 -45
  158. package/templates/generate/module/minimal/service.go.hbs +0 -71
  159. package/templates/generate/module/minimal/service_test.go.hbs +0 -77
  160. package/templates/generate/module/model/model.go.hbs +0 -36
  161. package/templates/generate/module/queries.go.hbs +0 -62
  162. package/templates/generate/module/repository.go.hbs +0 -103
  163. package/templates/generate/module/service.go.hbs +0 -164
  164. package/templates/generate/module/service_test.go.hbs +0 -161
@@ -0,0 +1,25 @@
1
+ package application
2
+
3
+ import (
4
+ "time"
5
+
6
+ "{{goModule}}/internal/app/{{modulePath}}/domain"
7
+
8
+ "github.com/google/uuid"
9
+ )
10
+
11
+ // A lean module starts with no use-case input type. `generate method` adds
12
+ // the exact input beside the endpoint that needs it.
13
+ // Response is a transport-neutral application output. The inbound adapter
14
+ // maps it to its protocol response before serialization.
15
+ type Response struct {
16
+ ID uuid.UUID
17
+ CreatedAt time.Time
18
+ Version int
19
+ }
20
+
21
+ func ToResponse(m *domain.{{pascalName}}) Response {
22
+ return Response{ID: m.ID, CreatedAt: m.CreatedAt, Version: m.Version}
23
+ }
24
+
25
+ // go-scaffold:dto
@@ -0,0 +1,33 @@
1
+ package application
2
+
3
+ import (
4
+ "context"
5
+ "{{goModule}}/internal/app/{{modulePath}}/domain"
6
+ "{{goModule}}/internal/app/{{modulePath}}/ports"
7
+
8
+ "github.com/google/uuid"
9
+ )
10
+
11
+ type QueryPort interface {
12
+ List(context.Context, int, int) ([]domain.{{pascalName}}, error)
13
+ Get(context.Context, uuid.UUID) (*domain.{{pascalName}}, error)
14
+ // go-scaffold:query-interface
15
+ }
16
+
17
+ type QueryHandler struct {
18
+ repo ports.QueryRepository
19
+ }
20
+
21
+ func NewQueryHandler(repo ports.QueryRepository) *QueryHandler {
22
+ return &QueryHandler{repo: repo}
23
+ }
24
+
25
+ func (h *QueryHandler) List(ctx context.Context, limit, offset int) ([]domain.{{pascalName}}, error) {
26
+ return h.repo.FindAll(ctx, limit, offset)
27
+ }
28
+
29
+ func (h *QueryHandler) Get(ctx context.Context, id uuid.UUID) (*domain.{{pascalName}}, error) {
30
+ return h.repo.FindByID(ctx, id)
31
+ }
32
+
33
+ // go-scaffold:query-methods
@@ -0,0 +1,25 @@
1
+ package application
2
+
3
+ import (
4
+ "context"
5
+
6
+ "{{goModule}}/internal/app/{{modulePath}}/domain"
7
+ "{{goModule}}/internal/app/{{modulePath}}/ports"
8
+ )
9
+
10
+ type QueryPort interface {
11
+ // go-scaffold:query-interface
12
+ }
13
+
14
+ type QueryHandler struct {
15
+ repo ports.QueryRepository
16
+ }
17
+
18
+ func NewQueryHandler(repo ports.QueryRepository) *QueryHandler {
19
+ return &QueryHandler{repo: repo}
20
+ }
21
+
22
+ var _ context.Context
23
+ var _ *domain.{{pascalName}}
24
+
25
+ // go-scaffold:query-methods
@@ -0,0 +1,73 @@
1
+ package application
2
+
3
+ import (
4
+ "context"
5
+ "errors"
6
+
7
+ "{{goModule}}/internal/app/{{modulePath}}/domain"
8
+ "{{goModule}}/internal/app/{{modulePath}}/ports"
9
+ "{{goModule}}/internal/shared/id"
10
+
11
+ "github.com/google/uuid"
12
+ )
13
+
14
+ type ServicePort interface {
15
+ Create(context.Context, CreateInput) (*domain.{{pascalName}}, error)
16
+ List(context.Context, int, int) ([]domain.{{pascalName}}, error)
17
+ Get(context.Context, uuid.UUID) (*domain.{{pascalName}}, error)
18
+ Update(context.Context, uuid.UUID, UpdateInput) (*domain.{{pascalName}}, error)
19
+ Delete(context.Context, uuid.UUID) error
20
+ // go-scaffold:service-interface
21
+ }
22
+
23
+ type Service struct {
24
+ repo ports.Repository
25
+ }
26
+
27
+ func NewService(repo ports.Repository) *Service {
28
+ return &Service{repo: repo}
29
+ }
30
+
31
+ func (s *Service) Create(ctx context.Context, in CreateInput) (*domain.{{pascalName}}, error) {
32
+ // TODO: map in to real domain fields and enforce invariants.
33
+ _ = in
34
+ m := &domain.{{pascalName}}{ID: id.New(), Version: 1}
35
+ if err := s.repo.Create(ctx, m); err != nil {
36
+ return nil, err
37
+ }
38
+ return m, nil
39
+ }
40
+
41
+ func (s *Service) List(ctx context.Context, limit, offset int) ([]domain.{{pascalName}}, error) {
42
+ return s.repo.FindAll(ctx, limit, offset)
43
+ }
44
+
45
+ func (s *Service) Get(ctx context.Context, id uuid.UUID) (*domain.{{pascalName}}, error) {
46
+ return s.repo.FindByID(ctx, id)
47
+ }
48
+
49
+ func (s *Service) Update(ctx context.Context, id uuid.UUID, in UpdateInput) (*domain.{{pascalName}}, error) {
50
+ m, err := s.repo.FindByID(ctx, id)
51
+ if err != nil {
52
+ return nil, err
53
+ }
54
+ // TODO: map in to real domain fields before saving.
55
+ m.Version = in.Version
56
+ if err := s.repo.Update(ctx, m); err != nil {
57
+ return nil, err
58
+ }
59
+ return m, nil
60
+ }
61
+
62
+ func (s *Service) Delete(ctx context.Context, id uuid.UUID) error {
63
+ if err := s.repo.Delete(ctx, id); err != nil {
64
+ return err
65
+ }
66
+ return nil
67
+ }
68
+
69
+ // IsNotFound is useful to non-HTTP callers without exposing persistence
70
+ // errors. The inbound HTTP adapter maps the same domain sentinel to 404.
71
+ func IsNotFound(err error) bool { return errors.Is(err, domain.ErrNotFound) }
72
+
73
+ // go-scaffold:service-methods
@@ -0,0 +1,29 @@
1
+ package application
2
+
3
+ import (
4
+ "context"
5
+
6
+ "{{goModule}}/internal/app/{{modulePath}}/domain"
7
+ "{{goModule}}/internal/app/{{modulePath}}/ports"
8
+ )
9
+
10
+ // ServicePort is the inbound application port used by the HTTP adapter.
11
+ type ServicePort interface {
12
+ // go-scaffold:service-interface
13
+ }
14
+
15
+ // Service orchestrates {{pkg}} use cases and depends only on ports.
16
+ type Service struct {
17
+ repo ports.Repository
18
+ }
19
+
20
+ func NewService(repo ports.Repository) *Service {
21
+ return &Service{repo: repo}
22
+ }
23
+
24
+ // Keep these imports and the port visible in a lean module before its first
25
+ // method is generated. Methods are added to this file by `generate method`.
26
+ var _ context.Context
27
+ var _ *domain.{{pascalName}}
28
+
29
+ // go-scaffold:service-methods
@@ -0,0 +1,62 @@
1
+ package application
2
+
3
+ import (
4
+ "context"
5
+ "testing"
6
+
7
+ "{{goModule}}/internal/app/{{modulePath}}/domain"
8
+
9
+ "github.com/google/uuid"
10
+ )
11
+
12
+ // repositoryStub exposes one function per dependency operation. Focused
13
+ // application tests can configure only the calls they expect; an unexpected
14
+ // call panics instead of silently returning a zero value that hides broken
15
+ // orchestration.
16
+ type repositoryStub struct {
17
+ createFn func(context.Context, *domain.{{pascalName}}) error
18
+ findAllFn func(context.Context, int, int) ([]domain.{{pascalName}}, error)
19
+ findByIDFn func(context.Context, uuid.UUID) (*domain.{{pascalName}}, error)
20
+ updateFn func(context.Context, *domain.{{pascalName}}) error
21
+ deleteFn func(context.Context, uuid.UUID) error
22
+ // go-scaffold:repository-stub-fields
23
+ }
24
+
25
+ func (s *repositoryStub) Create(ctx context.Context, m *domain.{{pascalName}}) error {
26
+ if s.createFn == nil {
27
+ panic("unexpected repository.Create call")
28
+ }
29
+ return s.createFn(ctx, m)
30
+ }
31
+ func (s *repositoryStub) FindAll(ctx context.Context, limit, offset int) ([]domain.{{pascalName}}, error) {
32
+ if s.findAllFn == nil {
33
+ panic("unexpected repository.FindAll call")
34
+ }
35
+ return s.findAllFn(ctx, limit, offset)
36
+ }
37
+ func (s *repositoryStub) FindByID(ctx context.Context, id uuid.UUID) (*domain.{{pascalName}}, error) {
38
+ if s.findByIDFn == nil {
39
+ panic("unexpected repository.FindByID call")
40
+ }
41
+ return s.findByIDFn(ctx, id)
42
+ }
43
+ func (s *repositoryStub) Update(ctx context.Context, m *domain.{{pascalName}}) error {
44
+ if s.updateFn == nil {
45
+ panic("unexpected repository.Update call")
46
+ }
47
+ return s.updateFn(ctx, m)
48
+ }
49
+ func (s *repositoryStub) Delete(ctx context.Context, id uuid.UUID) error {
50
+ if s.deleteFn == nil {
51
+ panic("unexpected repository.Delete call")
52
+ }
53
+ return s.deleteFn(ctx, id)
54
+ }
55
+
56
+ func TestService_ComposesWithRepositoryPort(t *testing.T) {
57
+ if NewService(&repositoryStub{}) == nil {
58
+ t.Fatal("expected service")
59
+ }
60
+ }
61
+
62
+ // go-scaffold:repository-stub-methods
@@ -0,0 +1,27 @@
1
+ package {{pkg}}
2
+
3
+ import (
4
+ httpadapter "{{goModule}}/internal/app/{{modulePath}}/adapters/inbound/http"
5
+ "{{goModule}}/internal/app/{{modulePath}}/adapters/outbound/postgres"
6
+ "{{goModule}}/internal/app/{{modulePath}}/application"
7
+ {{#unless cqrs}}
8
+ "{{goModule}}/internal/app/{{modulePath}}/ports"
9
+ {{/unless}}
10
+ "gorm.io/gorm"
11
+ {{#if permission}} "{{goModule}}/internal/shared/middleware"
12
+ {{/if}}
13
+ )
14
+
15
+ // NewHandlerFromDB is this module's composition root. The process root owns
16
+ // the database handle; this package owns the feature's adapter graph.
17
+ func NewHandlerFromDB(db *gorm.DB{{#if auth}}, jwtSecret string{{/if}}{{#if permission}}, authz *middleware.Authz{{/if}}) *httpadapter.Handler {
18
+ repo := postgres.NewRepository(db)
19
+ {{#if cqrs}}
20
+ commands := application.NewCommandHandler(repo)
21
+ queries := application.NewQueryHandler(repo)
22
+ return httpadapter.NewHandler(commands, queries{{#if auth}}, jwtSecret{{/if}}{{#if permission}}, authz{{/if}})
23
+ {{else}}
24
+ var service ports.Repository = repo
25
+ return httpadapter.NewHandler(application.NewService(service){{#if auth}}, jwtSecret{{/if}}{{#if permission}}, authz{{/if}})
26
+ {{/if}}
27
+ }
@@ -0,0 +1,20 @@
1
+ // Package domain contains {{pkg}}'s business state and invariants. It does
2
+ // not import Gin, GORM, SQL drivers, or HTTP concerns.
3
+ package domain
4
+
5
+ import (
6
+ "time"
7
+
8
+ "github.com/google/uuid"
9
+ )
10
+
11
+ // {{pascalName}} is the domain entity. Persistence and transport models are
12
+ // deliberately separate so a database column or JSON field cannot silently
13
+ // become part of the business API.
14
+ type {{pascalName}} struct {
15
+ ID uuid.UUID
16
+ CreatedAt time.Time
17
+ UpdatedAt time.Time
18
+ Version int
19
+ // TODO: add business fields and invariants here.
20
+ }
@@ -0,0 +1,11 @@
1
+ package domain
2
+
3
+ import "errors"
4
+
5
+ var (
6
+ ErrNotFound = errors.New("{{pkg}} not found")
7
+ ErrConflict = errors.New("{{pkg}} conflicts with an existing record")
8
+ ErrStaleVersion = errors.New("{{pkg}} was modified by someone else")
9
+ ErrHasReferences = errors.New("{{pkg}} still has related records")
10
+ ErrNotImplemented = errors.New("{{pkg}} operation is not implemented")
11
+ )
@@ -0,0 +1,38 @@
1
+ // Package ports declares the dependencies owned by the {{pkg}} application.
2
+ // Adapters implement these interfaces; application code never imports a
3
+ // database driver.
4
+ package ports
5
+
6
+ import (
7
+ "context"
8
+
9
+ "{{goModule}}/internal/app/{{modulePath}}/domain"
10
+
11
+ "github.com/google/uuid"
12
+ )
13
+
14
+ // Repository is the complete persistence port for service-style modules.
15
+ type Repository interface {
16
+ Create(context.Context, *domain.{{pascalName}}) error
17
+ FindAll(context.Context, int, int) ([]domain.{{pascalName}}, error)
18
+ FindByID(context.Context, uuid.UUID) (*domain.{{pascalName}}, error)
19
+ Update(context.Context, *domain.{{pascalName}}) error
20
+ Delete(context.Context, uuid.UUID) error
21
+ // go-scaffold:repository-interface
22
+ }
23
+
24
+ // CommandRepository and QueryRepository keep CQRS handlers honest: a write
25
+ // use case cannot grow a read dependency by accident, and vice versa.
26
+ type CommandRepository interface {
27
+ Create(context.Context, *domain.{{pascalName}}) error
28
+ FindByID(context.Context, uuid.UUID) (*domain.{{pascalName}}, error)
29
+ Update(context.Context, *domain.{{pascalName}}) error
30
+ Delete(context.Context, uuid.UUID) error
31
+ // go-scaffold:command-repository-interface
32
+ }
33
+
34
+ type QueryRepository interface {
35
+ FindAll(context.Context, int, int) ([]domain.{{pascalName}}, error)
36
+ FindByID(context.Context, uuid.UUID) (*domain.{{pascalName}}, error)
37
+ // go-scaffold:query-repository-interface
38
+ }
@@ -1,4 +1,4 @@
1
- -- one schema per domain — see model.go's TableName for why
1
+ -- one schema per domain — see adapters/outbound/postgres/model.go's TableName for why
2
2
  CREATE SCHEMA IF NOT EXISTS {{schemaName}};
3
3
 
4
4
  CREATE TABLE {{schemaName}}.{{tableName}} (