@h-ai/cli 1.0.0-beta.17 → 1.0.0-beta.19
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.en.md +6 -0
- package/README.ja.md +6 -0
- package/README.md +6 -0
- package/dist/{chunk-EWTIVFHN.js → chunk-2ZL5ORQ2.js} +94 -28
- package/dist/chunk-2ZL5ORQ2.js.map +1 -0
- package/dist/{chunk-S2ZP7BHG.js → chunk-L53P43B2.js} +138 -32
- package/dist/chunk-L53P43B2.js.map +1 -0
- package/dist/cli-config-templates-LWMD2I6V.js +5 -0
- package/dist/{cli-config-templates-MKVYSK3E.js.map → cli-config-templates-LWMD2I6V.js.map} +1 -1
- package/dist/{cli-skill-templates-M64VPXZ4.js → cli-skill-templates-D6S2OQ6X.js} +3 -3
- package/dist/{cli-skill-templates-M64VPXZ4.js.map → cli-skill-templates-D6S2OQ6X.js.map} +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2797 -666
- package/dist/index.js.map +1 -1
- package/dist/metafile-esm.json +1 -1
- package/package.json +22 -22
- package/templates/apps/admin/src/routes/+layout.svelte.hbs +16 -10
- package/templates/apps/admin/src/routes/+page.svelte.hbs +4 -4
- package/templates/apps/admin/src/routes/admin/+layout.svelte.hbs +17 -7
- package/templates/apps/admin/src/routes/admin/+page.svelte.hbs +26 -6
- package/templates/apps/admin/src/routes/admin/settings/+page.svelte.hbs +2 -2
- package/templates/apps/api/apps/{{projectName}}-contract/src/app-schemas.ts.hbs +6 -1
- package/templates/apps/api/apps/{{projectName}}-contract/src/{{projectName}}-contract.ts.hbs +13 -5
- package/templates/apps/api/apps/{{projectName}}-contract/tests/contract.test.ts.hbs +13 -5
- package/templates/apps/api/apps/{{projectName}}-contract/tsup.config.ts.hbs +3 -1
- package/templates/apps/api/apps/{{projectName}}-serv/src/app.ts.hbs +71 -32
- package/templates/apps/api/apps/{{projectName}}-serv/src/build-metadata.ts.hbs +0 -1
- package/templates/apps/api/apps/{{projectName}}-serv/src/lib/server/init.ts.hbs +131 -54
- package/templates/apps/api/apps/{{projectName}}-serv/src/server/procedures/index.ts.hbs +39 -13
- package/templates/apps/api/apps/{{projectName}}-serv/tests/service.test.ts.hbs +78 -31
- package/templates/apps/api/apps/{{projectName}}-serv/tsup.config.ts.hbs +3 -1
- package/templates/apps/api/apps/{{projectName}}-serv/vitest.config.ts.hbs +3 -1
- package/templates/apps/api/e2e/api-serv.spec.ts.hbs +20 -3
- package/templates/apps/api/e2e/capture-evidence.ts.hbs +4 -1
- package/templates/apps/api/eslint.config.js.hbs +3 -1
- package/templates/apps/api/images/serv.Dockerfile.hbs +3 -4
- package/templates/apps/api/playwright.config.ts.hbs +23 -19
- package/templates/apps/api/pnpm-workspace.yaml.hbs +27 -16
- package/templates/apps/fullstack/apps/{{projectName}}-app/capacitor.config.ts.hbs +1 -3
- package/templates/apps/fullstack/apps/{{projectName}}-app/eslint.config.js.hbs +10 -2
- package/templates/apps/fullstack/apps/{{projectName}}-app/src/App.svelte.hbs +10 -2
- package/templates/apps/fullstack/apps/{{projectName}}-app/src/build-metadata.ts.hbs +0 -1
- package/templates/apps/fullstack/apps/{{projectName}}-app/src/lib/capacitor.ts.hbs +0 -1
- package/templates/apps/fullstack/apps/{{projectName}}-app/svelte.config.js.hbs +4 -1
- package/templates/apps/fullstack/apps/{{projectName}}-app/vite.config.ts.hbs +16 -4
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/src/App.svelte.hbs +8 -2
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/src/build-metadata.ts.hbs +0 -1
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/svelte.config.js.hbs +4 -1
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/vite.config.ts.hbs +16 -4
- package/templates/apps/fullstack/apps/{{projectName}}-web/eslint.config.js.hbs +10 -2
- package/templates/apps/fullstack/apps/{{projectName}}-web/src/build-metadata.ts.hbs +0 -1
- package/templates/apps/fullstack/apps/{{projectName}}-web/src/main.ts.hbs +0 -2
- package/templates/apps/fullstack/apps/{{projectName}}-web/svelte.config.js.hbs +4 -1
- package/templates/apps/fullstack/apps/{{projectName}}-web/vite.config.ts.hbs +19 -5
- package/templates/apps/fullstack/e2e/fullstack.spec.ts.hbs +111 -26
- package/templates/apps/fullstack/e2e/session-close.spec.ts.hbs +19 -5
- package/templates/apps/fullstack/eslint.config.js.hbs +3 -1
- package/templates/apps/fullstack/images/serv.Dockerfile.hbs +3 -4
- package/templates/apps/fullstack/images/web.Dockerfile.hbs +0 -2
- package/templates/apps/fullstack/packages/{{projectName}}-contract/src/admin-contract.ts.hbs +114 -23
- package/templates/apps/fullstack/packages/{{projectName}}-contract/src/admin-schemas.ts.hbs +57 -11
- package/templates/apps/fullstack/packages/{{projectName}}-contract/tsup.config.ts.hbs +3 -1
- package/templates/apps/fullstack/packages/{{projectName}}-serv/src/app-server.ts.hbs +65 -30
- package/templates/apps/fullstack/packages/{{projectName}}-serv/src/build-metadata.ts.hbs +0 -1
- package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/admin-service.ts.hbs +69 -21
- package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/business-settings.ts.hbs +21 -5
- package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/procedures/admin-procedures.ts.hbs +63 -11
- package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/procedures/app-procedures.ts.hbs +6 -1
- package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/procedures/index.ts.hbs +3 -1
- package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/runtime.ts.hbs +139 -49
- package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/security-roles.ts.hbs +43 -12
- package/templates/apps/fullstack/packages/{{projectName}}-serv/tests/app-server.test.ts.hbs +139 -28
- package/templates/apps/fullstack/packages/{{projectName}}-serv/tests/runtime.test.ts.hbs +33 -10
- package/templates/apps/fullstack/packages/{{projectName}}-serv/tests/security.test.ts.hbs +141 -25
- package/templates/apps/fullstack/packages/{{projectName}}-serv/tsup.config.ts.hbs +6 -2
- package/templates/apps/fullstack/packages/{{projectName}}-serv/vitest.config.ts.hbs +3 -1
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/api/api-client.ts.hbs +9 -2
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/api/feedback-client.ts.hbs +0 -3
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/api/sensitive-actions.svelte.ts.hbs +15 -3
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/AppShell.svelte.hbs +33 -7
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/FeedbackEntry.svelte.hbs +16 -2
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/audit.svelte.hbs +104 -13
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/feedback.svelte.hbs +37 -7
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/roles.svelte.hbs +9 -3
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/settings.svelte.hbs +25 -3
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/users.svelte.hbs +133 -19
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/security/role-label.ts.hbs +14 -2
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/stores/guide-store.ts.hbs +66 -34
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/stores/management-store.svelte.ts.hbs +132 -30
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/stores/theme-store.svelte.ts.hbs +8 -2
- package/templates/apps/fullstack/packages/{{projectName}}-shared/tests/api-client.test.ts.hbs +23 -5
- package/templates/apps/fullstack/packages/{{projectName}}-shared/tests/management-store.test.ts.hbs +143 -21
- package/templates/apps/fullstack/packages/{{projectName}}-shared/vitest.config.ts.hbs +7 -2
- package/templates/apps/fullstack/playwright.config.ts.hbs +9 -3
- package/templates/apps/fullstack/pnpm-workspace.yaml.hbs +30 -15
- package/templates/apps/h5/src/routes/+layout.svelte.hbs +57 -21
- package/templates/apps/h5/src/routes/+page.svelte.hbs +2 -2
- package/templates/apps/h5/src/routes/discover/+page.svelte.hbs +2 -2
- package/templates/apps/h5/src/routes/profile/+page.svelte.hbs +2 -2
- package/templates/apps/mobile-app/src/App.svelte.hbs +17 -5
- package/templates/apps/mobile-app/src/lib/api.ts.hbs +14 -3
- package/templates/apps/mobile-app/src/lib/app-config.ts.hbs +8 -1
- package/templates/apps/mobile-app/src/lib/pages/home.svelte.hbs +6 -2
- package/templates/apps/mobile-app/src/lib/pages/tasks.svelte.hbs +5 -1
- package/templates/apps/website/scripts/static-website.mjs.hbs +16 -3
- package/templates/apps/website/src/routes/+layout.svelte.hbs +44 -23
- package/templates/apps/website/src/routes/+page.svelte.hbs +4 -4
- package/templates/apps/website/src/routes/about/+page.svelte.hbs +2 -2
- package/templates/apps/website/src/routes/contact/+page.svelte.hbs +2 -2
- package/templates/apps/website/src/routes/services/+page.svelte.hbs +20 -5
- package/templates/base/e2e/app.spec.ts.hbs +49 -16
- package/templates/base/eslint-rules.js +44 -25
- package/templates/base/eslint.config.js.hbs +3 -1
- package/templates/base/images/web.Dockerfile.hbs +3 -4
- package/templates/base/package.json.hbs +5 -4
- package/templates/base/patches/daisyui-5.7.50.patch +8 -0
- package/templates/base/patches/deepmerge-4.3.1.patch +51 -0
- package/templates/base/playwright.config.ts +3 -1
- package/templates/base/pnpm-workspace.yaml.hbs +33 -16
- package/templates/base/src/app.d.ts.hbs +1 -1
- package/templates/base/src/hooks.server.ts.hbs +36 -27
- package/templates/base/src/lib/build-metadata.ts.hbs +0 -1
- package/templates/base/src/lib/server/init.ts.hbs +124 -51
- package/templates/base/src/lib/server/kit-config.ts.hbs +7 -4
- package/templates/base/src/routes/+layout.server.ts.hbs +2 -2
- package/templates/base/svelte.config.js.hbs +10 -100
- package/templates/base/tests/scaffold.test.ts.hbs +12 -3
- package/templates/base/tsconfig.json.hbs +7 -4
- package/templates/base/vite.config.ts.hbs +108 -14
- package/templates/base/vitest.config.ts.hbs +6 -2
- package/templates/features/ai/routes/api/chat/+server.ts +3 -1
- package/templates/features/iam/routes-admin/(auth)/auth/forgot-password/+page.svelte.hbs +3 -3
- package/templates/features/iam/routes-admin/(auth)/auth/login/+page.svelte.hbs +3 -3
- package/templates/features/iam/routes-admin/(auth)/auth/register/+page.svelte.hbs +3 -3
- package/templates/features/iam/routes-admin/(auth)/auth/reset-password/+page.svelte.hbs +3 -3
- package/templates/features/iam/routes-h5/auth/forgot-password/+page.svelte.hbs +3 -3
- package/templates/features/iam/routes-h5/auth/login/+page.svelte.hbs +3 -3
- package/templates/features/iam/routes-h5/auth/register/+page.svelte.hbs +3 -3
- package/templates/features/iam/routes-h5/auth/reset-password/+page.svelte.hbs +3 -3
- package/templates/features/iam/routes-website/auth/forgot-password/+page.svelte.hbs +3 -3
- package/templates/features/iam/routes-website/auth/login/+page.svelte.hbs +3 -3
- package/templates/features/iam/routes-website/auth/register/+page.svelte.hbs +3 -3
- package/templates/features/iam/routes-website/auth/reset-password/+page.svelte.hbs +3 -3
- package/templates/features/payment/routes/api/v1/payment/create/+server.ts +11 -2
- package/templates/shared/browser-check/startup.js +32 -6
- package/templates/shared/browser-startup.spec.ts +3 -1
- package/templates/shared/build-metadata.ts +18 -4
- package/templates/shared/e2e-env.ts +22 -6
- package/templates/shared/native-name.mjs +9 -2
- package/templates/shared/public-base-path.mjs +17 -4
- package/templates/shared/run-e2e.mjs +40 -9
- package/templates/shared/run-node.mjs +15 -4
- package/templates/shared/run-tauri.mjs +37 -8
- package/templates/shared/vite-runtime-config.ts +9 -3
- package/templates/skills/AGENTS.md +10 -73
- package/templates/skills/bridges/admin/AGENTS.md +9 -65
- package/templates/skills/bridges/api/AGENTS.md +8 -59
- package/templates/skills/bridges/fullstack/AGENTS.md.hbs +9 -54
- package/templates/skills/bridges/h5/AGENTS.md +8 -63
- package/templates/skills/bridges/mobile-app/AGENTS.md +8 -62
- package/templates/skills/bridges/website/AGENTS.md +8 -63
- package/templates/skills/hai-ai/acp.md +9 -1
- package/templates/skills/hai-ai/guide.md +17 -6
- package/templates/skills/hai-ai/reference.md +21 -12
- package/templates/skills/hai-api-client/reference.md +1 -1
- package/templates/skills/hai-app-create/development.md +3 -1
- package/templates/skills/hai-app-create/project.md +33 -0
- package/templates/skills/hai-app-create/reference.md +8 -8
- package/templates/skills/hai-app-tests/reference.md +1 -1
- package/templates/skills/hai-build/SKILL.md +3 -2
- package/templates/skills/hai-build/paths.md +1 -1
- package/templates/skills/hai-build/reference.md +11 -7
- package/templates/skills/hai-capacitor/reference.md +2 -0
- package/templates/skills/hai-ci/SKILL.md +1 -0
- package/templates/skills/hai-core/reference.md +3 -0
- package/templates/skills/hai-crypto/reference.md +2 -0
- package/templates/skills/hai-deploy/SKILL.md +7 -1
- package/templates/skills/hai-deploy/middleware.md +3 -1
- package/templates/skills/hai-framework-sync/SKILL.md +13 -9
- package/templates/skills/hai-framework-sync/sync.md +2 -0
- package/templates/skills/hai-kit/reference.md +16 -4
- package/templates/skills/hai-payment/reference.md +2 -0
- package/templates/skills/hai-reach/SKILL.md +2 -1
- package/templates/skills/hai-reach/reference.md +1 -1
- package/templates/skills/hai-reldb/SKILL.md +1 -1
- package/templates/skills/hai-reldb/reference.md +4 -0
- package/templates/skills/hai-serv/reference.md +12 -2
- package/templates/skills/hai-storage/reference.md +12 -3
- package/templates/skills/hai-ui/SKILL.md +3 -2
- package/templates/skills/hai-ui/guide.md +1 -1
- package/templates/skills/hai-ui/reference.md +23 -35
- package/templates/skills/hai-vecdb/reference.md +1 -1
- package/dist/chunk-EWTIVFHN.js.map +0 -1
- package/dist/chunk-S2ZP7BHG.js.map +0 -1
- package/dist/cli-config-templates-MKVYSK3E.js +0 -5
|
@@ -42,7 +42,10 @@ function startFeedback(): Promise<void> {
|
|
|
42
42
|
feedbackClosing = false
|
|
43
43
|
feedbackStarting = (async () => {
|
|
44
44
|
try {
|
|
45
|
-
const result = await feedback.init({
|
|
45
|
+
const result = await feedback.init({
|
|
46
|
+
...core.config.getOrThrow<import('@h-ai/feedback').FeedbackConfigInput>('feedback'),
|
|
47
|
+
...buildMetadata,
|
|
48
|
+
})
|
|
46
49
|
if (!result.success) {
|
|
47
50
|
if (feedbackFailure !== result.error.code)
|
|
48
51
|
core.logger.warn('Optional feedback unavailable; business runtime remains ready', { module: 'feedback', code: result.error.code })
|
|
@@ -76,62 +79,127 @@ async function closeFeedback() {
|
|
|
76
79
|
await feedbackStarting
|
|
77
80
|
return feedback.close()
|
|
78
81
|
}
|
|
79
|
-
|
|
80
82
|
{{/if}}
|
|
81
83
|
const runtime = core.module.createRuntime([
|
|
82
|
-
{
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
84
|
+
{
|
|
85
|
+
name: 'configuration',
|
|
86
|
+
start: () => {
|
|
87
|
+
core.init({
|
|
88
|
+
configDir: './config',
|
|
89
|
+
configSchemas: {
|
|
86
90
|
{{#if features.ai}}
|
|
87
|
-
|
|
91
|
+
ai: AIConfigSchema,
|
|
88
92
|
{{/if}}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
93
|
+
audit: AuditInitConfigSchema,
|
|
94
|
+
cache: CacheConfigSchema,
|
|
95
|
+
db: ReldbConfigSchema,
|
|
92
96
|
{{#if features.feedback}}
|
|
93
|
-
|
|
97
|
+
feedback: FeedbackConfigSchema,
|
|
94
98
|
{{/if}}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
99
|
+
iam: IamConfigSchema,
|
|
100
|
+
reach: ReachConfigSchema,
|
|
101
|
+
scheduler: SchedulerConfigSchema,
|
|
102
|
+
serv: ServConfigSchema,
|
|
103
|
+
storage: StorageConfigSchema,
|
|
100
104
|
{{#if features.vecdb}}
|
|
101
|
-
|
|
105
|
+
vecdb: VecdbConfigSchema,
|
|
102
106
|
{{/if}}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
},
|
|
113
|
-
{
|
|
107
|
+
},
|
|
108
|
+
})
|
|
109
|
+
},
|
|
110
|
+
close: () => { },
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
name: 'serv-config',
|
|
114
|
+
start: () => core.config.validate('serv', ServConfigSchema),
|
|
115
|
+
close: () => { },
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
name: 'reldb',
|
|
119
|
+
start: async () => {
|
|
120
|
+
const config = ReldbConfigSchema.parse(core.config.getOrThrow('db'))
|
|
121
|
+
if (config.type === 'sqlite' && config.database !== ':memory:')
|
|
122
|
+
await mkdir(path.dirname(config.database), { recursive: true })
|
|
123
|
+
return reldb.init(config)
|
|
124
|
+
},
|
|
125
|
+
close: () => reldb.close(),
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
name: 'cache',
|
|
129
|
+
start: () => cache.init(CacheConfigSchema.parse(core.config.getOrThrow('cache'))),
|
|
130
|
+
close: () => cache.close(),
|
|
131
|
+
},
|
|
114
132
|
{{#if features.vecdb}}
|
|
115
|
-
{
|
|
133
|
+
{
|
|
134
|
+
name: 'vecdb',
|
|
135
|
+
start: () => vecdb.init(VecdbConfigSchema.parse(core.config.getOrThrow('vecdb'))),
|
|
136
|
+
close: () => vecdb.close(),
|
|
137
|
+
},
|
|
116
138
|
{{/if}}
|
|
117
|
-
{
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
{
|
|
139
|
+
{
|
|
140
|
+
name: 'crypto',
|
|
141
|
+
start: () => crypto.init(),
|
|
142
|
+
close: () => crypto.close(),
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
name: 'storage',
|
|
146
|
+
start: () => storage.init(StorageConfigSchema.parse(core.config.getOrThrow('storage'))),
|
|
147
|
+
close: () => storage.close(),
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
name: 'reach',
|
|
151
|
+
enabled: () => Boolean(core.config.get('reach')),
|
|
152
|
+
start: () => reach.init(ReachConfigSchema.parse(core.config.getOrThrow('reach'))),
|
|
153
|
+
close: () => reach.close(),
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
name: 'audit',
|
|
157
|
+
start: () => audit.init({
|
|
158
|
+
...AuditInitConfigSchema.parse(core.config.get('audit') ?? {}),
|
|
159
|
+
storage,
|
|
160
|
+
reach,
|
|
161
|
+
}),
|
|
162
|
+
close: () => audit.close(),
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
name: 'iam',
|
|
166
|
+
start: () => iam.init({
|
|
167
|
+
...IamConfigSchema.parse(core.config.getOrThrow('iam')),
|
|
168
|
+
reach,
|
|
169
|
+
}),
|
|
170
|
+
close: () => iam.close(),
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
name: 'audit-jobs',
|
|
174
|
+
enabled: () => Boolean(audit.schedulerPlan),
|
|
175
|
+
start: startAuditJobs,
|
|
176
|
+
close: () => scheduler.close(),
|
|
177
|
+
},
|
|
123
178
|
{{#if features.feedback}}
|
|
124
|
-
{
|
|
179
|
+
{
|
|
180
|
+
name: 'feedback',
|
|
181
|
+
enabled: () => Boolean(core.config.get('feedback')),
|
|
182
|
+
start: startFeedback,
|
|
183
|
+
close: closeFeedback,
|
|
184
|
+
},
|
|
125
185
|
{{/if}}
|
|
126
186
|
{{#if features.ai}}
|
|
127
|
-
{
|
|
187
|
+
{
|
|
188
|
+
name: 'ai',
|
|
189
|
+
start: () => ai.init(AIConfigSchema.parse(core.config.getOrThrow('ai'))),
|
|
190
|
+
close: () => ai.close(),
|
|
191
|
+
},
|
|
128
192
|
{{/if}}
|
|
129
|
-
{
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
193
|
+
{
|
|
194
|
+
name: 'bootstrap',
|
|
195
|
+
start: async () => {
|
|
196
|
+
requireInitialized(await initBusinessSettings(), 'business-settings')
|
|
197
|
+
await ensureRolesAndAdmin()
|
|
198
|
+
const app = core.config.getOrThrow<CoreConfig>('core')
|
|
199
|
+
core.logger.info('Application initialized', { name: app.name, version: app.version, environment: app.env })
|
|
200
|
+
},
|
|
201
|
+
close: () => { },
|
|
202
|
+
},
|
|
135
203
|
])
|
|
136
204
|
|
|
137
205
|
/** 仅声明模块和业务钩子,失败回滚、并发及关闭重试由 core 负责。 */
|
|
@@ -149,15 +217,27 @@ export async function closeModules(): Promise<void> {
|
|
|
149
217
|
|
|
150
218
|
/** 此函数仅用于进程启动边界,业务请求继续返回 HaiResult。 */
|
|
151
219
|
function requireInitialized<T>(result: HaiResult<T>, moduleName: string): T {
|
|
152
|
-
if (!result.success)
|
|
153
|
-
throw Object.assign(new Error(moduleName), {
|
|
220
|
+
if (!result.success) {
|
|
221
|
+
throw Object.assign(new Error(moduleName), {
|
|
222
|
+
code: result.error.code,
|
|
223
|
+
ext: {
|
|
224
|
+
fields: moduleName === 'iam-bootstrap'
|
|
225
|
+
? ['HAI_ADMIN_DEFAULT_PASSWORD']
|
|
226
|
+
: [],
|
|
227
|
+
},
|
|
228
|
+
})
|
|
229
|
+
}
|
|
154
230
|
return result.data
|
|
155
231
|
}
|
|
156
232
|
|
|
157
233
|
async function ensureRolesAndAdmin(): Promise<void> {
|
|
158
234
|
requireInitialized(await seedAppDuties(), 'app-duties')
|
|
159
235
|
await ensureInitialSystemAdmin()
|
|
160
|
-
for (const [username, password] of [
|
|
236
|
+
for (const [username, password] of [
|
|
237
|
+
['audit_admin', process.env.HAI_AUDIT_ADMIN_PASSWORD],
|
|
238
|
+
['config_admin', process.env.HAI_CONFIG_ADMIN_PASSWORD],
|
|
239
|
+
['business_operator', process.env.HAI_BUSINESS_OPERATOR_PASSWORD],
|
|
240
|
+
] as const)
|
|
161
241
|
requireInitialized(await bootstrapDutyAccount(username, password), 'app-duty-bootstrap')
|
|
162
242
|
}
|
|
163
243
|
|
|
@@ -166,9 +246,19 @@ async function ensureInitialSystemAdmin(): Promise<void> {
|
|
|
166
246
|
if (users.total > 0)
|
|
167
247
|
return
|
|
168
248
|
const password = process.env.HAI_ADMIN_DEFAULT_PASSWORD?.trim()
|
|
169
|
-
if (!password)
|
|
170
|
-
throw Object.assign(new Error('Administrator bootstrap configuration required'), {
|
|
171
|
-
|
|
249
|
+
if (!password) {
|
|
250
|
+
throw Object.assign(new Error('Administrator bootstrap configuration required'), {
|
|
251
|
+
code: HaiConfigError.CONFIG_ENV_VAR_MISSING.code,
|
|
252
|
+
ext: {
|
|
253
|
+
fields: ['HAI_ADMIN_DEFAULT_PASSWORD'],
|
|
254
|
+
},
|
|
255
|
+
})
|
|
256
|
+
}
|
|
257
|
+
const registered = requireInitialized(await iam.user.createUser({
|
|
258
|
+
username: 'admin',
|
|
259
|
+
password,
|
|
260
|
+
displayName: serverM('admin'),
|
|
261
|
+
}), 'iam-bootstrap')
|
|
172
262
|
const adminRole = requireInitialized(await iam.authz.getRoleByCode('admin'), 'iam-role')
|
|
173
263
|
if (!adminRole)
|
|
174
264
|
throw new Error('Administrator role is missing')
|
package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/security-roles.ts.hbs
CHANGED
|
@@ -8,14 +8,26 @@ export const APP_DUTIES = {
|
|
|
8
8
|
admin: ['app.users.manage', 'app.roles.manage'],
|
|
9
9
|
audit_admin: ['audit:read', 'audit:backup'],
|
|
10
10
|
config_admin: ['app.config.manage'],
|
|
11
|
-
business_operator: [
|
|
11
|
+
business_operator: [
|
|
12
|
+
'feedback:create',
|
|
13
|
+
'feedback:read',
|
|
14
|
+
'feedback:process',
|
|
15
|
+
'feedback:reopen',
|
|
16
|
+
'feedback:export',
|
|
17
|
+
],
|
|
12
18
|
user: ['feedback:create'],
|
|
13
19
|
} as const
|
|
14
20
|
|
|
15
21
|
export async function seedAppDuties(): Promise<HaiResult<void>> {
|
|
16
22
|
if (iam.config?.rbac.superAdminRole && iam.config.rbac.superAdminRole in APP_DUTIES)
|
|
17
23
|
return err(HaiCommonError.VALIDATION_ERROR)
|
|
18
|
-
for (const code of [
|
|
24
|
+
for (const code of [
|
|
25
|
+
'admin',
|
|
26
|
+
'audit_admin',
|
|
27
|
+
'config_admin',
|
|
28
|
+
'business_operator',
|
|
29
|
+
'user',
|
|
30
|
+
] as const) {
|
|
19
31
|
const permissionIds: string[] = []
|
|
20
32
|
for (const permissionCode of APP_DUTIES[code]) {
|
|
21
33
|
let permission = await iam.authz.getPermissionByCode(permissionCode)
|
|
@@ -25,8 +37,11 @@ export async function seedAppDuties(): Promise<HaiResult<void>> {
|
|
|
25
37
|
permission = await iam.authz.createPermission({ code: permissionCode, name: permissionCode, type: 'api' })
|
|
26
38
|
if (!permission.success) {
|
|
27
39
|
permission = await iam.authz.getPermissionByCode(permissionCode)
|
|
28
|
-
if (!permission.success || !permission.data)
|
|
29
|
-
return permission.success
|
|
40
|
+
if (!permission.success || !permission.data) {
|
|
41
|
+
return permission.success
|
|
42
|
+
? err(HaiCommonError.INTERNAL_ERROR)
|
|
43
|
+
: permission
|
|
44
|
+
}
|
|
30
45
|
}
|
|
31
46
|
}
|
|
32
47
|
if (permission.data)
|
|
@@ -36,16 +51,26 @@ export async function seedAppDuties(): Promise<HaiResult<void>> {
|
|
|
36
51
|
if (!role.success)
|
|
37
52
|
return role
|
|
38
53
|
if (!role.data) {
|
|
39
|
-
role = await iam.authz.createRole({
|
|
54
|
+
role = await iam.authz.createRole({
|
|
55
|
+
code,
|
|
56
|
+
name: serverM(code),
|
|
57
|
+
isSystem: true,
|
|
58
|
+
})
|
|
40
59
|
if (!role.success) {
|
|
41
60
|
role = await iam.authz.getRoleByCode(code)
|
|
42
|
-
if (!role.success || !role.data)
|
|
43
|
-
return role.success
|
|
61
|
+
if (!role.success || !role.data) {
|
|
62
|
+
return role.success
|
|
63
|
+
? err(HaiCommonError.INTERNAL_ERROR)
|
|
64
|
+
: role
|
|
65
|
+
}
|
|
44
66
|
}
|
|
45
67
|
}
|
|
46
68
|
if (!role.data)
|
|
47
69
|
return err(HaiCommonError.INTERNAL_ERROR)
|
|
48
|
-
const updated = await iam.authz.updateRole(role.data.id, {
|
|
70
|
+
const updated = await iam.authz.updateRole(role.data.id, {
|
|
71
|
+
name: serverM(code),
|
|
72
|
+
permissionIds,
|
|
73
|
+
})
|
|
49
74
|
if (!updated.success)
|
|
50
75
|
return updated
|
|
51
76
|
}
|
|
@@ -58,11 +83,17 @@ export async function bootstrapDutyAccount(username: 'audit_admin' | 'config_adm
|
|
|
58
83
|
if (!password)
|
|
59
84
|
return ok(undefined)
|
|
60
85
|
const created = await iam.user.createUser({ username, password })
|
|
61
|
-
if (!created.success)
|
|
62
|
-
return created.error.code === HaiIamError.USER_ALREADY_EXISTS.code
|
|
86
|
+
if (!created.success) {
|
|
87
|
+
return created.error.code === HaiIamError.USER_ALREADY_EXISTS.code
|
|
88
|
+
? ok(undefined)
|
|
89
|
+
: created
|
|
90
|
+
}
|
|
63
91
|
const role = await iam.authz.getRoleByCode(username)
|
|
64
|
-
if (!role.success || !role.data)
|
|
65
|
-
return role.success
|
|
92
|
+
if (!role.success || !role.data) {
|
|
93
|
+
return role.success
|
|
94
|
+
? err(HaiCommonError.INTERNAL_ERROR)
|
|
95
|
+
: role
|
|
96
|
+
}
|
|
66
97
|
const assigned = await iam.authz.syncRoles(created.data.user.id, [role.data.id])
|
|
67
98
|
if (!assigned.success) {
|
|
68
99
|
const removed = await iam.user.deleteUser(created.data.user.id)
|
|
@@ -34,8 +34,17 @@ interface LoginData { user: { id: string }, tokens: { accessToken: string, refre
|
|
|
34
34
|
async function call<T>(route: string, input?: object, token?: string, method = 'POST'): Promise<HaiResult<T>> {
|
|
35
35
|
const response = await transport.encryptedFetch(`http://localhost${PREFIX}${route}`, {
|
|
36
36
|
method,
|
|
37
|
-
headers: {
|
|
38
|
-
|
|
37
|
+
headers: {
|
|
38
|
+
'content-type': 'application/json',
|
|
39
|
+
...(token
|
|
40
|
+
? { authorization: `Bearer ${token}` }
|
|
41
|
+
: {}),
|
|
42
|
+
},
|
|
43
|
+
...(input
|
|
44
|
+
? {
|
|
45
|
+
body: JSON.stringify(input),
|
|
46
|
+
}
|
|
47
|
+
: {}),
|
|
39
48
|
})
|
|
40
49
|
return response.json() as Promise<HaiResult<T>>
|
|
41
50
|
}
|
|
@@ -49,7 +58,10 @@ beforeAll(async () => {
|
|
|
49
58
|
const key = crypto.asymmetric.generateKeyPair()
|
|
50
59
|
if (!key.success)
|
|
51
60
|
throw new Error(key.error.message)
|
|
52
|
-
vi.stubEnv('HAI_CRYPTO_TRANSPORT_SERVERKEYRING', JSON.stringify({
|
|
61
|
+
vi.stubEnv('HAI_CRYPTO_TRANSPORT_SERVERKEYRING', JSON.stringify({
|
|
62
|
+
activeKeyId: 'test-v1',
|
|
63
|
+
keys: { 'test-v1': key.data },
|
|
64
|
+
}))
|
|
53
65
|
app = createServerApp()
|
|
54
66
|
transport = crypto.transport.createClient({
|
|
55
67
|
keyExchangeUrl: `http://localhost${PREFIX}/_hai/key-exchange`,
|
|
@@ -99,7 +111,12 @@ describe('{{fullstack.servPackageName}} mini administration', () => {
|
|
|
99
111
|
if (!result.success)
|
|
100
112
|
return
|
|
101
113
|
const config = core.config.getOrThrow<CoreConfig>('core')
|
|
102
|
-
expect(result.data).toMatchObject({
|
|
114
|
+
expect(result.data).toMatchObject({
|
|
115
|
+
name: config.name,
|
|
116
|
+
version: config.version,
|
|
117
|
+
env: config.env,
|
|
118
|
+
defaultLocale: config.defaultLocale,
|
|
119
|
+
})
|
|
103
120
|
const invalid = await transport.encryptedFetch(`http://localhost${PREFIX}/app/echo`, {
|
|
104
121
|
method: 'POST',
|
|
105
122
|
headers: { 'content-type': 'application/json' },
|
|
@@ -129,7 +146,14 @@ describe('{{fullstack.servPackageName}} mini administration', () => {
|
|
|
129
146
|
it('mounts public auth routes while keeping default IAM administration routes private', async () => {
|
|
130
147
|
const config = await transport.encryptedFetch(`http://localhost${PREFIX}/auth/config`)
|
|
131
148
|
expect(config.status).toBe(200)
|
|
132
|
-
for (const route of [
|
|
149
|
+
for (const route of [
|
|
150
|
+
'/auth/register',
|
|
151
|
+
'/auth/login/ldap',
|
|
152
|
+
'/auth/login/otp',
|
|
153
|
+
'/auth/otp/send',
|
|
154
|
+
'/auth/password-reset/request',
|
|
155
|
+
'/auth/password-reset/confirm',
|
|
156
|
+
]) {
|
|
133
157
|
const response = await transport.encryptedFetch(`http://localhost${PREFIX}${route}`, {
|
|
134
158
|
method: 'POST',
|
|
135
159
|
headers: { 'content-type': 'application/json' },
|
|
@@ -144,14 +168,21 @@ describe('{{fullstack.servPackageName}} mini administration', () => {
|
|
|
144
168
|
const response = await transport.encryptedFetch(`http://localhost${PREFIX}${route}`, {
|
|
145
169
|
method,
|
|
146
170
|
headers: { 'content-type': 'application/json', 'authorization': `Bearer ${adminToken}` },
|
|
147
|
-
...(method === 'POST'
|
|
171
|
+
...(method === 'POST'
|
|
172
|
+
? { body: '{}' }
|
|
173
|
+
: {}),
|
|
148
174
|
})
|
|
149
175
|
expect(response.status).toBe(404)
|
|
150
176
|
}
|
|
151
177
|
})
|
|
152
178
|
|
|
153
179
|
it('creates a normal user and enforces guards on every administration API', async () => {
|
|
154
|
-
const created = await call<AdminUser>('/admin/users/create', {
|
|
180
|
+
const created = await call<AdminUser>('/admin/users/create', {
|
|
181
|
+
username: 'normal-user',
|
|
182
|
+
password: TEST_PASSWORD,
|
|
183
|
+
role: 'user',
|
|
184
|
+
accountStatus: 'active',
|
|
185
|
+
}, adminToken)
|
|
155
186
|
expect(created.success).toBe(true)
|
|
156
187
|
if (!created.success)
|
|
157
188
|
return
|
|
@@ -162,14 +193,35 @@ describe('{{fullstack.servPackageName}} mini administration', () => {
|
|
|
162
193
|
return
|
|
163
194
|
userToken = loggedIn.data.tokens.accessToken
|
|
164
195
|
for (const [route, input] of [
|
|
165
|
-
[
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
[
|
|
196
|
+
[
|
|
197
|
+
'/admin/users/list',
|
|
198
|
+
{ page: 1, pageSize: 20 },
|
|
199
|
+
],
|
|
200
|
+
[
|
|
201
|
+
'/admin/users/create',
|
|
202
|
+
{ username: 'forbidden', password: TEST_PASSWORD },
|
|
203
|
+
],
|
|
204
|
+
[
|
|
205
|
+
'/admin/users/update',
|
|
206
|
+
{ id: adminId, accountStatus: 'dormant' },
|
|
207
|
+
],
|
|
208
|
+
[
|
|
209
|
+
'/admin/users/delete',
|
|
210
|
+
{ id: adminId },
|
|
211
|
+
],
|
|
212
|
+
[
|
|
213
|
+
'/admin/users/avatar',
|
|
214
|
+
{ id: adminId, contentType: 'image/png', base64: PNG },
|
|
215
|
+
],
|
|
170
216
|
['/admin/roles/list', undefined],
|
|
171
|
-
[
|
|
172
|
-
|
|
217
|
+
[
|
|
218
|
+
'/admin/roles/update',
|
|
219
|
+
{ id: 'missing', description: 'x' },
|
|
220
|
+
],
|
|
221
|
+
[
|
|
222
|
+
'/admin/audit/list',
|
|
223
|
+
{ page: 1, pageSize: 20 },
|
|
224
|
+
],
|
|
173
225
|
] as const) {
|
|
174
226
|
const denied = await call(route, input, userToken)
|
|
175
227
|
expect(denied.success).toBe(false)
|
|
@@ -179,8 +231,16 @@ describe('{{fullstack.servPackageName}} mini administration', () => {
|
|
|
179
231
|
})
|
|
180
232
|
|
|
181
233
|
it('allows refresh-token body transport only for an identified trusted native origin', async () => {
|
|
182
|
-
for (const [clientName, origin] of [
|
|
183
|
-
|
|
234
|
+
for (const [clientName, origin] of [
|
|
235
|
+
['{{projectName}}-app', 'capacitor://localhost'],
|
|
236
|
+
['{{projectName}}-desktop', 'http://tauri.localhost'],
|
|
237
|
+
] as const) {
|
|
238
|
+
const headers = {
|
|
239
|
+
'content-type': 'application/json',
|
|
240
|
+
'x-request-client': clientName,
|
|
241
|
+
'x-hai-token-transport': 'body',
|
|
242
|
+
origin,
|
|
243
|
+
}
|
|
184
244
|
const login = await transport.encryptedFetch(`http://localhost${PREFIX}/auth/login`, {
|
|
185
245
|
method: 'POST',
|
|
186
246
|
headers,
|
|
@@ -202,10 +262,18 @@ describe('{{fullstack.servPackageName}} mini administration', () => {
|
|
|
202
262
|
if (refreshed.success)
|
|
203
263
|
expect(refreshed.data.tokens.refreshToken).not.toBe(result.data.tokens.refreshToken)
|
|
204
264
|
}
|
|
205
|
-
for (const [clientName, origin] of [
|
|
265
|
+
for (const [clientName, origin] of [
|
|
266
|
+
['{{projectName}}-app', 'http://untrusted.example'],
|
|
267
|
+
['unrecognized-client', 'capacitor://localhost'],
|
|
268
|
+
] as const) {
|
|
206
269
|
const response = await transport.encryptedFetch(`http://localhost${PREFIX}/auth/login`, {
|
|
207
270
|
method: 'POST',
|
|
208
|
-
headers: {
|
|
271
|
+
headers: {
|
|
272
|
+
'content-type': 'application/json',
|
|
273
|
+
'x-request-client': clientName,
|
|
274
|
+
'x-hai-token-transport': 'body',
|
|
275
|
+
origin,
|
|
276
|
+
},
|
|
209
277
|
body: JSON.stringify({ identifier: 'admin', password: TEST_PASSWORD }),
|
|
210
278
|
})
|
|
211
279
|
const result = await response.json() as HaiResult<LoginData>
|
|
@@ -222,7 +290,20 @@ describe('{{fullstack.servPackageName}} mini administration', () => {
|
|
|
222
290
|
expect(listed.data.items).toHaveLength(1)
|
|
223
291
|
expect(listed.data.total).toBe(2)
|
|
224
292
|
}
|
|
225
|
-
for (const [route, input] of [
|
|
293
|
+
for (const [route, input] of [
|
|
294
|
+
[
|
|
295
|
+
'/admin/users/delete',
|
|
296
|
+
{ id: adminId },
|
|
297
|
+
],
|
|
298
|
+
[
|
|
299
|
+
'/admin/users/update',
|
|
300
|
+
{ id: adminId, accountStatus: 'dormant' },
|
|
301
|
+
],
|
|
302
|
+
[
|
|
303
|
+
'/admin/users/update',
|
|
304
|
+
{ id: adminId, role: 'user' },
|
|
305
|
+
],
|
|
306
|
+
] as const) {
|
|
226
307
|
const denied = await call(route, input, adminToken)
|
|
227
308
|
expect(denied.success).toBe(false)
|
|
228
309
|
if (!denied.success)
|
|
@@ -248,7 +329,10 @@ describe('{{fullstack.servPackageName}} mini administration', () => {
|
|
|
248
329
|
expect(invalid.success).toBe(false)
|
|
249
330
|
if (!invalid.success)
|
|
250
331
|
expect(invalid.error.httpStatus).toBe(400)
|
|
251
|
-
const oversized = await call('/profile/avatar', {
|
|
332
|
+
const oversized = await call('/profile/avatar', {
|
|
333
|
+
contentType: 'image/png',
|
|
334
|
+
base64: Buffer.alloc(512 * 1024 + 1).toString('base64'),
|
|
335
|
+
}, userToken)
|
|
252
336
|
expect(oversized.success).toBe(false)
|
|
253
337
|
const anonymous = await call('/profile/avatar', { contentType: 'image/png', base64: PNG })
|
|
254
338
|
expect(anonymous.success).toBe(false)
|
|
@@ -296,7 +380,13 @@ describe('{{fullstack.servPackageName}} mini administration', () => {
|
|
|
296
380
|
const roles = await call<{ items: { id: string, code: string, userCount: number }[] }>('/admin/roles/list', undefined, adminToken)
|
|
297
381
|
expect(roles.success).toBe(true)
|
|
298
382
|
if (roles.success) {
|
|
299
|
-
expect(roles.data.items.map(item => item.code).sort()).toEqual([
|
|
383
|
+
expect(roles.data.items.map(item => item.code).sort()).toEqual([
|
|
384
|
+
'admin',
|
|
385
|
+
'audit_admin',
|
|
386
|
+
'business_operator',
|
|
387
|
+
'config_admin',
|
|
388
|
+
'user',
|
|
389
|
+
])
|
|
300
390
|
const role = roles.data.items.find(item => item.code === 'user')!
|
|
301
391
|
expect(role.userCount).toBe(1)
|
|
302
392
|
expect((await call('/admin/roles/update', { id: role.id, description: 'Updated description' }, adminToken)).success).toBe(true)
|
|
@@ -324,7 +414,12 @@ describe('{{fullstack.servPackageName}} mini administration', () => {
|
|
|
324
414
|
const before = await iam.user.getUser(user.id)
|
|
325
415
|
expect(before.success).toBe(true)
|
|
326
416
|
const sync = vi.spyOn(iam.authz, 'syncRoles').mockResolvedValueOnce(err(HaiCommonError.API_ERROR, 'Injected role persistence failure'))
|
|
327
|
-
const failed = await call('/admin/users/update', {
|
|
417
|
+
const failed = await call('/admin/users/update', {
|
|
418
|
+
id: user.id,
|
|
419
|
+
role: 'admin',
|
|
420
|
+
email: 'changed@example.com',
|
|
421
|
+
displayName: 'Should not change',
|
|
422
|
+
}, adminToken)
|
|
328
423
|
sync.mockRestore()
|
|
329
424
|
expect(failed.success).toBe(false)
|
|
330
425
|
const after = await iam.user.getUser(user.id)
|
|
@@ -338,7 +433,7 @@ describe('{{fullstack.servPackageName}} mini administration', () => {
|
|
|
338
433
|
const log = vi.spyOn(audit, 'log').mockImplementation((input, tx) => input.action === 'update' && input.resource === 'profile'
|
|
339
434
|
? Promise.resolve(err(HaiCommonError.API_ERROR, 'Injected audit persistence failure'))
|
|
340
435
|
: originalLog(input, tx))
|
|
341
|
-
const logger = vi.spyOn(core.logger, 'error').mockImplementation(() => {})
|
|
436
|
+
const logger = vi.spyOn(core.logger, 'error').mockImplementation(() => { })
|
|
342
437
|
try {
|
|
343
438
|
const result = await call<AdminUser>('/profile/update', { displayName: 'Committed profile' }, userToken)
|
|
344
439
|
expect(result.success).toBe(true)
|
|
@@ -374,9 +469,22 @@ describe('{{fullstack.servPackageName}} mini administration', () => {
|
|
|
374
469
|
const snapshot = await iam.session.get(userToken)
|
|
375
470
|
if (!snapshot.success || !snapshot.data)
|
|
376
471
|
throw new Error('Session unavailable')
|
|
377
|
-
const stale = await iam.session.update(userToken, {
|
|
472
|
+
const stale = await iam.session.update(userToken, {
|
|
473
|
+
roles: ['admin'],
|
|
474
|
+
permissions: ['*'],
|
|
475
|
+
}, snapshot.data.version)
|
|
378
476
|
expect(stale.success).toBe(true)
|
|
379
|
-
for (const [route, input] of [
|
|
477
|
+
for (const [route, input] of [
|
|
478
|
+
[
|
|
479
|
+
'/admin/users/list',
|
|
480
|
+
{ page: 1, pageSize: 20 },
|
|
481
|
+
],
|
|
482
|
+
['/admin/roles/list', undefined],
|
|
483
|
+
[
|
|
484
|
+
'/admin/audit/list',
|
|
485
|
+
{ page: 1, pageSize: 20 },
|
|
486
|
+
],
|
|
487
|
+
] as const) {
|
|
380
488
|
const denied = await call(route, input, userToken)
|
|
381
489
|
expect(denied.success).toBe(false)
|
|
382
490
|
if (!denied.success)
|
|
@@ -415,8 +523,8 @@ describe('{{fullstack.servPackageName}} mini administration', () => {
|
|
|
415
523
|
expect(otherLogin.success).toBe(true)
|
|
416
524
|
if (!otherLogin.success)
|
|
417
525
|
return
|
|
418
|
-
let resume: () => void = () => {}
|
|
419
|
-
let entered: () => void = () => {}
|
|
526
|
+
let resume: () => void = () => { }
|
|
527
|
+
let entered: () => void = () => { }
|
|
420
528
|
const paused = new Promise<void>((resolve) => {
|
|
421
529
|
resume = resolve
|
|
422
530
|
})
|
|
@@ -467,7 +575,10 @@ describe('{{fullstack.servPackageName}} mini administration', () => {
|
|
|
467
575
|
call('/admin/users/update', { id: adminId, accountStatus: 'dormant' }, loggedIn.data.tokens.accessToken),
|
|
468
576
|
])
|
|
469
577
|
expect(results.filter(result => result.success)).toHaveLength(1)
|
|
470
|
-
const admins = await iam.user.listUsers({
|
|
578
|
+
const admins = await iam.user.listUsers({
|
|
579
|
+
accountStatus: 'active',
|
|
580
|
+
include: ['roles'],
|
|
581
|
+
})
|
|
471
582
|
expect(admins.success).toBe(true)
|
|
472
583
|
if (admins.success) {
|
|
473
584
|
const surviving = admins.data.items.filter(item => item.roles?.some(role => role.code === 'admin'))
|