@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
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
package {{pkg}}
|
|
2
|
-
|
|
3
|
-
import (
|
|
4
|
-
"context"
|
|
5
|
-
"errors"
|
|
6
|
-
|
|
7
|
-
"{{goModule}}/internal/app/{{modulePath}}/model"
|
|
8
|
-
"{{goModule}}/internal/shared/apperror"
|
|
9
|
-
"{{goModule}}/internal/shared/dberr"
|
|
10
|
-
"{{goModule}}/internal/shared/id"
|
|
11
|
-
|
|
12
|
-
"github.com/google/uuid"
|
|
13
|
-
"gorm.io/gorm"
|
|
14
|
-
)
|
|
15
|
-
|
|
16
|
-
// commandRepository is the outbound port for state-changing use cases. It is
|
|
17
|
-
// intentionally smaller than queryRepository so command handlers do not grow
|
|
18
|
-
// a dependency on read-only operations by accident.
|
|
19
|
-
type commandRepository interface {
|
|
20
|
-
Create(ctx context.Context, m *model.{{pascalName}}) error
|
|
21
|
-
FindByID(ctx context.Context, id uuid.UUID) (*model.{{pascalName}}, error)
|
|
22
|
-
Update(ctx context.Context, m *model.{{pascalName}}) error
|
|
23
|
-
Delete(ctx context.Context, id uuid.UUID) error
|
|
24
|
-
// go-scaffold:command-repository-interface
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// commandService is the inbound application port consumed by the HTTP
|
|
28
|
-
// adapter. Its implementation is free of Gin and is independently testable.
|
|
29
|
-
type commandService interface {
|
|
30
|
-
Create(context.Context, createInput) (*model.{{pascalName}}, error)
|
|
31
|
-
Update(context.Context, uuid.UUID, updateInput) (*model.{{pascalName}}, error)
|
|
32
|
-
Delete(context.Context, uuid.UUID) error
|
|
33
|
-
// go-scaffold:command-interface
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// CommandHandler owns state-changing use cases for {{pkg}}. It is deliberately
|
|
37
|
-
// separate from QueryHandler even though both adapters may share one database.
|
|
38
|
-
type CommandHandler struct {
|
|
39
|
-
repo commandRepository
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
func NewCommandHandler(repo commandRepository) *CommandHandler {
|
|
43
|
-
return &CommandHandler{repo: repo}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
func (h *CommandHandler) Create(ctx context.Context, in createInput) (*model.{{pascalName}}, error) {
|
|
47
|
-
// TODO: set real fields from in
|
|
48
|
-
_ = in
|
|
49
|
-
m := &model.{{pascalName}}{ID: id.New()}
|
|
50
|
-
if err := h.repo.Create(ctx, m); err != nil {
|
|
51
|
-
if dberr.IsDuplicate(err) {
|
|
52
|
-
return nil, errConflict()
|
|
53
|
-
}
|
|
54
|
-
return nil, apperror.NewInternal(err)
|
|
55
|
-
}
|
|
56
|
-
return m, nil
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
func (h *CommandHandler) Update(ctx context.Context, id uuid.UUID, in updateInput) (*model.{{pascalName}}, error) {
|
|
60
|
-
m, err := h.repo.FindByID(ctx, id)
|
|
61
|
-
if err != nil {
|
|
62
|
-
return nil, wrapFindErr(err)
|
|
63
|
-
}
|
|
64
|
-
// TODO: apply real fields from in before saving
|
|
65
|
-
// The version the client read comes from the request, not from the row we
|
|
66
|
-
// just loaded — comparing the row against itself would always succeed and
|
|
67
|
-
// defeat the check.
|
|
68
|
-
m.Version = in.Version
|
|
69
|
-
if err := h.repo.Update(ctx, m); err != nil {
|
|
70
|
-
if errors.Is(err, ErrStaleVersion) {
|
|
71
|
-
return nil, errStale()
|
|
72
|
-
}
|
|
73
|
-
if dberr.IsDuplicate(err) {
|
|
74
|
-
return nil, errConflict()
|
|
75
|
-
}
|
|
76
|
-
return nil, apperror.NewInternal(err)
|
|
77
|
-
}
|
|
78
|
-
return m, nil
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
func (h *CommandHandler) Delete(ctx context.Context, id uuid.UUID) error {
|
|
82
|
-
if err := h.repo.Delete(ctx, id); err != nil {
|
|
83
|
-
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
84
|
-
return errNotFound()
|
|
85
|
-
}
|
|
86
|
-
// deleting a {{pkg}} that's still referenced elsewhere → FK RESTRICT trips = 409, not 500
|
|
87
|
-
if dberr.IsForeignKey(err) {
|
|
88
|
-
return errHasReferences()
|
|
89
|
-
}
|
|
90
|
-
return apperror.NewInternal(err)
|
|
91
|
-
}
|
|
92
|
-
return nil
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// go-scaffold:command-methods
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
package {{pkg}}
|
|
2
|
-
|
|
3
|
-
import "gorm.io/gorm"
|
|
4
|
-
{{#if permission}}
|
|
5
|
-
import "{{goModule}}/internal/shared/middleware"
|
|
6
|
-
{{/if}}
|
|
7
|
-
|
|
8
|
-
// NewHandlerFromDB is this feature's local composition root. The API binary
|
|
9
|
-
// only chooses infrastructure and registers the route; repository, application
|
|
10
|
-
// handlers, and delivery construction stay next to the feature.
|
|
11
|
-
func NewHandlerFromDB(db *gorm.DB{{#if auth}}, jwtSecret string{{/if}}{{#if permission}}, authz *middleware.Authz{{/if}}) *Handler {
|
|
12
|
-
{{#if cqrs}}
|
|
13
|
-
repo := NewRepository(db)
|
|
14
|
-
return NewHandlerFromCQRS(
|
|
15
|
-
NewCommandHandler(repo),
|
|
16
|
-
NewQueryHandler(repo){{#if auth}}, jwtSecret{{/if}}{{#if permission}}, authz{{/if}},
|
|
17
|
-
)
|
|
18
|
-
{{else}}
|
|
19
|
-
return NewHandler(
|
|
20
|
-
NewService(NewRepository(db)){{#if auth}}, jwtSecret{{/if}}{{#if permission}}, authz{{/if}},
|
|
21
|
-
)
|
|
22
|
-
{{/if}}
|
|
23
|
-
}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
package {{pkg}}
|
|
2
|
-
|
|
3
|
-
// These compile-time assertions make the generated boundary visible to both
|
|
4
|
-
// users and tooling: commands and queries are separate application ports even
|
|
5
|
-
// when they share one Postgres adapter in this modular monolith.
|
|
6
|
-
var _ commandService = (*CommandHandler)(nil)
|
|
7
|
-
var _ queryService = (*QueryHandler)(nil)
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
package {{pkg}}
|
|
2
|
-
|
|
3
|
-
import (
|
|
4
|
-
"time"
|
|
5
|
-
|
|
6
|
-
"{{goModule}}/internal/app/{{modulePath}}/model"
|
|
7
|
-
|
|
8
|
-
"github.com/google/uuid"
|
|
9
|
-
)
|
|
10
|
-
|
|
11
|
-
// TODO: add request fields, e.g. Name string `json:"name" binding:"required"`
|
|
12
|
-
// When you do: update createBody in handler_test.go to match, or the
|
|
13
|
-
// generated create test starts failing on its own empty `{}` body.
|
|
14
|
-
type createInput struct {
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
// TODO: add request fields, e.g. Name string `json:"name" binding:"omitempty"`
|
|
18
|
-
type updateInput struct {
|
|
19
|
-
// Version the client last read. Required: without it an update is a blind
|
|
20
|
-
// overwrite of whatever anyone else has since saved.
|
|
21
|
-
Version int `json:"version" binding:"required"`
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
// response = the DTO sent out (kept separate from the model so a later DB column doesn't leak automatically)
|
|
25
|
-
type response struct {
|
|
26
|
-
ID uuid.UUID `json:"id"`
|
|
27
|
-
CreatedAt time.Time `json:"created_at"`
|
|
28
|
-
// echoed back so the client can send it with its next update
|
|
29
|
-
Version int `json:"version"`
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
func toResponse(m *model.{{pascalName}}) response {
|
|
33
|
-
return response{ID: m.ID, CreatedAt: m.CreatedAt, Version: m.Version}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// go-scaffold:dto
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
package {{pkg}}
|
|
2
|
-
|
|
3
|
-
import (
|
|
4
|
-
"net/http"
|
|
5
|
-
|
|
6
|
-
"{{goModule}}/internal/shared/apperror"
|
|
7
|
-
)
|
|
8
|
-
|
|
9
|
-
// error catalog specific to {{pkg}} — codes carry more meaning than the generic apperror ones
|
|
10
|
-
// functions, not vars: the error middleware writes RequestID onto the returned pointer directly
|
|
11
|
-
// (a shared instance would race across concurrent requests). Not every entry is called by every
|
|
12
|
-
// module (a minimal module calls none of these until `generate method` wires one in), hence nolint.
|
|
13
|
-
//
|
|
14
|
-
//nolint:unused
|
|
15
|
-
func errNotFound() *apperror.AppError {
|
|
16
|
-
return apperror.New(http.StatusNotFound, "{{errorPrefix}}_NOT_FOUND", "{{pkg}} not found")
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
//nolint:unused
|
|
20
|
-
func errConflict() *apperror.AppError {
|
|
21
|
-
// TODO: rename/reword once a real unique field exists (e.g. email already exists)
|
|
22
|
-
return apperror.New(http.StatusConflict, "{{errorPrefix}}_CONFLICT", "{{pkg}} already exists")
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
//nolint:unused
|
|
26
|
-
func errStale() *apperror.AppError {
|
|
27
|
-
return apperror.New(http.StatusConflict, "{{errorPrefix}}_STALE", "{{pkg}} was modified by someone else — reload and try again")
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
//nolint:unused
|
|
31
|
-
func errHasReferences() *apperror.AppError {
|
|
32
|
-
return apperror.New(http.StatusConflict, "{{errorPrefix}}_HAS_REFERENCES", "{{pkg}} still has related records")
|
|
33
|
-
}
|
|
@@ -1,179 +0,0 @@
|
|
|
1
|
-
package {{pkg}}
|
|
2
|
-
|
|
3
|
-
import (
|
|
4
|
-
"context"
|
|
5
|
-
"net/http"
|
|
6
|
-
|
|
7
|
-
"{{goModule}}/internal/app/{{modulePath}}/model"
|
|
8
|
-
"{{goModule}}/internal/shared/httpx"
|
|
9
|
-
{{#if auth}}
|
|
10
|
-
"{{goModule}}/internal/shared/middleware"
|
|
11
|
-
{{/if}}
|
|
12
|
-
"{{goModule}}/internal/shared/pagination"
|
|
13
|
-
|
|
14
|
-
"github.com/gin-gonic/gin"
|
|
15
|
-
"github.com/google/uuid"
|
|
16
|
-
)
|
|
17
|
-
|
|
18
|
-
{{#if cqrs}}
|
|
19
|
-
// service is retained as a compatibility seam for handler tests and callers
|
|
20
|
-
// that still want one feature facade. Production wiring uses the narrower
|
|
21
|
-
// commandService/queryService ports through NewHandlerFromCQRS.
|
|
22
|
-
type service interface {
|
|
23
|
-
commandService
|
|
24
|
-
queryService
|
|
25
|
-
Create(context.Context, createInput) (*model.{{pascalName}}, error)
|
|
26
|
-
List(context.Context, int, int) ([]model.{{pascalName}}, error)
|
|
27
|
-
Get(context.Context, uuid.UUID) (*model.{{pascalName}}, error)
|
|
28
|
-
Update(context.Context, uuid.UUID, updateInput) (*model.{{pascalName}}, error)
|
|
29
|
-
Delete(context.Context, uuid.UUID) error
|
|
30
|
-
// go-scaffold:service-interface
|
|
31
|
-
}
|
|
32
|
-
{{else}}
|
|
33
|
-
// service is the narrow application API required by this HTTP adapter. Keeping
|
|
34
|
-
// the dependency as an interface makes handler tests fast and database-free.
|
|
35
|
-
type service interface {
|
|
36
|
-
Create(context.Context, createInput) (*model.{{pascalName}}, error)
|
|
37
|
-
List(context.Context, int, int) ([]model.{{pascalName}}, error)
|
|
38
|
-
Get(context.Context, uuid.UUID) (*model.{{pascalName}}, error)
|
|
39
|
-
Update(context.Context, uuid.UUID, updateInput) (*model.{{pascalName}}, error)
|
|
40
|
-
Delete(context.Context, uuid.UUID) error
|
|
41
|
-
// go-scaffold:service-interface
|
|
42
|
-
}
|
|
43
|
-
{{/if}}
|
|
44
|
-
|
|
45
|
-
// Handler = delivery for {{pkg}} (parses HTTP, calls the service, attaches errors for the middleware to render)
|
|
46
|
-
type Handler struct {
|
|
47
|
-
{{#if cqrs}}
|
|
48
|
-
commands commandService
|
|
49
|
-
queries queryService
|
|
50
|
-
{{else}}
|
|
51
|
-
svc service
|
|
52
|
-
{{/if}}
|
|
53
|
-
{{#if auth}}
|
|
54
|
-
jwtSecret string
|
|
55
|
-
{{/if}}
|
|
56
|
-
{{#if permission}}
|
|
57
|
-
authz *middleware.Authz
|
|
58
|
-
{{/if}}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
{{#if cqrs}}
|
|
62
|
-
// NewHandler keeps the pre-CQRS constructor usable for focused handler tests
|
|
63
|
-
// and hand-written callers. New production composition should use the explicit
|
|
64
|
-
// command/query constructor below.
|
|
65
|
-
func NewHandler(svc service{{#if auth}}, jwtSecret string{{/if}}{{#if permission}}, authz *middleware.Authz{{/if}}) *Handler {
|
|
66
|
-
return NewHandlerFromCQRS(svc, svc{{#if auth}}, jwtSecret{{/if}}{{#if permission}}, authz{{/if}})
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
func NewHandlerFromCQRS(commands commandService, queries queryService{{#if auth}}, jwtSecret string{{/if}}{{#if permission}}, authz *middleware.Authz{{/if}}) *Handler {
|
|
70
|
-
return &Handler{
|
|
71
|
-
commands: commands,
|
|
72
|
-
queries: queries,
|
|
73
|
-
{{#if auth}}
|
|
74
|
-
jwtSecret: jwtSecret,
|
|
75
|
-
{{/if}}
|
|
76
|
-
{{#if permission}}
|
|
77
|
-
authz: authz,
|
|
78
|
-
{{/if}}
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
{{else}}
|
|
82
|
-
func NewHandler(svc service{{#if auth}}, jwtSecret string{{/if}}{{#if permission}}, authz *middleware.Authz{{/if}}) *Handler {
|
|
83
|
-
return &Handler{
|
|
84
|
-
svc: svc,
|
|
85
|
-
{{#if auth}}
|
|
86
|
-
jwtSecret: jwtSecret,
|
|
87
|
-
{{/if}}
|
|
88
|
-
{{#if permission}}
|
|
89
|
-
authz: authz,
|
|
90
|
-
{{/if}}
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
{{/if}}
|
|
94
|
-
|
|
95
|
-
// Register wires {{pkg}}'s routes onto the router group (takes an IRouter so it can be nested under /v1)
|
|
96
|
-
func (h *Handler) Register(rg gin.IRouter) {
|
|
97
|
-
g := rg.Group("/{{plural}}"{{#if auth}}, middleware.RequireAuth(h.jwtSecret){{/if}}{{#if permission}}, h.authz.Require("{{permission}}"){{/if}})
|
|
98
|
-
g.POST("", h.create)
|
|
99
|
-
g.GET("", h.list)
|
|
100
|
-
g.GET("/:id", h.get)
|
|
101
|
-
g.PUT("/:id", h.update)
|
|
102
|
-
g.DELETE("/:id", h.delete)
|
|
103
|
-
// go-scaffold:handler-routes
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
func (h *Handler) create(c *gin.Context) {
|
|
107
|
-
var in createInput
|
|
108
|
-
if err := c.ShouldBindJSON(&in); err != nil {
|
|
109
|
-
c.Error(httpx.BindErr(err))
|
|
110
|
-
return
|
|
111
|
-
}
|
|
112
|
-
{{#if cqrs}}m, err := h.commands.Create(c.Request.Context(), in){{else}}m, err := h.svc.Create(c.Request.Context(), in){{/if}}
|
|
113
|
-
if err != nil {
|
|
114
|
-
c.Error(err)
|
|
115
|
-
return
|
|
116
|
-
}
|
|
117
|
-
c.JSON(http.StatusCreated, toResponse(m))
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
func (h *Handler) list(c *gin.Context) {
|
|
121
|
-
p := pagination.Parse(c)
|
|
122
|
-
{{#if cqrs}}items, err := h.queries.List(c.Request.Context(), p.Limit, p.Offset){{else}}items, err := h.svc.List(c.Request.Context(), p.Limit, p.Offset){{/if}}
|
|
123
|
-
if err != nil {
|
|
124
|
-
c.Error(err)
|
|
125
|
-
return
|
|
126
|
-
}
|
|
127
|
-
out := make([]response, len(items))
|
|
128
|
-
for i := range items {
|
|
129
|
-
out[i] = toResponse(&items[i])
|
|
130
|
-
}
|
|
131
|
-
c.JSON(http.StatusOK, p.Response(out))
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
func (h *Handler) get(c *gin.Context) {
|
|
135
|
-
id, ok := httpx.ParseID(c)
|
|
136
|
-
if !ok {
|
|
137
|
-
return
|
|
138
|
-
}
|
|
139
|
-
{{#if cqrs}}m, err := h.queries.Get(c.Request.Context(), id){{else}}m, err := h.svc.Get(c.Request.Context(), id){{/if}}
|
|
140
|
-
if err != nil {
|
|
141
|
-
c.Error(err)
|
|
142
|
-
return
|
|
143
|
-
}
|
|
144
|
-
c.JSON(http.StatusOK, toResponse(m))
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
func (h *Handler) update(c *gin.Context) {
|
|
148
|
-
id, ok := httpx.ParseID(c)
|
|
149
|
-
if !ok {
|
|
150
|
-
return
|
|
151
|
-
}
|
|
152
|
-
var in updateInput
|
|
153
|
-
if err := c.ShouldBindJSON(&in); err != nil {
|
|
154
|
-
c.Error(httpx.BindErr(err))
|
|
155
|
-
return
|
|
156
|
-
}
|
|
157
|
-
{{#if cqrs}}m, err := h.commands.Update(c.Request.Context(), id, in){{else}}m, err := h.svc.Update(c.Request.Context(), id, in){{/if}}
|
|
158
|
-
if err != nil {
|
|
159
|
-
c.Error(err)
|
|
160
|
-
return
|
|
161
|
-
}
|
|
162
|
-
c.JSON(http.StatusOK, toResponse(m))
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
func (h *Handler) delete(c *gin.Context) {
|
|
166
|
-
id, ok := httpx.ParseID(c)
|
|
167
|
-
if !ok {
|
|
168
|
-
return
|
|
169
|
-
}
|
|
170
|
-
{{#if cqrs}} if err := h.commands.Delete(c.Request.Context(), id); err != nil {
|
|
171
|
-
{{else}} if err := h.svc.Delete(c.Request.Context(), id); err != nil {
|
|
172
|
-
{{/if}}
|
|
173
|
-
c.Error(err)
|
|
174
|
-
return
|
|
175
|
-
}
|
|
176
|
-
c.Status(http.StatusNoContent)
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// go-scaffold:handler-funcs
|
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
package {{pkg}}
|
|
2
|
-
|
|
3
|
-
import (
|
|
4
|
-
"bytes"
|
|
5
|
-
"context"
|
|
6
|
-
"net/http"
|
|
7
|
-
"net/http/httptest"
|
|
8
|
-
"testing"
|
|
9
|
-
{{#if auth}}
|
|
10
|
-
"time"
|
|
11
|
-
{{/if}}
|
|
12
|
-
|
|
13
|
-
"{{goModule}}/internal/app/{{modulePath}}/model"
|
|
14
|
-
"{{goModule}}/internal/shared/middleware"
|
|
15
|
-
|
|
16
|
-
"github.com/gin-gonic/gin"
|
|
17
|
-
{{#if auth}}
|
|
18
|
-
"github.com/golang-jwt/jwt/v5"
|
|
19
|
-
{{/if}}
|
|
20
|
-
"github.com/google/uuid"
|
|
21
|
-
)
|
|
22
|
-
|
|
23
|
-
// serviceStub keeps handler tests at the HTTP boundary. It exercises binding,
|
|
24
|
-
// routing, middleware, status codes, and serialization without a database.
|
|
25
|
-
type serviceStub struct {
|
|
26
|
-
// Embedding keeps this stub source-compatible when `generate method` adds a
|
|
27
|
-
// new operation to the handler's service interface. Base CRUD methods below
|
|
28
|
-
// still override the promoted concrete methods for focused unit tests.
|
|
29
|
-
*Service
|
|
30
|
-
createFn func(context.Context, createInput) (*model.{{pascalName}}, error)
|
|
31
|
-
listFn func(context.Context, int, int) ([]model.{{pascalName}}, error)
|
|
32
|
-
getFn func(context.Context, uuid.UUID) (*model.{{pascalName}}, error)
|
|
33
|
-
updateFn func(context.Context, uuid.UUID, updateInput) (*model.{{pascalName}}, error)
|
|
34
|
-
deleteFn func(context.Context, uuid.UUID) error
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
func (s *serviceStub) Create(ctx context.Context, in createInput) (*model.{{pascalName}}, error) {
|
|
38
|
-
if s.createFn == nil {
|
|
39
|
-
panic("unexpected service.Create call")
|
|
40
|
-
}
|
|
41
|
-
return s.createFn(ctx, in)
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
func (s *serviceStub) List(ctx context.Context, limit, offset int) ([]model.{{pascalName}}, error) {
|
|
45
|
-
if s.listFn == nil {
|
|
46
|
-
panic("unexpected service.List call")
|
|
47
|
-
}
|
|
48
|
-
return s.listFn(ctx, limit, offset)
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
func (s *serviceStub) Get(ctx context.Context, id uuid.UUID) (*model.{{pascalName}}, error) {
|
|
52
|
-
if s.getFn == nil {
|
|
53
|
-
panic("unexpected service.Get call")
|
|
54
|
-
}
|
|
55
|
-
return s.getFn(ctx, id)
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
func (s *serviceStub) Update(ctx context.Context, id uuid.UUID, in updateInput) (*model.{{pascalName}}, error) {
|
|
59
|
-
if s.updateFn == nil {
|
|
60
|
-
panic("unexpected service.Update call")
|
|
61
|
-
}
|
|
62
|
-
return s.updateFn(ctx, id, in)
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
func (s *serviceStub) Delete(ctx context.Context, id uuid.UUID) error {
|
|
66
|
-
if s.deleteFn == nil {
|
|
67
|
-
panic("unexpected service.Delete call")
|
|
68
|
-
}
|
|
69
|
-
return s.deleteFn(ctx, id)
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// go-scaffold:service-stub-methods
|
|
73
|
-
|
|
74
|
-
func setupHandlerTest(t *testing.T, svc service) *gin.Engine {
|
|
75
|
-
t.Helper()
|
|
76
|
-
gin.SetMode(gin.TestMode)
|
|
77
|
-
r := gin.New()
|
|
78
|
-
r.Use(middleware.RequestID(), middleware.Error(true))
|
|
79
|
-
{{#if permission}}
|
|
80
|
-
// The handler unit test verifies route composition, not the role repository.
|
|
81
|
-
authz := middleware.NewAuthz(func(_ context.Context, _ string) (map[string]struct{}, error) {
|
|
82
|
-
return map[string]struct{}{"{{permission}}": {}}, nil
|
|
83
|
-
}, time.Minute)
|
|
84
|
-
{{/if}}
|
|
85
|
-
NewHandler(svc{{#if auth}}, testJWTSecret{{/if}}{{#if permission}}, authz{{/if}}).Register(r)
|
|
86
|
-
return r
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
{{#if auth}}
|
|
90
|
-
const testJWTSecret = "test-secret"
|
|
91
|
-
|
|
92
|
-
func authHeader() string {
|
|
93
|
-
claims := jwt.MapClaims{
|
|
94
|
-
"typ": "access",
|
|
95
|
-
"sub": uuid.NewString(),
|
|
96
|
-
"role": "staff",
|
|
97
|
-
"exp": time.Now().Add(time.Hour).Unix(),
|
|
98
|
-
"iat": time.Now().Unix(),
|
|
99
|
-
}
|
|
100
|
-
tok, _ := jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString([]byte(testJWTSecret))
|
|
101
|
-
return "Bearer " + tok
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
{{/if}}
|
|
105
|
-
func doHandlerRequest(r *gin.Engine, method, requestPath, body string) *httptest.ResponseRecorder {
|
|
106
|
-
req := httptest.NewRequest(method, requestPath, bytes.NewBufferString(body))
|
|
107
|
-
req.Header.Set("Content-Type", "application/json")
|
|
108
|
-
{{#if auth}}
|
|
109
|
-
req.Header.Set("Authorization", authHeader())
|
|
110
|
-
{{/if}}
|
|
111
|
-
w := httptest.NewRecorder()
|
|
112
|
-
r.ServeHTTP(w, req)
|
|
113
|
-
return w
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// createBody is the JSON these tests POST. It starts empty because createInput
|
|
117
|
-
// starts empty — add a value here for every field you add to createInput in
|
|
118
|
-
// dto.go. Miss that and this test fails with a 400 the moment one of those
|
|
119
|
-
// fields is `binding:"required"`: the handler is fine, the fixture just no
|
|
120
|
-
// longer satisfies it.
|
|
121
|
-
const createBody = `{}`
|
|
122
|
-
|
|
123
|
-
func TestHandler_Create_OK(t *testing.T) {
|
|
124
|
-
svc := &serviceStub{
|
|
125
|
-
createFn: func(context.Context, createInput) (*model.{{pascalName}}, error) {
|
|
126
|
-
return &model.{{pascalName}}{ID: uuid.New()}, nil
|
|
127
|
-
},
|
|
128
|
-
}
|
|
129
|
-
r := setupHandlerTest(t, svc)
|
|
130
|
-
|
|
131
|
-
w := doHandlerRequest(r, http.MethodPost, "/{{plural}}", createBody)
|
|
132
|
-
|
|
133
|
-
if w.Code != http.StatusCreated {
|
|
134
|
-
t.Fatalf("want 201, got %d body=%s", w.Code, w.Body)
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
func TestHandler_Get_NotFound(t *testing.T) {
|
|
139
|
-
svc := &serviceStub{
|
|
140
|
-
getFn: func(context.Context, uuid.UUID) (*model.{{pascalName}}, error) {
|
|
141
|
-
return nil, errNotFound()
|
|
142
|
-
},
|
|
143
|
-
}
|
|
144
|
-
r := setupHandlerTest(t, svc)
|
|
145
|
-
|
|
146
|
-
w := doHandlerRequest(r, http.MethodGet, "/{{plural}}/"+uuid.NewString(), "")
|
|
147
|
-
|
|
148
|
-
if w.Code != http.StatusNotFound {
|
|
149
|
-
t.Fatalf("want 404, got %d body=%s", w.Code, w.Body)
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
func TestHandler_Get_InvalidID_DoesNotCallService(t *testing.T) {
|
|
154
|
-
r := setupHandlerTest(t, &serviceStub{})
|
|
155
|
-
|
|
156
|
-
w := doHandlerRequest(r, http.MethodGet, "/{{plural}}/not-a-uuid", "")
|
|
157
|
-
|
|
158
|
-
if w.Code != http.StatusBadRequest {
|
|
159
|
-
t.Fatalf("want 400, got %d body=%s", w.Code, w.Body)
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
func TestHandler_Delete_OK(t *testing.T) {
|
|
164
|
-
svc := &serviceStub{
|
|
165
|
-
deleteFn: func(context.Context, uuid.UUID) error { return nil },
|
|
166
|
-
}
|
|
167
|
-
r := setupHandlerTest(t, svc)
|
|
168
|
-
|
|
169
|
-
w := doHandlerRequest(r, http.MethodDelete, "/{{plural}}/"+uuid.NewString(), "")
|
|
170
|
-
|
|
171
|
-
if w.Code != http.StatusNoContent {
|
|
172
|
-
t.Fatalf("want 204, got %d body=%s", w.Code, w.Body)
|
|
173
|
-
}
|
|
174
|
-
}
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
package {{pkg}}
|
|
2
|
-
|
|
3
|
-
import (
|
|
4
|
-
"context"
|
|
5
|
-
|
|
6
|
-
"{{goModule}}/internal/app/{{modulePath}}/model"
|
|
7
|
-
|
|
8
|
-
"github.com/google/uuid"
|
|
9
|
-
)
|
|
10
|
-
|
|
11
|
-
// commandRepository is the outbound port for state-changing use cases.
|
|
12
|
-
type commandRepository interface {
|
|
13
|
-
Create(ctx context.Context, m *model.{{pascalName}}) error
|
|
14
|
-
FindByID(ctx context.Context, id uuid.UUID) (*model.{{pascalName}}, error)
|
|
15
|
-
Update(ctx context.Context, m *model.{{pascalName}}) error
|
|
16
|
-
Delete(ctx context.Context, id uuid.UUID) error
|
|
17
|
-
// go-scaffold:command-repository-interface
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
// commandService is the inbound application port consumed by command routes.
|
|
21
|
-
type commandService interface {
|
|
22
|
-
// go-scaffold:command-interface
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// CommandHandler owns state-changing use cases for {{pkg}}.
|
|
26
|
-
type CommandHandler struct {
|
|
27
|
-
repo commandRepository
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
func NewCommandHandler(repo commandRepository) *CommandHandler {
|
|
31
|
-
return &CommandHandler{repo: repo}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// go-scaffold:command-methods
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
package {{pkg}}
|
|
2
|
-
|
|
3
|
-
import (
|
|
4
|
-
"time"
|
|
5
|
-
|
|
6
|
-
"{{goModule}}/internal/app/{{modulePath}}/model"
|
|
7
|
-
|
|
8
|
-
"github.com/google/uuid"
|
|
9
|
-
)
|
|
10
|
-
|
|
11
|
-
// response = the DTO sent out (kept separate from the model so a later DB column doesn't leak automatically)
|
|
12
|
-
// TODO: add fields as you generate methods that return more than id/created_at
|
|
13
|
-
// unused until `generate method` adds a method that returns one
|
|
14
|
-
//
|
|
15
|
-
//nolint:unused
|
|
16
|
-
type response struct {
|
|
17
|
-
ID uuid.UUID `json:"id"`
|
|
18
|
-
CreatedAt time.Time `json:"created_at"`
|
|
19
|
-
// clients send this back on an update so a concurrent save can be detected
|
|
20
|
-
Version int `json:"version"`
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
//nolint:unused
|
|
24
|
-
func toResponse(m *model.{{pascalName}}) response {
|
|
25
|
-
return response{ID: m.ID, CreatedAt: m.CreatedAt, Version: m.Version}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// go-scaffold:dto
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
package {{pkg}}
|
|
2
|
-
|
|
3
|
-
import (
|
|
4
|
-
{{#if auth}}
|
|
5
|
-
"{{goModule}}/internal/shared/middleware"
|
|
6
|
-
|
|
7
|
-
{{/if}}
|
|
8
|
-
"github.com/gin-gonic/gin"
|
|
9
|
-
)
|
|
10
|
-
|
|
11
|
-
{{#if cqrs}}
|
|
12
|
-
// service is retained as a compatibility seam. Production composition uses
|
|
13
|
-
// the separate commandService/queryService ports below.
|
|
14
|
-
type service interface {
|
|
15
|
-
commandService
|
|
16
|
-
queryService
|
|
17
|
-
// go-scaffold:service-interface
|
|
18
|
-
}
|
|
19
|
-
{{else}}
|
|
20
|
-
// service is intentionally empty until `generate method` adds the exact
|
|
21
|
-
// application operations required by this HTTP adapter.
|
|
22
|
-
type service interface {
|
|
23
|
-
// go-scaffold:service-interface
|
|
24
|
-
}
|
|
25
|
-
{{/if}}
|
|
26
|
-
|
|
27
|
-
// Handler = delivery for {{pkg}} (parses HTTP, calls the service, attaches errors for the middleware to render)
|
|
28
|
-
type Handler struct {
|
|
29
|
-
{{#if cqrs}}
|
|
30
|
-
commands commandService
|
|
31
|
-
queries queryService
|
|
32
|
-
{{else}}
|
|
33
|
-
svc service
|
|
34
|
-
{{/if}}
|
|
35
|
-
{{#if auth}}
|
|
36
|
-
jwtSecret string
|
|
37
|
-
{{/if}}
|
|
38
|
-
{{#if permission}}
|
|
39
|
-
authz *middleware.Authz
|
|
40
|
-
{{/if}}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
{{#if cqrs}}
|
|
44
|
-
func NewHandler(svc service{{#if auth}}, jwtSecret string{{/if}}{{#if permission}}, authz *middleware.Authz{{/if}}) *Handler {
|
|
45
|
-
return NewHandlerFromCQRS(svc, svc{{#if auth}}, jwtSecret{{/if}}{{#if permission}}, authz{{/if}})
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
func NewHandlerFromCQRS(commands commandService, queries queryService{{#if auth}}, jwtSecret string{{/if}}{{#if permission}}, authz *middleware.Authz{{/if}}) *Handler {
|
|
49
|
-
return &Handler{
|
|
50
|
-
commands: commands,
|
|
51
|
-
queries: queries,
|
|
52
|
-
{{#if auth}}
|
|
53
|
-
jwtSecret: jwtSecret,
|
|
54
|
-
{{/if}}
|
|
55
|
-
{{#if permission}}
|
|
56
|
-
authz: authz,
|
|
57
|
-
{{/if}}
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
{{else}}
|
|
61
|
-
func NewHandler(svc service{{#if auth}}, jwtSecret string{{/if}}{{#if permission}}, authz *middleware.Authz{{/if}}) *Handler {
|
|
62
|
-
return &Handler{
|
|
63
|
-
svc: svc,
|
|
64
|
-
{{#if auth}}
|
|
65
|
-
jwtSecret: jwtSecret,
|
|
66
|
-
{{/if}}
|
|
67
|
-
{{#if permission}}
|
|
68
|
-
authz: authz,
|
|
69
|
-
{{/if}}
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
{{/if}}
|
|
73
|
-
|
|
74
|
-
// Register wires {{pkg}}'s routes onto the router group (takes an IRouter so it can be nested under /v1)
|
|
75
|
-
// minimal module: no routes yet — add them with `go-scaffold generate method {{pkg}} <name> --type ...`
|
|
76
|
-
func (h *Handler) Register(rg gin.IRouter) {
|
|
77
|
-
g := rg.Group("/{{plural}}"{{#if auth}}, middleware.RequireAuth(h.jwtSecret){{/if}}{{#if permission}}, h.authz.Require("{{permission}}"){{/if}})
|
|
78
|
-
_ = g
|
|
79
|
-
// go-scaffold:handler-routes
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// go-scaffold:handler-funcs
|