@nakedev/go-scaffold 0.4.3 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +567 -513
- package/dist/commands/auth.js +12 -1
- package/dist/commands/check.js +281 -0
- package/dist/commands/create.js +2 -1
- package/dist/commands/generate.js +5 -2
- package/dist/commands/method.js +66 -70
- package/dist/commands/observability.js +4 -53
- package/dist/commands/rbac.js +19 -8
- package/dist/commands/undo.js +6 -3
- package/dist/commands/worker.js +14 -4
- package/dist/index.js +13 -1
- package/dist/templates/auth-manifest.js +46 -45
- package/dist/templates/create-manifest.js +4 -0
- package/dist/templates/module-manifest.js +82 -60
- package/dist/templates/rbac-manifest.js +15 -11
- package/dist/templates/worker-manifest.js +4 -1
- package/dist/types.js +2 -0
- package/dist/utils/auth-patcher.js +22 -22
- package/dist/utils/config.js +24 -5
- package/dist/utils/docs-patcher.js +68 -0
- package/dist/utils/hexagonal-method-patcher.js +334 -0
- package/dist/utils/main-patcher.js +3 -3
- package/dist/utils/module-location.js +17 -11
- package/dist/utils/platform-patcher.js +27 -0
- package/dist/utils/rbac-patcher.js +73 -216
- package/package.json +1 -1
- package/templates/add/auth/cmd/seed/main.go.hbs +2 -0
- package/templates/add/auth/internal/app/user/{browser_policy.go.hbs → adapters/inbound/http/browser_policy.go.hbs} +3 -3
- package/templates/add/auth/internal/app/user/adapters/inbound/http/dto.go.hbs +159 -0
- package/templates/add/auth/internal/app/user/{handler.go.hbs → adapters/inbound/http/handler.go.hbs} +103 -8
- package/templates/add/auth/internal/app/user/{handler_local.go.hbs → adapters/inbound/http/handler_local.go.hbs} +8 -7
- package/templates/add/auth/internal/app/user/{handler_mfa.go.hbs → adapters/inbound/http/handler_mfa.go.hbs} +6 -6
- package/templates/add/auth/internal/app/user/{handler_oauth.go.hbs → adapters/inbound/http/handler_oauth.go.hbs} +17 -17
- package/templates/add/auth/internal/app/user/{handler_recovery.go.hbs → adapters/inbound/http/handler_recovery.go.hbs} +4 -4
- package/templates/add/auth/internal/app/user/{handler_test.go.hbs → adapters/inbound/http/handler_test.go.hbs} +52 -31
- package/templates/add/auth/internal/app/user/{handler_user.go.hbs → adapters/inbound/http/handler_user.go.hbs} +4 -4
- package/templates/add/auth/internal/app/user/{session_cookie.go.hbs → adapters/inbound/http/session_cookie.go.hbs} +5 -3
- package/templates/add/auth/internal/app/user/adapters/outbound/password/bcrypt.go.hbs +35 -0
- package/templates/add/auth/internal/app/user/adapters/outbound/password/bcrypt_test.go.hbs +20 -0
- package/templates/add/auth/internal/app/user/{mfa_store.go.hbs → adapters/outbound/postgres/mfa_store.go.hbs} +22 -20
- package/templates/add/auth/internal/app/user/{mfa_store_test.go.hbs → adapters/outbound/postgres/mfa_store_test.go.hbs} +7 -7
- package/templates/add/auth/internal/app/user/adapters/outbound/postgres/model.go.hbs +84 -0
- package/templates/add/auth/internal/app/user/adapters/outbound/postgres/repository.go.hbs +211 -0
- package/templates/add/auth/internal/app/user/{repository_test.go.hbs → adapters/outbound/postgres/repository_test.go.hbs} +18 -19
- package/templates/add/auth/internal/app/user/{tokenstore_pg.go.hbs → adapters/outbound/postgres/tokenstore_pg.go.hbs} +36 -32
- package/templates/add/auth/internal/app/user/{tokenstore_pg_test.go.hbs → adapters/outbound/postgres/tokenstore_pg_test.go.hbs} +10 -3
- package/templates/add/auth/internal/app/user/{tokenstore_recovery.go.hbs → adapters/outbound/postgres/tokenstore_recovery.go.hbs} +29 -3
- package/templates/add/auth/internal/app/user/{tokenstore_redis.go.hbs → adapters/outbound/redis/tokenstore.go.hbs} +43 -36
- package/templates/add/auth/internal/app/user/{tokenstore_redis_test.go.hbs → adapters/outbound/redis/tokenstore_test.go.hbs} +14 -3
- package/templates/add/auth/internal/app/user/application/contracts.go.hbs +52 -0
- package/templates/add/auth/internal/app/user/application/dto.go.hbs +75 -0
- package/templates/add/auth/internal/app/user/application/errors.go.hbs +62 -0
- package/templates/add/auth/internal/app/user/{external_login.go.hbs → application/external_login.go.hbs} +30 -40
- package/templates/add/auth/internal/app/user/{jwt.go.hbs → application/jwt.go.hbs} +6 -3
- package/templates/add/auth/internal/app/user/{local_auth.go.hbs → application/local_auth.go.hbs} +17 -19
- package/templates/add/auth/internal/app/user/{mfa_service.go.hbs → application/mfa_service.go.hbs} +27 -28
- package/templates/add/auth/internal/app/user/{mfa_service_test.go.hbs → application/mfa_service_test.go.hbs} +12 -11
- package/templates/add/auth/internal/app/user/application/oauth.go.hbs +3 -3
- package/templates/add/auth/internal/app/user/{provider_test.go.hbs → application/provider_test.go.hbs} +55 -56
- package/templates/add/auth/internal/app/user/application/recovery.go.hbs +16 -47
- package/templates/add/auth/internal/app/user/{recovery_service.go.hbs → application/recovery_service.go.hbs} +13 -15
- package/templates/add/auth/internal/app/user/application/service.go.hbs +145 -0
- package/templates/add/auth/internal/app/user/{service_test.go.hbs → application/service_test.go.hbs} +94 -75
- package/templates/add/auth/internal/app/user/{sessions.go.hbs → application/sessions.go.hbs} +14 -14
- package/templates/add/auth/internal/app/user/application/tokenstore_ports.go.hbs +14 -0
- package/templates/add/auth/internal/app/user/{user_query.go.hbs → application/user_query.go.hbs} +16 -16
- package/templates/add/auth/internal/app/user/composition.go.hbs +89 -86
- package/templates/add/auth/internal/app/user/domain/entity.go.hbs +41 -0
- package/templates/add/auth/internal/app/user/domain/errors.go.hbs +32 -0
- package/templates/add/auth/internal/app/user/ports/password.go.hbs +9 -0
- package/templates/add/auth/internal/app/user/ports/repository.go.hbs +90 -0
- package/templates/add/auth/migrations/create_auth_tokens.up.sql.hbs +1 -1
- package/templates/add/auth/migrations/create_login_throttle.up.sql.hbs +1 -1
- package/templates/add/auth/migrations/create_users.up.sql.hbs +2 -1
- package/templates/add/rbac/internal/app/role/adapters/inbound/http/handler.go.hbs +142 -0
- package/templates/add/rbac/internal/app/role/adapters/inbound/http/handler_test.go.hbs +19 -0
- package/templates/add/rbac/internal/app/role/adapters/outbound/postgres/model.go.hbs +48 -0
- package/templates/add/rbac/internal/app/role/adapters/outbound/postgres/repository.go.hbs +127 -0
- package/templates/add/rbac/internal/app/role/{repository_test.go.hbs → adapters/outbound/postgres/repository_test.go.hbs} +8 -8
- package/templates/add/rbac/internal/app/role/application/dto.go.hbs +47 -0
- package/templates/add/rbac/internal/app/role/application/errors.go.hbs +19 -0
- package/templates/add/rbac/internal/app/role/application/service.go.hbs +157 -0
- package/templates/add/rbac/internal/app/role/{service_test.go.hbs → application/service_test.go.hbs} +26 -19
- package/templates/add/rbac/internal/app/role/composition.go.hbs +23 -10
- package/templates/add/rbac/internal/app/role/domain/entity.go.hbs +23 -0
- package/templates/add/rbac/internal/app/role/domain/errors.go.hbs +26 -0
- package/templates/add/rbac/internal/app/role/ports/repository.go.hbs +25 -0
- package/templates/add/rbac/migrations/add_roles.down.sql.hbs +3 -11
- package/templates/add/rbac/migrations/add_roles.up.sql.hbs +17 -6
- package/templates/add/worker/internal/platform/queue/river_test.go.hbs +84 -0
- package/templates/create/base/.claude/skills/go-scaffold/SKILL.md.hbs +54 -36
- package/templates/create/base/.golangci.yml.hbs +2 -2
- package/templates/create/base/AGENTS.md.hbs +55 -31
- package/templates/create/base/README.md.hbs +77 -22
- package/templates/create/base/cmd/api/wiring.go.hbs +3 -2
- package/templates/create/base/internal/composition/doc.go.hbs +7 -0
- package/templates/create/features/docs/architecture.md.hbs +64 -26
- package/templates/create/features/docs/patterns.md.hbs +117 -90
- package/templates/create/features/docs/techstack.md.hbs +17 -2
- package/templates/generate/module/hexagonal/adapters/inbound/http/dto.go.hbs +45 -0
- package/templates/generate/module/hexagonal/adapters/inbound/http/dto.minimal.go.hbs +28 -0
- package/templates/generate/module/hexagonal/adapters/inbound/http/handler.go.hbs +182 -0
- package/templates/generate/module/hexagonal/adapters/inbound/http/handler.minimal.go.hbs +83 -0
- package/templates/generate/module/hexagonal/adapters/inbound/http/handler_crud_test.go.hbs +18 -0
- package/templates/generate/module/hexagonal/adapters/inbound/http/handler_test.go.hbs +30 -0
- package/templates/generate/module/hexagonal/adapters/outbound/postgres/model.go.hbs +37 -0
- package/templates/generate/module/hexagonal/adapters/outbound/postgres/repository.go.hbs +95 -0
- package/templates/generate/module/{repository_test.go.hbs → hexagonal/adapters/outbound/postgres/repository_test.go.hbs} +8 -8
- package/templates/generate/module/hexagonal/application/commands.crud.go.hbs +54 -0
- package/templates/generate/module/hexagonal/application/commands.go.hbs +25 -0
- package/templates/generate/module/hexagonal/application/cqrs_test.go.hbs +66 -0
- package/templates/generate/module/hexagonal/application/dto.go.hbs +35 -0
- package/templates/generate/module/hexagonal/application/dto.minimal.go.hbs +25 -0
- package/templates/generate/module/hexagonal/application/queries.crud.go.hbs +33 -0
- package/templates/generate/module/hexagonal/application/queries.go.hbs +25 -0
- package/templates/generate/module/hexagonal/application/service.crud.go.hbs +73 -0
- package/templates/generate/module/hexagonal/application/service.go.hbs +29 -0
- package/templates/generate/module/hexagonal/application/service_test.go.hbs +62 -0
- package/templates/generate/module/hexagonal/composition.go.hbs +27 -0
- package/templates/generate/module/hexagonal/domain/entity.go.hbs +20 -0
- package/templates/generate/module/hexagonal/domain/errors.go.hbs +11 -0
- package/templates/generate/module/hexagonal/ports/repository.go.hbs +38 -0
- package/templates/generate/module/migration.up.sql.hbs +1 -1
- package/dist/utils/method-patcher.js +0 -537
- package/templates/add/auth/internal/app/user/contracts.go.hbs +0 -88
- package/templates/add/auth/internal/app/user/dto.go.hbs +0 -134
- package/templates/add/auth/internal/app/user/errors.go.hbs +0 -68
- package/templates/add/auth/internal/app/user/model/authtoken.go.hbs +0 -45
- package/templates/add/auth/internal/app/user/model/identity.go.hbs +0 -32
- package/templates/add/auth/internal/app/user/model/loginthrottle.go.hbs +0 -26
- package/templates/add/auth/internal/app/user/model/mfa_challenge.go.hbs +0 -17
- package/templates/add/auth/internal/app/user/model/mfa_enrollment.go.hbs +0 -20
- package/templates/add/auth/internal/app/user/model/mfa_recovery_code.go.hbs +0 -17
- package/templates/add/auth/internal/app/user/model/user.go.hbs +0 -31
- package/templates/add/auth/internal/app/user/repository.go.hbs +0 -139
- package/templates/add/auth/internal/app/user/service.go.hbs +0 -135
- package/templates/add/auth/internal/app/user/tokenstore.go.hbs +0 -58
- package/templates/add/rbac/internal/app/role/dto.go.hbs +0 -45
- package/templates/add/rbac/internal/app/role/errors.go.hbs +0 -39
- package/templates/add/rbac/internal/app/role/handler.go.hbs +0 -104
- package/templates/add/rbac/internal/app/role/model/permission.go.hbs +0 -12
- package/templates/add/rbac/internal/app/role/model/role.go.hbs +0 -22
- package/templates/add/rbac/internal/app/role/model/role_permission.go.hbs +0 -11
- package/templates/add/rbac/internal/app/role/repository.go.hbs +0 -97
- package/templates/add/rbac/internal/app/role/service.go.hbs +0 -217
- package/templates/generate/module/commands.go.hbs +0 -95
- package/templates/generate/module/composition.go.hbs +0 -23
- package/templates/generate/module/cqrs_test.go.hbs +0 -7
- package/templates/generate/module/dto.go.hbs +0 -36
- package/templates/generate/module/errors.go.hbs +0 -33
- package/templates/generate/module/handler.go.hbs +0 -179
- package/templates/generate/module/handler_test.go.hbs +0 -174
- package/templates/generate/module/minimal/commands.go.hbs +0 -34
- package/templates/generate/module/minimal/dto.go.hbs +0 -28
- package/templates/generate/module/minimal/handler.go.hbs +0 -82
- package/templates/generate/module/minimal/handler_test.go.hbs +0 -10
- package/templates/generate/module/minimal/queries.go.hbs +0 -45
- package/templates/generate/module/minimal/service.go.hbs +0 -71
- package/templates/generate/module/minimal/service_test.go.hbs +0 -77
- package/templates/generate/module/model/model.go.hbs +0 -36
- package/templates/generate/module/queries.go.hbs +0 -62
- package/templates/generate/module/repository.go.hbs +0 -103
- package/templates/generate/module/service.go.hbs +0 -164
- package/templates/generate/module/service_test.go.hbs +0 -161
|
@@ -0,0 +1,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
|
+
}
|