@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,537 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.assertMethodAbsent = assertMethodAbsent;
|
|
7
|
-
exports.patchMethod = patchMethod;
|
|
8
|
-
exports.markersPresent = markersPresent;
|
|
9
|
-
exports.markersPresentForPaths = markersPresentForPaths;
|
|
10
|
-
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
11
|
-
const marker_patch_1 = require("./marker-patch");
|
|
12
|
-
const naming_1 = require("./naming");
|
|
13
|
-
const DTO_MARKER = "// go-scaffold:dto";
|
|
14
|
-
const AUTH_DTO_MARKER = "// go-scaffold:user-dto";
|
|
15
|
-
const REPO_INTERFACE_MARKER = "// go-scaffold:repository-interface";
|
|
16
|
-
const REPO_IMPL_MARKER = "// go-scaffold:repository-methods";
|
|
17
|
-
const SERVICE_MARKER = "// go-scaffold:service-methods";
|
|
18
|
-
const SERVICE_INTERFACE_MARKER = "// go-scaffold:service-interface";
|
|
19
|
-
const COMMAND_REPO_INTERFACE_MARKER = "// go-scaffold:command-repository-interface";
|
|
20
|
-
const QUERY_REPO_INTERFACE_MARKER = "// go-scaffold:query-repository-interface";
|
|
21
|
-
const COMMAND_MARKER = "// go-scaffold:command-methods";
|
|
22
|
-
const QUERY_MARKER = "// go-scaffold:query-methods";
|
|
23
|
-
const COMMAND_INTERFACE_MARKER = "// go-scaffold:command-interface";
|
|
24
|
-
const QUERY_INTERFACE_MARKER = "// go-scaffold:query-interface";
|
|
25
|
-
const HANDLER_ROUTES_MARKER = "// go-scaffold:handler-routes";
|
|
26
|
-
const HANDLER_FUNCS_MARKER = "// go-scaffold:handler-funcs";
|
|
27
|
-
// Auth deliberately keeps its route/service composition files focused rather
|
|
28
|
-
// than using the generic CRUD marker names. `generate method user ...` is
|
|
29
|
-
// still a supported extension point, so accept both marker vocabularies.
|
|
30
|
-
const AUTH_REPO_INTERFACE_MARKER = "// go-scaffold:user-repository-interface";
|
|
31
|
-
const AUTH_REPO_IMPL_MARKER = "// go-scaffold:user-repository-methods";
|
|
32
|
-
const AUTH_SERVICE_MARKER = "// go-scaffold:user-service-methods";
|
|
33
|
-
const AUTH_HANDLER_ROUTES_MARKER = "// go-scaffold:user-routes";
|
|
34
|
-
const AUTH_HANDLER_FUNCS_MARKER = "// go-scaffold:user-handler-funcs";
|
|
35
|
-
const REPOSITORY_STUB_FIELDS_MARKER = "// go-scaffold:repository-stub-fields";
|
|
36
|
-
const REPOSITORY_STUB_METHODS_MARKER = "// go-scaffold:repository-stub-methods";
|
|
37
|
-
const LEGACY_FAKE_REPO_METHODS_MARKER = "// go-scaffold:fake-repo-methods";
|
|
38
|
-
const AUTH_FAKE_REPO_METHODS_MARKER = "// go-scaffold:user-fake-repo-methods";
|
|
39
|
-
const UNUSED_G_LINE = "\t_ = g\n";
|
|
40
|
-
// writeHandler ensures whatever packages the new handler code references are
|
|
41
|
-
// imported (a minimal module starts with only "gin" imported) and drops the
|
|
42
|
-
// `_ = g` placeholder once a real route makes it unnecessary — a no-op on a
|
|
43
|
-
// full module, which already imports everything and never has that line.
|
|
44
|
-
function writeHandler(handlerPath, content, goModule, needs) {
|
|
45
|
-
for (const pkg of needs) {
|
|
46
|
-
const importPath = pkg.startsWith("net/") ? pkg : `${goModule}/internal/shared/${pkg}`;
|
|
47
|
-
content = (0, marker_patch_1.ensureImport)(content, importPath);
|
|
48
|
-
}
|
|
49
|
-
fs_extra_1.default.writeFileSync(handlerPath, content.replace(UNUSED_G_LINE, ""));
|
|
50
|
-
}
|
|
51
|
-
function isCqrs(paths) {
|
|
52
|
-
return Boolean(paths.commandPath &&
|
|
53
|
-
paths.queryPath &&
|
|
54
|
-
fs_extra_1.default.existsSync(paths.commandPath) &&
|
|
55
|
-
fs_extra_1.default.existsSync(paths.queryPath));
|
|
56
|
-
}
|
|
57
|
-
function cqrsTarget(paths, type) {
|
|
58
|
-
if (!isCqrs(paths))
|
|
59
|
-
return null;
|
|
60
|
-
if (type === "get") {
|
|
61
|
-
return {
|
|
62
|
-
path: paths.queryPath,
|
|
63
|
-
implementationMarker: QUERY_MARKER,
|
|
64
|
-
interfaceMarker: QUERY_INTERFACE_MARKER,
|
|
65
|
-
callReceiver: "queries",
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
return {
|
|
69
|
-
path: paths.commandPath,
|
|
70
|
-
implementationMarker: COMMAND_MARKER,
|
|
71
|
-
interfaceMarker: COMMAND_INTERFACE_MARKER,
|
|
72
|
-
callReceiver: "commands",
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
|
-
function insertCqrsImplementation(paths, type, block, goModule) {
|
|
76
|
-
const target = cqrsTarget(paths, type);
|
|
77
|
-
if (!target)
|
|
78
|
-
return;
|
|
79
|
-
let content = fs_extra_1.default.readFileSync(target.path, "utf8");
|
|
80
|
-
if (type !== "get") {
|
|
81
|
-
content = (0, marker_patch_1.ensureImport)(content, `${goModule}/internal/shared/apperror`);
|
|
82
|
-
}
|
|
83
|
-
content = (0, marker_patch_1.insertBeforeMarker)(content, target.implementationMarker, block);
|
|
84
|
-
fs_extra_1.default.writeFileSync(target.path, content);
|
|
85
|
-
}
|
|
86
|
-
function insertCqrsFacade(servicePath, block, goModule, modulePath) {
|
|
87
|
-
let service = fs_extra_1.default.readFileSync(servicePath, "utf8");
|
|
88
|
-
// Minimal CQRS service.go intentionally has no imports until a method is
|
|
89
|
-
// generated. The compatibility facade still uses the normal feature input,
|
|
90
|
-
// model, and UUID types, so make the first generated method compile too.
|
|
91
|
-
service = (0, marker_patch_1.ensureImport)(service, "context");
|
|
92
|
-
service = (0, marker_patch_1.ensureImport)(service, `${goModule}/internal/app/${modulePath}/model`);
|
|
93
|
-
service = (0, marker_patch_1.ensureImport)(service, "github.com/google/uuid");
|
|
94
|
-
service = (0, marker_patch_1.insertBeforeMarker)(service, SERVICE_MARKER, block);
|
|
95
|
-
fs_extra_1.default.writeFileSync(servicePath, service);
|
|
96
|
-
}
|
|
97
|
-
function assertNotDuplicate(content, needle, what) {
|
|
98
|
-
if (content.includes(needle)) {
|
|
99
|
-
throw new Error(`${what} already exists — pick a different method name`);
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
function routeCall(type) {
|
|
103
|
-
return { get: "GET", post: "POST", put: "PUT", patch: "PATCH", delete: "DELETE" }[type];
|
|
104
|
-
}
|
|
105
|
-
function insertBeforeMethodMarker(content, standard, auth, block) {
|
|
106
|
-
const marker = (0, marker_patch_1.hasMarker)(content, standard) ? standard : (0, marker_patch_1.hasMarker)(content, auth) ? auth : standard;
|
|
107
|
-
return (0, marker_patch_1.insertBeforeMarker)(content, marker, block);
|
|
108
|
-
}
|
|
109
|
-
function hasMethodMarker(content, standard, auth) {
|
|
110
|
-
return (0, marker_patch_1.hasMarker)(content, standard) || (0, marker_patch_1.hasMarker)(content, auth);
|
|
111
|
-
}
|
|
112
|
-
function handlerRouteReceiver(content) {
|
|
113
|
-
return (0, marker_patch_1.hasMarker)(content, AUTH_HANDLER_ROUTES_MARKER) ? "usersGroup" : "g";
|
|
114
|
-
}
|
|
115
|
-
// assertMethodAbsent is the same check patchMethod runs, exported so callers
|
|
116
|
-
// can reach it before their own pre-flight checks. A name that already exists
|
|
117
|
-
// is the cause the caller needs to hear about, so it has to be reported ahead
|
|
118
|
-
// of anything downstream of it (the OpenAPI document that name would collide
|
|
119
|
-
// with, for one) rather than after.
|
|
120
|
-
function assertMethodAbsent(paths, method) {
|
|
121
|
-
const handlerSig = `func (h *Handler) ${method.handlerName}(`;
|
|
122
|
-
const serviceSig = `func (s *Service) ${method.pascalName}(`;
|
|
123
|
-
assertNotDuplicate(fs_extra_1.default.readFileSync(paths.handlerPath, "utf8"), handlerSig, `handler method "${method.handlerName}"`);
|
|
124
|
-
assertNotDuplicate(fs_extra_1.default.readFileSync(paths.servicePath, "utf8"), serviceSig, `service method "${method.pascalName}"`);
|
|
125
|
-
}
|
|
126
|
-
function patchMethod(paths, naming, method, opts, goModule) {
|
|
127
|
-
assertMethodAbsent(paths, method);
|
|
128
|
-
if (opts.type === "get" && opts.getMode === "all") {
|
|
129
|
-
patchGetAll(paths, naming, method, goModule);
|
|
130
|
-
}
|
|
131
|
-
else if (opts.type === "get") {
|
|
132
|
-
if (!opts.field)
|
|
133
|
-
throw new Error("--field is required for --type get --get-mode one");
|
|
134
|
-
if (opts.field.toLowerCase() === "id") {
|
|
135
|
-
throw new Error('--field cannot be "id" — GET /:id already exists as the default lookup');
|
|
136
|
-
}
|
|
137
|
-
patchGetOne(paths, naming, method, opts.field, goModule);
|
|
138
|
-
}
|
|
139
|
-
else if (opts.type === "post") {
|
|
140
|
-
patchPost(paths, naming, method, goModule);
|
|
141
|
-
}
|
|
142
|
-
else if (opts.type === "put" || opts.type === "patch") {
|
|
143
|
-
patchResourceAction(paths, method, opts.type, goModule, naming.pkg);
|
|
144
|
-
}
|
|
145
|
-
else {
|
|
146
|
-
patchDelete(paths, method, goModule, naming.pkg);
|
|
147
|
-
}
|
|
148
|
-
ensureNonCqrsServiceImports(paths, goModule);
|
|
149
|
-
patchHandlerServiceInterface(paths, naming, method, opts, goModule);
|
|
150
|
-
}
|
|
151
|
-
// Auth keeps service.go intentionally small, so it does not carry the
|
|
152
|
-
// apperror import that ordinary generated CRUD services use. A later
|
|
153
|
-
// `generate method user ...` still inserts the same error-returning method
|
|
154
|
-
// shape; make that extension compile without reintroducing the import into
|
|
155
|
-
// every auth-only project.
|
|
156
|
-
function ensureNonCqrsServiceImports(paths, goModule) {
|
|
157
|
-
if (isCqrs(paths))
|
|
158
|
-
return;
|
|
159
|
-
let service = fs_extra_1.default.readFileSync(paths.servicePath, "utf8");
|
|
160
|
-
service = (0, marker_patch_1.ensureImport)(service, `${goModule}/internal/shared/apperror`);
|
|
161
|
-
fs_extra_1.default.writeFileSync(paths.servicePath, service);
|
|
162
|
-
}
|
|
163
|
-
function patchHandlerServiceInterface(paths, naming, method, opts, goModule) {
|
|
164
|
-
let signature;
|
|
165
|
-
let needsModel = true;
|
|
166
|
-
let needsUUID = false;
|
|
167
|
-
if (opts.type === "get" && opts.getMode === "all") {
|
|
168
|
-
signature = `${method.pascalName}(context.Context, int, int) ([]model.${naming.pascalName}, error)`;
|
|
169
|
-
}
|
|
170
|
-
else if (opts.type === "get") {
|
|
171
|
-
signature = `${method.pascalName}(context.Context, string) (*model.${naming.pascalName}, error)`;
|
|
172
|
-
}
|
|
173
|
-
else if (opts.type === "post") {
|
|
174
|
-
signature = `${method.pascalName}(context.Context, ${method.pascalName}Input) (*model.${naming.pascalName}, error)`;
|
|
175
|
-
}
|
|
176
|
-
else if (opts.type === "put" || opts.type === "patch") {
|
|
177
|
-
signature = `${method.pascalName}(context.Context, uuid.UUID) error`;
|
|
178
|
-
needsModel = false;
|
|
179
|
-
needsUUID = true;
|
|
180
|
-
}
|
|
181
|
-
else {
|
|
182
|
-
signature = `${method.pascalName}(context.Context, uuid.UUID) error`;
|
|
183
|
-
needsModel = false;
|
|
184
|
-
needsUUID = true;
|
|
185
|
-
}
|
|
186
|
-
let handler = fs_extra_1.default.readFileSync(paths.handlerPath, "utf8");
|
|
187
|
-
if (!(0, marker_patch_1.hasMarker)(handler, SERVICE_INTERFACE_MARKER)) {
|
|
188
|
-
// Projects scaffolded before the narrow service interface stored *Service
|
|
189
|
-
// directly. The concrete type already exposes generated methods, so there
|
|
190
|
-
// is no interface declaration to patch and no migration is required.
|
|
191
|
-
if (handler.includes("svc *Service"))
|
|
192
|
-
return;
|
|
193
|
-
throw new Error(`marker "${SERVICE_INTERFACE_MARKER}" not found and Handler does not use legacy *Service wiring`);
|
|
194
|
-
}
|
|
195
|
-
handler = (0, marker_patch_1.insertBeforeMarker)(handler, SERVICE_INTERFACE_MARKER, signature);
|
|
196
|
-
handler = (0, marker_patch_1.ensureImport)(handler, "context");
|
|
197
|
-
if (needsModel) {
|
|
198
|
-
handler = (0, marker_patch_1.ensureImport)(handler, `${goModule}/internal/app/${naming.pkg}/model`);
|
|
199
|
-
}
|
|
200
|
-
if (needsUUID) {
|
|
201
|
-
handler = (0, marker_patch_1.ensureImport)(handler, "github.com/google/uuid");
|
|
202
|
-
}
|
|
203
|
-
fs_extra_1.default.writeFileSync(paths.handlerPath, handler);
|
|
204
|
-
const target = cqrsTarget(paths, opts.type);
|
|
205
|
-
if (target) {
|
|
206
|
-
let application = fs_extra_1.default.readFileSync(target.path, "utf8");
|
|
207
|
-
application = (0, marker_patch_1.insertBeforeMarker)(application, target.interfaceMarker, signature);
|
|
208
|
-
fs_extra_1.default.writeFileSync(target.path, application);
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
function patchGetAll(paths, naming, method, goModule) {
|
|
212
|
-
let handler = fs_extra_1.default.readFileSync(paths.handlerPath, "utf8");
|
|
213
|
-
const routeReceiver = handlerRouteReceiver(handler);
|
|
214
|
-
handler = insertBeforeMethodMarker(handler, HANDLER_ROUTES_MARKER, AUTH_HANDLER_ROUTES_MARKER, `${routeReceiver}.GET("/${method.pathSegment}", h.${method.handlerName})`);
|
|
215
|
-
handler = insertBeforeMethodMarker(handler, HANDLER_FUNCS_MARKER, AUTH_HANDLER_FUNCS_MARKER, [
|
|
216
|
-
`func (h *Handler) ${method.handlerName}(c *gin.Context) {`,
|
|
217
|
-
`\tp := pagination.Parse(c)`,
|
|
218
|
-
`\titems, err := ${isCqrs(paths) ? `h.queries.${method.pascalName}` : `h.svc.${method.pascalName}`}(c.Request.Context(), p.Limit, p.Offset)`,
|
|
219
|
-
`\tif err != nil {`,
|
|
220
|
-
`\t\tc.Error(err)`,
|
|
221
|
-
`\t\treturn`,
|
|
222
|
-
`\t}`,
|
|
223
|
-
`\tout := make([]response, len(items))`,
|
|
224
|
-
`\tfor i := range items {`,
|
|
225
|
-
`\t\tout[i] = toResponse(&items[i])`,
|
|
226
|
-
`\t}`,
|
|
227
|
-
`\tc.JSON(http.StatusOK, p.Response(out))`,
|
|
228
|
-
`}`,
|
|
229
|
-
``,
|
|
230
|
-
].join("\n"));
|
|
231
|
-
writeHandler(paths.handlerPath, handler, goModule, ["net/http", "pagination"]);
|
|
232
|
-
const queryMethod = [
|
|
233
|
-
`func (h *QueryHandler) ${method.pascalName}(ctx context.Context, limit, offset int) ([]model.${naming.pascalName}, error) {`,
|
|
234
|
-
`\t// TODO: add real filtering for "${method.name}" — currently reuses FindAll`,
|
|
235
|
-
`\titems, err := h.repo.FindAll(ctx, limit, offset)`,
|
|
236
|
-
`\tif err != nil {`,
|
|
237
|
-
`\t\treturn nil, apperror.NewInternal(err)`,
|
|
238
|
-
`\t}`,
|
|
239
|
-
`\treturn items, nil`,
|
|
240
|
-
`}`,
|
|
241
|
-
``,
|
|
242
|
-
].join("\n");
|
|
243
|
-
const serviceFacade = [
|
|
244
|
-
`func (s *Service) ${method.pascalName}(ctx context.Context, limit, offset int) ([]model.${naming.pascalName}, error) {`,
|
|
245
|
-
`\treturn s.queries.${method.pascalName}(ctx, limit, offset)`,
|
|
246
|
-
`}`,
|
|
247
|
-
``,
|
|
248
|
-
].join("\n");
|
|
249
|
-
if (isCqrs(paths)) {
|
|
250
|
-
insertCqrsImplementation(paths, "get", queryMethod, goModule);
|
|
251
|
-
insertCqrsFacade(paths.servicePath, serviceFacade, goModule, naming.pkg);
|
|
252
|
-
}
|
|
253
|
-
else {
|
|
254
|
-
let service = fs_extra_1.default.readFileSync(paths.servicePath, "utf8");
|
|
255
|
-
service = insertBeforeMethodMarker(service, SERVICE_MARKER, AUTH_SERVICE_MARKER, queryMethod.replace("(h *QueryHandler)", "(s *Service)").replace("h.repo", "s.repo"));
|
|
256
|
-
fs_extra_1.default.writeFileSync(paths.servicePath, service);
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
function patchGetOne(paths, naming, method, rawField, goModule) {
|
|
260
|
-
const fieldParam = (0, naming_1.toCamelCase)(rawField);
|
|
261
|
-
const fieldPascal = (0, naming_1.toPascalCase)(rawField);
|
|
262
|
-
const fieldColumn = (0, naming_1.toDbName)(rawField);
|
|
263
|
-
let repo = fs_extra_1.default.readFileSync(paths.repositoryPath, "utf8");
|
|
264
|
-
assertNotDuplicate(repo, `FindBy${fieldPascal}(`, `repository method "FindBy${fieldPascal}"`);
|
|
265
|
-
repo = insertBeforeMethodMarker(repo, REPO_IMPL_MARKER, AUTH_REPO_IMPL_MARKER, [
|
|
266
|
-
`func (r *Repository) FindBy${fieldPascal}(ctx context.Context, ${fieldParam} string) (*model.${naming.pascalName}, error) {`,
|
|
267
|
-
`\tvar m model.${naming.pascalName}`,
|
|
268
|
-
`\t// TODO: add the "${fieldColumn}" column and an index on it — this query`,
|
|
269
|
-
`\t// compiles against any table and only fails when it runs.`,
|
|
270
|
-
`\t//`,
|
|
271
|
-
`\t// tx.From, like every other method here: a lookup that reached for r.db`,
|
|
272
|
-
`\t// directly would read outside a transaction its caller had opened, and`,
|
|
273
|
-
`\t// so miss that transaction's own uncommitted writes.`,
|
|
274
|
-
`\tif err := tx.From(ctx, r.db).WithContext(ctx).First(&m, "${fieldColumn} = ?", ${fieldParam}).Error; err != nil {`,
|
|
275
|
-
`\t\treturn nil, err`,
|
|
276
|
-
`\t}`,
|
|
277
|
-
`\treturn &m, nil`,
|
|
278
|
-
`}`,
|
|
279
|
-
``,
|
|
280
|
-
].join("\n"));
|
|
281
|
-
fs_extra_1.default.writeFileSync(paths.repositoryPath, repo);
|
|
282
|
-
let service = fs_extra_1.default.readFileSync(paths.servicePath, "utf8");
|
|
283
|
-
if (isCqrs(paths)) {
|
|
284
|
-
let query = fs_extra_1.default.readFileSync(paths.queryPath, "utf8");
|
|
285
|
-
query = (0, marker_patch_1.insertBeforeMarker)(query, QUERY_REPO_INTERFACE_MARKER, `FindBy${fieldPascal}(ctx context.Context, ${fieldParam} string) (*model.${naming.pascalName}, error)`);
|
|
286
|
-
fs_extra_1.default.writeFileSync(paths.queryPath, query);
|
|
287
|
-
}
|
|
288
|
-
else {
|
|
289
|
-
service = insertBeforeMethodMarker(service, REPO_INTERFACE_MARKER, AUTH_REPO_INTERFACE_MARKER, `FindBy${fieldPascal}(ctx context.Context, ${fieldParam} string) (*model.${naming.pascalName}, error)`);
|
|
290
|
-
}
|
|
291
|
-
// The repository interface just grew, so the test double needs a matching
|
|
292
|
-
// method or focused service tests stop compiling. New projects use a
|
|
293
|
-
// function-backed stub; projects scaffolded before that refactor retain the
|
|
294
|
-
// old fakeRepo marker and error behavior.
|
|
295
|
-
let serviceTest = fs_extra_1.default.readFileSync(paths.serviceTestPath, "utf8");
|
|
296
|
-
if ((0, marker_patch_1.hasMarker)(serviceTest, REPOSITORY_STUB_FIELDS_MARKER) &&
|
|
297
|
-
(0, marker_patch_1.hasMarker)(serviceTest, REPOSITORY_STUB_METHODS_MARKER)) {
|
|
298
|
-
serviceTest = (0, marker_patch_1.insertBeforeMarker)(serviceTest, REPOSITORY_STUB_FIELDS_MARKER, `findBy${fieldPascal}Fn func(context.Context, string) (*model.${naming.pascalName}, error)`);
|
|
299
|
-
serviceTest = (0, marker_patch_1.insertBeforeMarker)(serviceTest, REPOSITORY_STUB_METHODS_MARKER, [
|
|
300
|
-
`//nolint:unused`,
|
|
301
|
-
`func (s *repositoryStub) FindBy${fieldPascal}(ctx context.Context, value string) (*model.${naming.pascalName}, error) {`,
|
|
302
|
-
`\tif s.findBy${fieldPascal}Fn == nil {`,
|
|
303
|
-
`\t\tpanic("unexpected repository.FindBy${fieldPascal} call")`,
|
|
304
|
-
`\t}`,
|
|
305
|
-
`\treturn s.findBy${fieldPascal}Fn(ctx, value)`,
|
|
306
|
-
`}`,
|
|
307
|
-
``,
|
|
308
|
-
].join("\n"));
|
|
309
|
-
}
|
|
310
|
-
else if ((0, marker_patch_1.hasMarker)(serviceTest, AUTH_FAKE_REPO_METHODS_MARKER)) {
|
|
311
|
-
serviceTest = (0, marker_patch_1.insertBeforeMarker)(serviceTest, AUTH_FAKE_REPO_METHODS_MARKER, [
|
|
312
|
-
`//nolint:unused`,
|
|
313
|
-
`func (f *fakeRepo) FindBy${fieldPascal}(context.Context, string) (*model.${naming.pascalName}, error) {`,
|
|
314
|
-
` return nil, gorm.ErrRecordNotFound`,
|
|
315
|
-
`}`,
|
|
316
|
-
``,
|
|
317
|
-
].join("\n"));
|
|
318
|
-
}
|
|
319
|
-
else if ((0, marker_patch_1.hasMarker)(serviceTest, LEGACY_FAKE_REPO_METHODS_MARKER)) {
|
|
320
|
-
serviceTest = (0, marker_patch_1.insertBeforeMarker)(serviceTest, LEGACY_FAKE_REPO_METHODS_MARKER, [
|
|
321
|
-
`//nolint:unused`,
|
|
322
|
-
`func (f *fakeRepo) FindBy${fieldPascal}(context.Context, string) (*model.${naming.pascalName}, error) {`,
|
|
323
|
-
`\tif f.err != nil {`,
|
|
324
|
-
`\t\treturn nil, f.err`,
|
|
325
|
-
`\t}`,
|
|
326
|
-
`\treturn f.m, nil`,
|
|
327
|
-
`}`,
|
|
328
|
-
``,
|
|
329
|
-
].join("\n"));
|
|
330
|
-
}
|
|
331
|
-
else {
|
|
332
|
-
throw new Error(`neither current repository stub markers nor legacy "${LEGACY_FAKE_REPO_METHODS_MARKER}" found in ${paths.serviceTestPath}`);
|
|
333
|
-
}
|
|
334
|
-
fs_extra_1.default.writeFileSync(paths.serviceTestPath, serviceTest);
|
|
335
|
-
const queryMethod = [
|
|
336
|
-
`func (h *QueryHandler) ${method.pascalName}(ctx context.Context, ${fieldParam} string) (*model.${naming.pascalName}, error) {`,
|
|
337
|
-
`\tm, err := h.repo.FindBy${fieldPascal}(ctx, ${fieldParam})`,
|
|
338
|
-
`\tif err != nil {`,
|
|
339
|
-
`\t\treturn nil, wrapFindErr(err)`,
|
|
340
|
-
`\t}`,
|
|
341
|
-
`\treturn m, nil`,
|
|
342
|
-
`}`,
|
|
343
|
-
``,
|
|
344
|
-
].join("\n");
|
|
345
|
-
const serviceFacade = [
|
|
346
|
-
`func (s *Service) ${method.pascalName}(ctx context.Context, ${fieldParam} string) (*model.${naming.pascalName}, error) {`,
|
|
347
|
-
`\treturn s.queries.${method.pascalName}(ctx, ${fieldParam})`,
|
|
348
|
-
`}`,
|
|
349
|
-
``,
|
|
350
|
-
].join("\n");
|
|
351
|
-
if (isCqrs(paths)) {
|
|
352
|
-
insertCqrsImplementation(paths, "get", queryMethod, goModule);
|
|
353
|
-
insertCqrsFacade(paths.servicePath, serviceFacade, goModule, naming.pkg);
|
|
354
|
-
}
|
|
355
|
-
else {
|
|
356
|
-
service = insertBeforeMethodMarker(service, SERVICE_MARKER, AUTH_SERVICE_MARKER, queryMethod.replace("(h *QueryHandler)", "(s *Service)").replace("h.repo", "s.repo"));
|
|
357
|
-
fs_extra_1.default.writeFileSync(paths.servicePath, service);
|
|
358
|
-
}
|
|
359
|
-
let handler = fs_extra_1.default.readFileSync(paths.handlerPath, "utf8");
|
|
360
|
-
const routeReceiver = handlerRouteReceiver(handler);
|
|
361
|
-
handler = insertBeforeMethodMarker(handler, HANDLER_ROUTES_MARKER, AUTH_HANDLER_ROUTES_MARKER, `${routeReceiver}.GET("/${fieldColumn}/:${fieldParam}", h.${method.handlerName})`);
|
|
362
|
-
handler = insertBeforeMethodMarker(handler, HANDLER_FUNCS_MARKER, AUTH_HANDLER_FUNCS_MARKER, [
|
|
363
|
-
`func (h *Handler) ${method.handlerName}(c *gin.Context) {`,
|
|
364
|
-
`\t${fieldParam} := c.Param("${fieldParam}")`,
|
|
365
|
-
`\tm, err := ${isCqrs(paths) ? `h.queries.${method.pascalName}` : `h.svc.${method.pascalName}`}(c.Request.Context(), ${fieldParam})`,
|
|
366
|
-
`\tif err != nil {`,
|
|
367
|
-
`\t\tc.Error(err)`,
|
|
368
|
-
`\t\treturn`,
|
|
369
|
-
`\t}`,
|
|
370
|
-
`\tc.JSON(http.StatusOK, toResponse(m))`,
|
|
371
|
-
`}`,
|
|
372
|
-
``,
|
|
373
|
-
].join("\n"));
|
|
374
|
-
writeHandler(paths.handlerPath, handler, goModule, ["net/http"]);
|
|
375
|
-
}
|
|
376
|
-
function patchPost(paths, naming, method, goModule) {
|
|
377
|
-
const inputName = `${method.pascalName}Input`;
|
|
378
|
-
let dto = fs_extra_1.default.readFileSync(paths.dtoPath, "utf8");
|
|
379
|
-
assertNotDuplicate(dto, `type ${inputName} struct`, `DTO "${inputName}"`);
|
|
380
|
-
dto = insertBeforeMethodMarker(dto, DTO_MARKER, AUTH_DTO_MARKER, [`type ${inputName} struct {`, `\t// TODO: add request fields`, `}`, ``].join("\n"));
|
|
381
|
-
fs_extra_1.default.writeFileSync(paths.dtoPath, dto);
|
|
382
|
-
let handler = fs_extra_1.default.readFileSync(paths.handlerPath, "utf8");
|
|
383
|
-
const routeReceiver = handlerRouteReceiver(handler);
|
|
384
|
-
handler = insertBeforeMethodMarker(handler, HANDLER_ROUTES_MARKER, AUTH_HANDLER_ROUTES_MARKER, `${routeReceiver}.POST("/${method.pathSegment}", h.${method.handlerName})`);
|
|
385
|
-
handler = insertBeforeMethodMarker(handler, HANDLER_FUNCS_MARKER, AUTH_HANDLER_FUNCS_MARKER, [
|
|
386
|
-
`func (h *Handler) ${method.handlerName}(c *gin.Context) {`,
|
|
387
|
-
`\tvar in ${inputName}`,
|
|
388
|
-
`\tif err := c.ShouldBindJSON(&in); err != nil {`,
|
|
389
|
-
`\t\tc.Error(httpx.BindErr(err))`,
|
|
390
|
-
`\t\treturn`,
|
|
391
|
-
`\t}`,
|
|
392
|
-
`\tm, err := ${isCqrs(paths) ? `h.commands.${method.pascalName}` : `h.svc.${method.pascalName}`}(c.Request.Context(), in)`,
|
|
393
|
-
`\tif err != nil {`,
|
|
394
|
-
`\t\tc.Error(err)`,
|
|
395
|
-
`\t\treturn`,
|
|
396
|
-
`\t}`,
|
|
397
|
-
`\tc.JSON(http.StatusCreated, toResponse(m))`,
|
|
398
|
-
`}`,
|
|
399
|
-
``,
|
|
400
|
-
].join("\n"));
|
|
401
|
-
writeHandler(paths.handlerPath, handler, goModule, ["net/http", "httpx"]);
|
|
402
|
-
const commandMethod = [
|
|
403
|
-
`func (h *CommandHandler) ${method.pascalName}(ctx context.Context, in ${inputName}) (*model.${naming.pascalName}, error) {`,
|
|
404
|
-
`\t// TODO: implement "${method.name}" — this stub does nothing yet`,
|
|
405
|
-
`\t_ = in`,
|
|
406
|
-
`\treturn nil, apperror.NewInternal()`,
|
|
407
|
-
`}`,
|
|
408
|
-
``,
|
|
409
|
-
].join("\n");
|
|
410
|
-
const serviceFacade = [
|
|
411
|
-
`func (s *Service) ${method.pascalName}(ctx context.Context, in ${inputName}) (*model.${naming.pascalName}, error) {`,
|
|
412
|
-
`\treturn s.commands.${method.pascalName}(ctx, in)`,
|
|
413
|
-
`}`,
|
|
414
|
-
``,
|
|
415
|
-
].join("\n");
|
|
416
|
-
if (isCqrs(paths)) {
|
|
417
|
-
insertCqrsImplementation(paths, "post", commandMethod, goModule);
|
|
418
|
-
insertCqrsFacade(paths.servicePath, serviceFacade, goModule, naming.pkg);
|
|
419
|
-
}
|
|
420
|
-
else {
|
|
421
|
-
let service = fs_extra_1.default.readFileSync(paths.servicePath, "utf8");
|
|
422
|
-
service = insertBeforeMethodMarker(service, SERVICE_MARKER, AUTH_SERVICE_MARKER, commandMethod.replace("(h *CommandHandler)", "(s *Service)").replace("h.repo", "s.repo"));
|
|
423
|
-
fs_extra_1.default.writeFileSync(paths.servicePath, service);
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
function patchResourceAction(paths, method, type, goModule, modulePath) {
|
|
427
|
-
let handler = fs_extra_1.default.readFileSync(paths.handlerPath, "utf8");
|
|
428
|
-
const routeReceiver = handlerRouteReceiver(handler);
|
|
429
|
-
handler = insertBeforeMethodMarker(handler, HANDLER_ROUTES_MARKER, AUTH_HANDLER_ROUTES_MARKER, `${routeReceiver}.${routeCall(type)}("/:id/${method.pathSegment}", h.${method.handlerName})`);
|
|
430
|
-
handler = insertBeforeMethodMarker(handler, HANDLER_FUNCS_MARKER, AUTH_HANDLER_FUNCS_MARKER, [
|
|
431
|
-
`func (h *Handler) ${method.handlerName}(c *gin.Context) {`,
|
|
432
|
-
`\tid, ok := httpx.ParseID(c)`,
|
|
433
|
-
`\tif !ok {`,
|
|
434
|
-
`\t\treturn`,
|
|
435
|
-
`\t}`,
|
|
436
|
-
`\tif err := ${isCqrs(paths) ? `h.commands.${method.pascalName}` : `h.svc.${method.pascalName}`}(c.Request.Context(), id); err != nil {`,
|
|
437
|
-
`\t\tc.Error(err)`,
|
|
438
|
-
`\t\treturn`,
|
|
439
|
-
`\t}`,
|
|
440
|
-
`\tc.Status(http.StatusNotImplemented)`,
|
|
441
|
-
`}`,
|
|
442
|
-
``,
|
|
443
|
-
].join("\n"));
|
|
444
|
-
writeHandler(paths.handlerPath, handler, goModule, ["net/http", "httpx"]);
|
|
445
|
-
const commandMethod = [
|
|
446
|
-
`func (h *CommandHandler) ${method.pascalName}(ctx context.Context, id uuid.UUID) error {`,
|
|
447
|
-
`\t_ = ctx`,
|
|
448
|
-
`\t_ = id`,
|
|
449
|
-
`\treturn apperror.NewNotImplemented()`,
|
|
450
|
-
`}`,
|
|
451
|
-
``,
|
|
452
|
-
].join("\n");
|
|
453
|
-
const serviceFacade = [
|
|
454
|
-
`func (s *Service) ${method.pascalName}(ctx context.Context, id uuid.UUID) error {`,
|
|
455
|
-
`\treturn s.commands.${method.pascalName}(ctx, id)`,
|
|
456
|
-
`}`,
|
|
457
|
-
``,
|
|
458
|
-
].join("\n");
|
|
459
|
-
if (isCqrs(paths)) {
|
|
460
|
-
insertCqrsImplementation(paths, type, commandMethod, goModule);
|
|
461
|
-
insertCqrsFacade(paths.servicePath, serviceFacade, goModule, modulePath);
|
|
462
|
-
}
|
|
463
|
-
else {
|
|
464
|
-
let service = fs_extra_1.default.readFileSync(paths.servicePath, "utf8");
|
|
465
|
-
service = insertBeforeMethodMarker(service, SERVICE_MARKER, AUTH_SERVICE_MARKER, commandMethod.replace("(h *CommandHandler)", "(s *Service)"));
|
|
466
|
-
fs_extra_1.default.writeFileSync(paths.servicePath, service);
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
function patchDelete(paths, method, goModule, modulePath) {
|
|
470
|
-
let handler = fs_extra_1.default.readFileSync(paths.handlerPath, "utf8");
|
|
471
|
-
const routeReceiver = handlerRouteReceiver(handler);
|
|
472
|
-
handler = insertBeforeMethodMarker(handler, HANDLER_ROUTES_MARKER, AUTH_HANDLER_ROUTES_MARKER, `${routeReceiver}.DELETE("/:id/${method.pathSegment}", h.${method.handlerName})`);
|
|
473
|
-
handler = insertBeforeMethodMarker(handler, HANDLER_FUNCS_MARKER, AUTH_HANDLER_FUNCS_MARKER, [
|
|
474
|
-
`func (h *Handler) ${method.handlerName}(c *gin.Context) {`,
|
|
475
|
-
`\tid, ok := httpx.ParseID(c)`,
|
|
476
|
-
`\tif !ok {`,
|
|
477
|
-
`\t\treturn`,
|
|
478
|
-
`\t}`,
|
|
479
|
-
`\tif err := ${isCqrs(paths) ? `h.commands.${method.pascalName}` : `h.svc.${method.pascalName}`}(c.Request.Context(), id); err != nil {`,
|
|
480
|
-
`\t\tc.Error(err)`,
|
|
481
|
-
`\t\treturn`,
|
|
482
|
-
`\t}`,
|
|
483
|
-
`\tc.Status(http.StatusNoContent)`,
|
|
484
|
-
`}`,
|
|
485
|
-
``,
|
|
486
|
-
].join("\n"));
|
|
487
|
-
writeHandler(paths.handlerPath, handler, goModule, ["net/http", "httpx"]);
|
|
488
|
-
const commandMethod = [
|
|
489
|
-
`func (h *CommandHandler) ${method.pascalName}(ctx context.Context, id uuid.UUID) error {`,
|
|
490
|
-
`\t// TODO: implement "${method.name}" — this stub does nothing yet`,
|
|
491
|
-
`\treturn apperror.NewInternal()`,
|
|
492
|
-
`}`,
|
|
493
|
-
``,
|
|
494
|
-
].join("\n");
|
|
495
|
-
const serviceFacade = [
|
|
496
|
-
`func (s *Service) ${method.pascalName}(ctx context.Context, id uuid.UUID) error {`,
|
|
497
|
-
`\treturn s.commands.${method.pascalName}(ctx, id)`,
|
|
498
|
-
`}`,
|
|
499
|
-
``,
|
|
500
|
-
].join("\n");
|
|
501
|
-
if (isCqrs(paths)) {
|
|
502
|
-
insertCqrsImplementation(paths, "delete", commandMethod, goModule);
|
|
503
|
-
insertCqrsFacade(paths.servicePath, serviceFacade, goModule, modulePath);
|
|
504
|
-
}
|
|
505
|
-
else {
|
|
506
|
-
let service = fs_extra_1.default.readFileSync(paths.servicePath, "utf8");
|
|
507
|
-
service = insertBeforeMethodMarker(service, SERVICE_MARKER, AUTH_SERVICE_MARKER, commandMethod.replace("(h *CommandHandler)", "(s *Service)"));
|
|
508
|
-
fs_extra_1.default.writeFileSync(paths.servicePath, service);
|
|
509
|
-
}
|
|
510
|
-
}
|
|
511
|
-
function markersPresent(handlerPath, servicePath) {
|
|
512
|
-
return markersPresentForPaths({ handlerPath, servicePath });
|
|
513
|
-
}
|
|
514
|
-
function markersPresentForPaths(paths) {
|
|
515
|
-
const handler = fs_extra_1.default.readFileSync(paths.handlerPath, "utf8");
|
|
516
|
-
const service = fs_extra_1.default.readFileSync(paths.servicePath, "utf8");
|
|
517
|
-
const baseMarkers = hasMethodMarker(handler, HANDLER_ROUTES_MARKER, AUTH_HANDLER_ROUTES_MARKER) &&
|
|
518
|
-
hasMethodMarker(handler, HANDLER_FUNCS_MARKER, AUTH_HANDLER_FUNCS_MARKER) &&
|
|
519
|
-
((0, marker_patch_1.hasMarker)(handler, SERVICE_INTERFACE_MARKER) || handler.includes("svc *Service")) &&
|
|
520
|
-
hasMethodMarker(service, SERVICE_MARKER, AUTH_SERVICE_MARKER) &&
|
|
521
|
-
hasMethodMarker(service, REPO_INTERFACE_MARKER, AUTH_REPO_INTERFACE_MARKER);
|
|
522
|
-
const hasCommand = Boolean(paths.commandPath && fs_extra_1.default.existsSync(paths.commandPath));
|
|
523
|
-
const hasQuery = Boolean(paths.queryPath && fs_extra_1.default.existsSync(paths.queryPath));
|
|
524
|
-
if (!hasCommand && !hasQuery)
|
|
525
|
-
return baseMarkers;
|
|
526
|
-
if (!hasCommand || !hasQuery)
|
|
527
|
-
return false;
|
|
528
|
-
const commands = fs_extra_1.default.readFileSync(paths.commandPath, "utf8");
|
|
529
|
-
const queries = fs_extra_1.default.readFileSync(paths.queryPath, "utf8");
|
|
530
|
-
return (baseMarkers &&
|
|
531
|
-
(0, marker_patch_1.hasMarker)(commands, COMMAND_MARKER) &&
|
|
532
|
-
(0, marker_patch_1.hasMarker)(commands, COMMAND_INTERFACE_MARKER) &&
|
|
533
|
-
(0, marker_patch_1.hasMarker)(commands, COMMAND_REPO_INTERFACE_MARKER) &&
|
|
534
|
-
(0, marker_patch_1.hasMarker)(queries, QUERY_MARKER) &&
|
|
535
|
-
(0, marker_patch_1.hasMarker)(queries, QUERY_INTERFACE_MARKER) &&
|
|
536
|
-
(0, marker_patch_1.hasMarker)(queries, QUERY_REPO_INTERFACE_MARKER));
|
|
537
|
-
}
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
package user
|
|
2
|
-
|
|
3
|
-
import (
|
|
4
|
-
"context"
|
|
5
|
-
"time"
|
|
6
|
-
|
|
7
|
-
"{{goModule}}/internal/app/user/application"
|
|
8
|
-
|
|
9
|
-
"github.com/google/uuid"
|
|
10
|
-
)
|
|
11
|
-
|
|
12
|
-
// Dependencies is the explicit composition contract for the auth application.
|
|
13
|
-
// Each port represents one lifecycle or capability, so a new adapter does not
|
|
14
|
-
// have to implement unrelated token operations just to be wired in.
|
|
15
|
-
type Dependencies struct {
|
|
16
|
-
Repository UserRepository
|
|
17
|
-
RefreshTokens RefreshTokenStore
|
|
18
|
-
OAuthTransactions OAuthTransactionStore
|
|
19
|
-
RecoveryTokens RecoveryTokenStore
|
|
20
|
-
MFA MFAStore
|
|
21
|
-
Mailer AuthMailer
|
|
22
|
-
Providers ProviderRegistry
|
|
23
|
-
Roles RoleChecker
|
|
24
|
-
Clock func() time.Time
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// MFASettings is the operator-side MFA policy. Enabled is a capability
|
|
28
|
-
// switch; each user's enrollment is still disabled until that user confirms a
|
|
29
|
-
// TOTP code. Keeping both switches lets an operator turn the feature off
|
|
30
|
-
// without deleting user enrollments.
|
|
31
|
-
type MFASettings struct {
|
|
32
|
-
Enabled bool
|
|
33
|
-
Issuer string
|
|
34
|
-
EncryptionKey string
|
|
35
|
-
ChallengeTTL time.Duration
|
|
36
|
-
TOTPWindow int
|
|
37
|
-
RecoveryCodeCount int
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// AuthConfig contains only auth policy. The application layer intentionally
|
|
41
|
-
// does not depend on the generated project's shared config package.
|
|
42
|
-
type AuthConfig struct {
|
|
43
|
-
JWTSecret string
|
|
44
|
-
JWTAccessTTL time.Duration
|
|
45
|
-
JWTRefreshTTL time.Duration
|
|
46
|
-
JWTRefreshMaxTTL time.Duration
|
|
47
|
-
OAuthStateTTL time.Duration
|
|
48
|
-
PasswordResetTTL time.Duration
|
|
49
|
-
PasswordResetURL string
|
|
50
|
-
EmailVerifyTTL time.Duration
|
|
51
|
-
EmailVerifyURL string
|
|
52
|
-
MFA MFASettings
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// ProviderRegistry is the application-facing lookup port for external login
|
|
56
|
-
// providers. The concrete registry lives in the application package; callers
|
|
57
|
-
// may supply a test double or another registry without coupling Service to
|
|
58
|
-
// that implementation.
|
|
59
|
-
type ProviderRegistry interface {
|
|
60
|
-
Lookup(name string) (application.LoginProvider, bool)
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// MFAEnrollment is the storage-neutral representation returned by MFAStore.
|
|
64
|
-
// The secret is always encrypted at rest; it is never serialized by an API
|
|
65
|
-
// response.
|
|
66
|
-
type MFAEnrollment struct {
|
|
67
|
-
EncryptedSecret string
|
|
68
|
-
Enabled bool
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// MFAChallenge is a short-lived, one-use pre-session challenge. The raw
|
|
72
|
-
// challenge is returned only to the client; stores receive its hash.
|
|
73
|
-
type MFAChallenge struct {
|
|
74
|
-
UserID uuid.UUID
|
|
75
|
-
ExpiresAt time.Time
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// MFAStore owns durable MFA state. Implementations must make challenge and
|
|
79
|
-
// recovery-code consumption atomic so concurrent requests cannot reuse them.
|
|
80
|
-
type MFAStore interface {
|
|
81
|
-
GetEnrollment(ctx context.Context, userID uuid.UUID) (MFAEnrollment, bool, error)
|
|
82
|
-
PutPendingEnrollment(ctx context.Context, userID uuid.UUID, encryptedSecret string) error
|
|
83
|
-
ConfirmEnrollment(ctx context.Context, userID uuid.UUID, encryptedSecret string, recoveryCodeHashes []string) error
|
|
84
|
-
Disable(ctx context.Context, userID uuid.UUID) error
|
|
85
|
-
CreateChallenge(ctx context.Context, hash string, challenge MFAChallenge) error
|
|
86
|
-
ConsumeChallenge(ctx context.Context, hash string) (MFAChallenge, bool, error)
|
|
87
|
-
ConsumeRecoveryCode(ctx context.Context, userID uuid.UUID, hash string) (bool, error)
|
|
88
|
-
}
|