@h-ai/cli 1.0.0-beta.18 → 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 +4 -0
- package/README.ja.md +4 -0
- package/README.md +4 -0
- package/dist/{chunk-6YZBC5MO.js → chunk-2ZL5ORQ2.js} +85 -25
- package/dist/chunk-2ZL5ORQ2.js.map +1 -0
- package/dist/{chunk-7NVRYYHN.js → chunk-L53P43B2.js} +129 -31
- package/dist/chunk-L53P43B2.js.map +1 -0
- package/dist/cli-config-templates-LWMD2I6V.js +5 -0
- package/dist/{cli-config-templates-QG5HOYR5.js.map → cli-config-templates-LWMD2I6V.js.map} +1 -1
- package/dist/{cli-skill-templates-W4I6ZKYW.js → cli-skill-templates-D6S2OQ6X.js} +3 -3
- package/dist/{cli-skill-templates-W4I6ZKYW.js.map → cli-skill-templates-D6S2OQ6X.js.map} +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2747 -671
- 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 +2 -0
- 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 +2 -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/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-6YZBC5MO.js.map +0 -1
- package/dist/chunk-7NVRYYHN.js.map +0 -1
- package/dist/cli-config-templates-QG5HOYR5.js +0 -5
|
@@ -2,13 +2,26 @@ import { IamListUsersInputSchema, IamPermissionSchema } from '@h-ai/api-contract
|
|
|
2
2
|
import { z } from 'zod'
|
|
3
3
|
|
|
4
4
|
/** 权限的敏感操作配置复用框架字段;职责授权仍由应用控制。 */
|
|
5
|
-
export const AdminPermissionPolicySchema = IamPermissionSchema.pick({
|
|
5
|
+
export const AdminPermissionPolicySchema = IamPermissionSchema.pick({
|
|
6
|
+
id: true,
|
|
7
|
+
code: true,
|
|
8
|
+
name: true,
|
|
9
|
+
sensitiveOperations: true,
|
|
10
|
+
})
|
|
6
11
|
export const AdminPermissionPolicyInputSchema = AdminPermissionPolicySchema.pick({ id: true, sensitiveOperations: true })
|
|
7
12
|
export type AdminPermissionPolicy = z.infer<typeof AdminPermissionPolicySchema>
|
|
8
13
|
|
|
9
|
-
export const BusinessSettingsSchema = z.object({
|
|
14
|
+
export const BusinessSettingsSchema = z.object({
|
|
15
|
+
notice: z.string().trim().max(200),
|
|
16
|
+
}).strict()
|
|
10
17
|
|
|
11
|
-
export const AdminRoleCodeSchema = z.enum([
|
|
18
|
+
export const AdminRoleCodeSchema = z.enum([
|
|
19
|
+
'admin',
|
|
20
|
+
'audit_admin',
|
|
21
|
+
'config_admin',
|
|
22
|
+
'business_operator',
|
|
23
|
+
'user',
|
|
24
|
+
])
|
|
12
25
|
export const AdminPageInputSchema = z.object({
|
|
13
26
|
page: z.number().int().min(1).default(1),
|
|
14
27
|
pageSize: z.number().int().min(1).max(100).default(20),
|
|
@@ -49,10 +62,16 @@ export const AdminCreateUserSchema = z.object({
|
|
|
49
62
|
accountStatus: z.enum(['active', 'dormant', 'closed']).default('active'),
|
|
50
63
|
accountExpiresAt: z.string().datetime().nullable().optional(),
|
|
51
64
|
}).strict()
|
|
52
|
-
export const AdminInviteUserSchema = AdminCreateUserSchema.omit({ password: true }).extend({
|
|
65
|
+
export const AdminInviteUserSchema = AdminCreateUserSchema.omit({ password: true }).extend({
|
|
66
|
+
phone: z.string().trim().min(1).max(32).optional(),
|
|
67
|
+
accountStatus: z.literal('active').default('active'),
|
|
68
|
+
})
|
|
53
69
|
export const AdminUpdateUserSchema = z.object({
|
|
54
70
|
id: z.string().min(1),
|
|
55
|
-
email: z.union([
|
|
71
|
+
email: z.union([
|
|
72
|
+
z.email(),
|
|
73
|
+
z.literal(''),
|
|
74
|
+
]).optional(),
|
|
56
75
|
displayName: z.string().max(100).optional(),
|
|
57
76
|
accountType: z.enum(['permanent', 'temporary']).optional(),
|
|
58
77
|
accountStatus: z.enum(['active', 'dormant', 'closed']).optional(),
|
|
@@ -73,7 +92,14 @@ export const AdminAuditInputSchema = AdminPageInputSchema.extend({
|
|
|
73
92
|
scopeId: z.string().max(256).optional(),
|
|
74
93
|
requestId: z.string().max(256).optional(),
|
|
75
94
|
traceId: z.string().max(256).optional(),
|
|
76
|
-
sortBy: z.enum([
|
|
95
|
+
sortBy: z.enum([
|
|
96
|
+
'createdAt',
|
|
97
|
+
'action',
|
|
98
|
+
'resource',
|
|
99
|
+
'category',
|
|
100
|
+
'outcome',
|
|
101
|
+
'severity',
|
|
102
|
+
]).optional(),
|
|
77
103
|
sortDirection: z.enum(['asc', 'desc']).optional(),
|
|
78
104
|
userId: z.string().max(100).optional(),
|
|
79
105
|
action: z.string().max(50).optional(),
|
|
@@ -88,7 +114,12 @@ export const AdminAuditLogSchema = z.object({
|
|
|
88
114
|
scopeId: z.string().nullish(),
|
|
89
115
|
requestId: z.string().nullish(),
|
|
90
116
|
traceId: z.string().nullish(),
|
|
91
|
-
actorSnapshot: z.object({
|
|
117
|
+
actorSnapshot: z.object({
|
|
118
|
+
id: z.string(),
|
|
119
|
+
username: z.string().optional(),
|
|
120
|
+
displayName: z.string().optional(),
|
|
121
|
+
roles: z.array(z.string()).optional(),
|
|
122
|
+
}).nullish(),
|
|
92
123
|
id: z.string(),
|
|
93
124
|
userId: z.string().nullable(),
|
|
94
125
|
username: z.string().nullable(),
|
|
@@ -100,10 +131,20 @@ export const AdminAuditLogSchema = z.object({
|
|
|
100
131
|
userAgent: z.string().nullable(),
|
|
101
132
|
createdAt: z.string().datetime(),
|
|
102
133
|
})
|
|
103
|
-
export const AuditBackupReceiptSchema = z.object({
|
|
134
|
+
export const AuditBackupReceiptSchema = z.object({
|
|
135
|
+
id: z.string(),
|
|
136
|
+
key: z.string(),
|
|
137
|
+
recordCount: z.number(),
|
|
138
|
+
sha256: z.string(),
|
|
139
|
+
createdAt: z.string().datetime(),
|
|
140
|
+
cutoff: z.string().datetime(),
|
|
141
|
+
})
|
|
104
142
|
export const AdminAuditSchema = z.object({
|
|
105
143
|
backupEnabled: z.boolean(),
|
|
106
|
-
stats: z.array(z.object({
|
|
144
|
+
stats: z.array(z.object({
|
|
145
|
+
action: z.string(),
|
|
146
|
+
count: z.number(),
|
|
147
|
+
})),
|
|
107
148
|
items: z.array(AdminAuditLogSchema),
|
|
108
149
|
total: z.number(),
|
|
109
150
|
page: z.number(),
|
|
@@ -111,7 +152,10 @@ export const AdminAuditSchema = z.object({
|
|
|
111
152
|
})
|
|
112
153
|
export const ProfileUpdateSchema = z.object({
|
|
113
154
|
displayName: z.string().max(100).optional(),
|
|
114
|
-
email: z.union([
|
|
155
|
+
email: z.union([
|
|
156
|
+
z.email(),
|
|
157
|
+
z.literal(''),
|
|
158
|
+
]).optional(),
|
|
115
159
|
locale: z.enum([{{#each fullstack.i18n.locales}}'{{this}}'{{#unless @last}}, {{/unless}}{{/each}}]).optional(),
|
|
116
160
|
theme: z.string().min(1).max(40).regex(/^[\w-]+$/).optional(),
|
|
117
161
|
}).strict()
|
|
@@ -120,7 +164,9 @@ export const AvatarUploadSchema = z.object({
|
|
|
120
164
|
contentType: z.enum(['image/png', 'image/jpeg', 'image/webp']),
|
|
121
165
|
base64: z.string().min(1).max(699052).regex(/^[A-Z\d+/]+={0,2}$/i),
|
|
122
166
|
}).strict()
|
|
123
|
-
export const AvatarOutputSchema = z.object({
|
|
167
|
+
export const AvatarOutputSchema = z.object({
|
|
168
|
+
avatarUrl: z.string().describe('Generated relative asset path'),
|
|
169
|
+
})
|
|
124
170
|
|
|
125
171
|
export type AdminUser = z.infer<typeof AdminUserSchema>
|
|
126
172
|
export type AdminRole = z.infer<typeof AdminRoleSchema>
|
|
@@ -3,7 +3,9 @@ import { defineConfig } from 'tsup'
|
|
|
3
3
|
export default defineConfig({
|
|
4
4
|
entry: ['src/index.ts'],
|
|
5
5
|
format: ['esm'],
|
|
6
|
-
dts: {
|
|
6
|
+
dts: {
|
|
7
|
+
compilerOptions: { ignoreDeprecations: '6.0' },
|
|
8
|
+
},
|
|
7
9
|
sourcemap: true,
|
|
8
10
|
clean: true,
|
|
9
11
|
})
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import type { CoreConfig } from '@h-ai/core'
|
|
2
2
|
import type { {{#if features.feedback}}CreateServContext, {{/if}}ServHttpApp } from '@h-ai/serv'
|
|
3
3
|
import process from 'node:process'
|
|
4
|
-
{{#if features.feedback}}
|
|
5
|
-
|
|
4
|
+
{{#if features.feedback}}
|
|
5
|
+
import { resolveFeedbackBudget } from '@h-ai/api-contract/feedback'
|
|
6
|
+
{{/if}}
|
|
7
|
+
import { core } from '@h-ai/core'
|
|
6
8
|
import { createReldbTransportKeyStore, crypto, parseTransportServerKeyRing } from '@h-ai/crypto'
|
|
7
9
|
{{#if features.feedback}}
|
|
8
10
|
import { FeedbackConfigSchema } from '@h-ai/feedback'
|
|
@@ -25,19 +27,29 @@ export function createServerApp(): ServHttpApp {
|
|
|
25
27
|
const ring = parseTransportServerKeyRing(process.env.HAI_CRYPTO_TRANSPORT_SERVERKEYRING)
|
|
26
28
|
if (!ring.success)
|
|
27
29
|
throw new Error(`HAI_CRYPTO_TRANSPORT_SERVERKEYRING: ${ring.error.message} (${ring.error.code})`)
|
|
28
|
-
{{#if features.feedback}}
|
|
30
|
+
{{#if features.feedback}}
|
|
31
|
+
// 可选反馈故障重试期间仍使用配置预算,避免缓存默认容量。
|
|
29
32
|
const feedbackSettings = FeedbackConfigSchema.safeParse(core.config.get('feedback'))
|
|
30
|
-
const feedbackBudget = resolveFeedbackBudget(feedbackSettings.success
|
|
31
|
-
|
|
33
|
+
const feedbackBudget = resolveFeedbackBudget(feedbackSettings.success
|
|
34
|
+
? feedbackSettings.data
|
|
35
|
+
: {})
|
|
36
|
+
{{/if}}
|
|
37
|
+
const security = serv.createRuntimeSecurityPolicy({
|
|
32
38
|
environment: coreConfig.env,
|
|
33
39
|
corsOrigin: servConfig.cors.origin,
|
|
34
40
|
nativeOrigins: servConfig.cors.nativeOrigins,
|
|
35
41
|
})
|
|
36
42
|
const publicPaths = [
|
|
37
43
|
`${servConfig.http.apiPrefix}/assets`,
|
|
38
|
-
...(servConfig.http.health
|
|
39
|
-
|
|
40
|
-
|
|
44
|
+
...(servConfig.http.health
|
|
45
|
+
? [servConfig.http.health.path, servConfig.http.health.readyPath].filter((path): path is `/${string}` => Boolean(path))
|
|
46
|
+
: []),
|
|
47
|
+
...(servConfig.http.openapi && security.exposeApiDocs
|
|
48
|
+
? [servConfig.http.openapi.path]
|
|
49
|
+
: []),
|
|
50
|
+
...(servConfig.http.docs && security.exposeApiDocs
|
|
51
|
+
? [servConfig.http.docs.path, `${servConfig.http.docs.path.slice(0, servConfig.http.docs.path.lastIndexOf('/') + 1)}_hai/scalar.js`]
|
|
52
|
+
: []),
|
|
41
53
|
]
|
|
42
54
|
{{#if features.feedback}}
|
|
43
55
|
// 普通业务日志与反馈使用同一可信应用范围;身份仍由 IAM 验证。
|
|
@@ -67,9 +79,11 @@ export function createServerApp(): ServHttpApp {
|
|
|
67
79
|
{{/if}}
|
|
68
80
|
refreshCookie: {
|
|
69
81
|
secure: security.secureRefreshCookie,
|
|
70
|
-
nativeTokenTransport: {
|
|
71
|
-
|
|
72
|
-
|
|
82
|
+
nativeTokenTransport: {
|
|
83
|
+
isRequest: request => request.headers.get('x-hai-token-transport') === 'body'
|
|
84
|
+
&& ['{{projectName}}-app', '{{projectName}}-desktop'].includes(request.headers.get('x-request-client') ?? '')
|
|
85
|
+
&& security.isNativeOrigin(request.headers.get('origin') ?? ''),
|
|
86
|
+
},
|
|
73
87
|
},
|
|
74
88
|
// 管理业务默认强制双向加密;各节点复用受管服务端密钥环与共享 reldb 客户端 registry。
|
|
75
89
|
transport: {
|
|
@@ -83,7 +97,12 @@ export function createServerApp(): ServHttpApp {
|
|
|
83
97
|
...servConfig.transport.routePlaintextBytes,
|
|
84
98
|
},
|
|
85
99
|
{{/if}}
|
|
86
|
-
excludePaths: [
|
|
100
|
+
excludePaths: [
|
|
101
|
+
...new Set([
|
|
102
|
+
...publicPaths,
|
|
103
|
+
...servConfig.transport.excludePaths,
|
|
104
|
+
]),
|
|
105
|
+
],
|
|
87
106
|
crypto,
|
|
88
107
|
serverKeyRing: ring.data,
|
|
89
108
|
keyStore: createReldbTransportKeyStore({ reldb, ttlSeconds: 3600 }),
|
|
@@ -93,23 +112,39 @@ export function createServerApp(): ServHttpApp {
|
|
|
93
112
|
openapi: security.exposeApiDocs ? servConfig.http.openapi : false,
|
|
94
113
|
docs: security.exposeApiDocs ? servConfig.http.docs : false,
|
|
95
114
|
},
|
|
96
|
-
middlewares: [
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
115
|
+
middlewares: [
|
|
116
|
+
{
|
|
117
|
+
middleware: serv.cors({
|
|
118
|
+
origin: security.allowOrigin,
|
|
119
|
+
credentials: servConfig.cors.credentials,
|
|
120
|
+
allowedHeaders: [
|
|
121
|
+
...new Set([
|
|
122
|
+
...servConfig.cors.allowedHeaders,
|
|
123
|
+
'X-Client-Id',
|
|
124
|
+
'X-Encrypted',
|
|
125
|
+
'X-Hai-Transport',
|
|
126
|
+
'X-Request-Client',
|
|
127
|
+
'X-HAI-Token-Transport',
|
|
128
|
+
'X-Hai-Reauthentication',
|
|
129
|
+
'X-Hai-Terminal',
|
|
130
|
+
]),
|
|
131
|
+
],
|
|
132
|
+
exposedHeaders: [
|
|
133
|
+
...new Set([...servConfig.cors.exposedHeaders, 'X-Encrypted', 'X-Hai-Transport']),
|
|
134
|
+
],
|
|
135
|
+
}),
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
path: `${servConfig.http.apiPrefix}/assets/*`,
|
|
139
|
+
middleware: serv.storageAssets({
|
|
140
|
+
storage,
|
|
141
|
+
pathPrefix: `${servConfig.http.apiPrefix}/assets/`,
|
|
142
|
+
keyPattern: /^avatars\/[\w-]+\/[\w-]+\.(png|jpg|webp)$/,
|
|
143
|
+
allowedContentTypes: ['image/png', 'image/jpeg', 'image/webp'],
|
|
144
|
+
cacheControl: 'public, max-age=31536000, immutable',
|
|
145
|
+
crossOriginResourcePolicy: 'cross-origin',
|
|
146
|
+
}),
|
|
147
|
+
},
|
|
148
|
+
],
|
|
114
149
|
})
|
|
115
150
|
}
|
package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/admin-service.ts.hbs
CHANGED
|
@@ -11,8 +11,18 @@ import { storage } from '@h-ai/storage'
|
|
|
11
11
|
import { SUPPORTED_LOCALES } from '{{fullstack.contractPackageName}}'
|
|
12
12
|
import { serverM } from './messages.js'
|
|
13
13
|
|
|
14
|
-
const ROLE_CODES = new Set<AdminRoleCode>([
|
|
15
|
-
|
|
14
|
+
const ROLE_CODES = new Set<AdminRoleCode>([
|
|
15
|
+
'admin',
|
|
16
|
+
'audit_admin',
|
|
17
|
+
'config_admin',
|
|
18
|
+
'business_operator',
|
|
19
|
+
'user',
|
|
20
|
+
])
|
|
21
|
+
const SUPPORTED_THEMES = [
|
|
22
|
+
{{#each fullstack.theme.themes}}
|
|
23
|
+
'{{this}}',
|
|
24
|
+
{{/each}}
|
|
25
|
+
] as readonly string[]
|
|
16
26
|
let adminMutationActive = false
|
|
17
27
|
|
|
18
28
|
/** 管理写操作串行化,锁内重新鉴权,防止两个管理员同时互相删除或降级。 */
|
|
@@ -42,7 +52,10 @@ export async function withAdminMutation<T>(context: ServContext, operation: stri
|
|
|
42
52
|
void cache.lock.extend(key, 300, owner).then((renewed) => {
|
|
43
53
|
if (!renewed.success || !renewed.data) {
|
|
44
54
|
leaseLost = true
|
|
45
|
-
core.logger.error('Admin mutation lease renewal failed', {
|
|
55
|
+
core.logger.error('Admin mutation lease renewal failed', {
|
|
56
|
+
owner,
|
|
57
|
+
error: renewed.success ? 'lease_not_owned' : renewed.error.code,
|
|
58
|
+
})
|
|
46
59
|
}
|
|
47
60
|
}).catch(() => {
|
|
48
61
|
leaseLost = true
|
|
@@ -55,7 +68,9 @@ export async function withAdminMutation<T>(context: ServContext, operation: stri
|
|
|
55
68
|
const actor = await getAdminUser(context.session?.userId ?? '')
|
|
56
69
|
if (!actor.success)
|
|
57
70
|
return actor
|
|
58
|
-
const permitted = await iam.authz.checkPermission(actor.data.id, operation.startsWith('iam.roles.')
|
|
71
|
+
const permitted = await iam.authz.checkPermission(actor.data.id, operation.startsWith('iam.roles.')
|
|
72
|
+
? 'app.roles.manage'
|
|
73
|
+
: 'app.users.manage')
|
|
59
74
|
if (!permitted.success)
|
|
60
75
|
return permitted
|
|
61
76
|
if (actor.data.accountStatus !== 'active' || (actor.data.accountType === 'temporary' && (!actor.data.accountExpiresAt || new Date(actor.data.accountExpiresAt).getTime() <= Date.now())) || !permitted.data)
|
|
@@ -73,8 +88,12 @@ export async function withAdminMutation<T>(context: ServContext, operation: stri
|
|
|
73
88
|
try {
|
|
74
89
|
if (acquired) {
|
|
75
90
|
const released = await cache.lock.release(key, owner)
|
|
76
|
-
if (!released.success || !released.data)
|
|
77
|
-
core.logger.error('Failed to release admin mutation lock', {
|
|
91
|
+
if (!released.success || !released.data) {
|
|
92
|
+
core.logger.error('Failed to release admin mutation lock', {
|
|
93
|
+
owner,
|
|
94
|
+
error: released.success ? 'lease_not_owned' : released.error.code,
|
|
95
|
+
})
|
|
96
|
+
}
|
|
78
97
|
}
|
|
79
98
|
}
|
|
80
99
|
finally {
|
|
@@ -123,7 +142,12 @@ export async function createAdminUser(input: AdminCreateUser, context: ServConte
|
|
|
123
142
|
return role
|
|
124
143
|
if (!role.data)
|
|
125
144
|
return err(HaiCommonError.NOT_FOUND, serverM('notFound', { locale: context.locale }))
|
|
126
|
-
const registered = await iam.user.createUser({
|
|
145
|
+
const registered = await iam.user.createUser({
|
|
146
|
+
...input,
|
|
147
|
+
accountExpiresAt: input.accountExpiresAt
|
|
148
|
+
? new Date(input.accountExpiresAt)
|
|
149
|
+
: undefined,
|
|
150
|
+
})
|
|
127
151
|
if (!registered.success)
|
|
128
152
|
return registered
|
|
129
153
|
const userId = registered.data.user.id
|
|
@@ -139,9 +163,17 @@ export async function createAdminUser(input: AdminCreateUser, context: ServConte
|
|
|
139
163
|
/** 安全通知发送后发生未知结果时保留账号,不自动重试或删除已发出的邀请。 */
|
|
140
164
|
export async function inviteAdminUser(input: AdminInviteUser, context: ServContext): Promise<HaiResult<AdminUser>> {
|
|
141
165
|
const role = await iam.authz.getRoleByCode(input.role)
|
|
142
|
-
if (!role.success || !role.data)
|
|
143
|
-
return role.success
|
|
144
|
-
|
|
166
|
+
if (!role.success || !role.data) {
|
|
167
|
+
return role.success
|
|
168
|
+
? err(HaiCommonError.NOT_FOUND, serverM('notFound', { locale: context.locale }))
|
|
169
|
+
: role
|
|
170
|
+
}
|
|
171
|
+
const created = await iam.user.inviteUser({
|
|
172
|
+
...input,
|
|
173
|
+
accountExpiresAt: input.accountExpiresAt
|
|
174
|
+
? new Date(input.accountExpiresAt)
|
|
175
|
+
: undefined,
|
|
176
|
+
})
|
|
145
177
|
if (!created.success)
|
|
146
178
|
return created
|
|
147
179
|
const assigned = await iam.authz.syncRoles(created.data.user.id, [role.data.id])
|
|
@@ -165,7 +197,9 @@ export async function updateAdminUser(input: AdminUpdateUser, context: ServConte
|
|
|
165
197
|
return existing
|
|
166
198
|
if (!existing.data)
|
|
167
199
|
return err(HaiCommonError.NOT_FOUND, serverM('notFound', { locale: context.locale }))
|
|
168
|
-
const role = input.role
|
|
200
|
+
const role = input.role
|
|
201
|
+
? await iam.authz.getRoleByCode(input.role)
|
|
202
|
+
: undefined
|
|
169
203
|
if (role && !role.success)
|
|
170
204
|
return role
|
|
171
205
|
if (role?.success && !role.data)
|
|
@@ -179,7 +213,14 @@ export async function updateAdminUser(input: AdminUpdateUser, context: ServConte
|
|
|
179
213
|
if (!assigned.success)
|
|
180
214
|
return assigned
|
|
181
215
|
}
|
|
182
|
-
const updated = await iam.user.updateUser(id, {
|
|
216
|
+
const updated = await iam.user.updateUser(id, {
|
|
217
|
+
...profile,
|
|
218
|
+
accountExpiresAt: profile.accountExpiresAt === undefined
|
|
219
|
+
? undefined
|
|
220
|
+
: profile.accountExpiresAt
|
|
221
|
+
? new Date(profile.accountExpiresAt)
|
|
222
|
+
: null,
|
|
223
|
+
})
|
|
183
224
|
if (!updated.success) {
|
|
184
225
|
// 角色 ID 可精确还原,避免 undefined 资料字段被 updateUser 忽略后无法补偿。
|
|
185
226
|
if (role?.success && role.data) {
|
|
@@ -189,7 +230,10 @@ export async function updateAdminUser(input: AdminUpdateUser, context: ServConte
|
|
|
189
230
|
}
|
|
190
231
|
return updated
|
|
191
232
|
}
|
|
192
|
-
await recordAudit(context, 'update', 'users', id, {
|
|
233
|
+
await recordAudit(context, 'update', 'users', id, {
|
|
234
|
+
fields: Object.keys(profile),
|
|
235
|
+
role: input.role,
|
|
236
|
+
})
|
|
193
237
|
return getAdminUser(id)
|
|
194
238
|
}
|
|
195
239
|
|
|
@@ -201,13 +245,15 @@ export async function getAdminRoles(): Promise<HaiResult<{ items: AdminRole[] }>
|
|
|
201
245
|
const counts = await iam.authz.getRoleUserCounts(items.map(role => role.id))
|
|
202
246
|
if (!counts.success)
|
|
203
247
|
return counts
|
|
204
|
-
return ok({
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
248
|
+
return ok({
|
|
249
|
+
items: items.map(role => ({
|
|
250
|
+
id: role.id,
|
|
251
|
+
code: role.code as AdminRoleCode,
|
|
252
|
+
name: role.name,
|
|
253
|
+
description: role.description,
|
|
254
|
+
userCount: counts.data.get(role.id) ?? 0,
|
|
255
|
+
})),
|
|
256
|
+
})
|
|
211
257
|
}
|
|
212
258
|
|
|
213
259
|
export async function updateProfile(input: ProfileUpdate, context: ServContext): Promise<HaiResult<AdminUser>> {
|
|
@@ -225,7 +271,9 @@ export async function updateProfile(input: ProfileUpdate, context: ServContext):
|
|
|
225
271
|
const updated = await iam.user.updateCurrentUser(token, { displayName: input.displayName, email: input.email, metadata })
|
|
226
272
|
if (!updated.success)
|
|
227
273
|
return updated
|
|
228
|
-
await recordAudit(context, 'update', 'profile', current.data.id, {
|
|
274
|
+
await recordAudit(context, 'update', 'profile', current.data.id, {
|
|
275
|
+
fields: Object.keys(input),
|
|
276
|
+
})
|
|
229
277
|
return getAdminUser(current.data.id)
|
|
230
278
|
}
|
|
231
279
|
|
package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/business-settings.ts.hbs
CHANGED
|
@@ -5,15 +5,22 @@ import { err, HaiCommonError, ok } from '@h-ai/core'
|
|
|
5
5
|
import { reldb } from '@h-ai/reldb'
|
|
6
6
|
import { z } from 'zod'
|
|
7
7
|
|
|
8
|
-
export const BusinessSettingsSchema = z.object({
|
|
8
|
+
export const BusinessSettingsSchema = z.object({
|
|
9
|
+
notice: z.string().trim().max(200),
|
|
10
|
+
}).strict()
|
|
9
11
|
|
|
10
12
|
export function initBusinessSettings(): Promise<HaiResult<void>> {
|
|
11
|
-
return reldb.ddl.createTable('app_business_settings', {
|
|
13
|
+
return reldb.ddl.createTable('app_business_settings', {
|
|
14
|
+
id: { type: 'TEXT', primaryKey: true },
|
|
15
|
+
notice: { type: 'TEXT', notNull: true },
|
|
16
|
+
})
|
|
12
17
|
}
|
|
13
18
|
|
|
14
19
|
export async function readBusinessSettings(): Promise<HaiResult<{ notice: string }>> {
|
|
15
20
|
const row = await reldb.sql.get<{ notice: string }>('SELECT notice FROM app_business_settings WHERE id = ?', ['public'])
|
|
16
|
-
return row.success
|
|
21
|
+
return row.success
|
|
22
|
+
? ok({ notice: row.data?.notice ?? '' })
|
|
23
|
+
: row
|
|
17
24
|
}
|
|
18
25
|
|
|
19
26
|
export async function saveBusinessSettings(input: { notice: string }): Promise<HaiResult<{ notice: string }>> {
|
|
@@ -33,7 +40,14 @@ export async function saveBusinessSettings(input: { notice: string }): Promise<H
|
|
|
33
40
|
await tx.rollback()
|
|
34
41
|
return saved
|
|
35
42
|
}
|
|
36
|
-
const logged = await audit.capture({
|
|
43
|
+
const logged = await audit.capture({
|
|
44
|
+
action: 'business.configuration.update',
|
|
45
|
+
resource: 'business-settings',
|
|
46
|
+
category: 'business',
|
|
47
|
+
details: {
|
|
48
|
+
fields: ['notice'],
|
|
49
|
+
},
|
|
50
|
+
}, { tx })
|
|
37
51
|
if (!logged.success) {
|
|
38
52
|
await tx.rollback()
|
|
39
53
|
return logged
|
|
@@ -41,7 +55,9 @@ export async function saveBusinessSettings(input: { notice: string }): Promise<H
|
|
|
41
55
|
const committed = await tx.commit()
|
|
42
56
|
if (!committed.success)
|
|
43
57
|
await tx.rollback()
|
|
44
|
-
return committed.success
|
|
58
|
+
return committed.success
|
|
59
|
+
? ok(parsed.data)
|
|
60
|
+
: committed
|
|
45
61
|
}
|
|
46
62
|
catch (error) {
|
|
47
63
|
await tx.rollback()
|
|
@@ -28,13 +28,27 @@ export function createAuthProcedures() {
|
|
|
28
28
|
export function createAdminProcedures() {
|
|
29
29
|
return serv.implement(adminContract).context<ServContext>().route('permissions.list').permission('app.roles.manage').handle(async ({ input }) => {
|
|
30
30
|
const result = await iam.authz.getAllPermissions(input)
|
|
31
|
-
return result.success
|
|
31
|
+
return result.success
|
|
32
|
+
? ok({
|
|
33
|
+
total: result.data.total,
|
|
34
|
+
items: result.data.items.map(item => ({
|
|
35
|
+
id: item.id,
|
|
36
|
+
code: item.code,
|
|
37
|
+
name: item.name,
|
|
38
|
+
sensitiveOperations: item.sensitiveOperations ?? [],
|
|
39
|
+
})),
|
|
40
|
+
})
|
|
41
|
+
: result
|
|
32
42
|
}).route('permissions.update').permission('app.roles.manage').handle(({ input, context }) => withAdminMutation(context, 'iam.permissions.sensitiveOperations', async () => {
|
|
33
43
|
const result = await iam.authz.setPermissionSensitiveOperations(input.id, input.sensitiveOperations)
|
|
34
|
-
return result.success
|
|
44
|
+
return result.success
|
|
45
|
+
? ok({ saved: true as const })
|
|
46
|
+
: result
|
|
35
47
|
})).route('configuration.get').permission('app.config.manage').handle(() => readBusinessSettings()).route('configuration.update').permission('app.config.manage').handle(async ({ input, context }) => {
|
|
36
48
|
const checked = await iam.auth.checkSensitiveOperation(context.accessToken ?? '', 'business.settings.update', context.request.headers.get('x-hai-reauthentication') ?? undefined)
|
|
37
|
-
return checked.success
|
|
49
|
+
return checked.success
|
|
50
|
+
? saveBusinessSettings(input)
|
|
51
|
+
: checked
|
|
38
52
|
}).route('users.list').permission('app.users.manage').handle(async ({ input }) => {
|
|
39
53
|
const users = await iam.user.listUsers(input)
|
|
40
54
|
if (!users.success)
|
|
@@ -42,7 +56,10 @@ export function createAdminProcedures() {
|
|
|
42
56
|
const roles = await iam.authz.getUserRolesForMany(users.data.items.map(user => user.id))
|
|
43
57
|
if (!roles.success)
|
|
44
58
|
return roles
|
|
45
|
-
return ok({
|
|
59
|
+
return ok({
|
|
60
|
+
...users.data,
|
|
61
|
+
items: users.data.items.map(user => toAdminUser(user, roles.data.get(user.id) ?? [])),
|
|
62
|
+
})
|
|
46
63
|
}).route('users.resetPassword').permission('app.users.manage').handle(({ input, context }) => withAdminMutation(context, 'iam.users.resetPassword', async () => {
|
|
47
64
|
const result = await iam.user.adminResetPassword(input.id, input.newPassword)
|
|
48
65
|
if (!result.success)
|
|
@@ -74,33 +91,68 @@ export function createAdminProcedures() {
|
|
|
74
91
|
const result = roles.data.items.find(item => item.id === input.id)
|
|
75
92
|
if (!result)
|
|
76
93
|
return err(HaiCommonError.NOT_FOUND, serverM('notFound', { locale: context.locale }))
|
|
77
|
-
await recordAudit(context, 'update', 'roles', input.id, {
|
|
94
|
+
await recordAudit(context, 'update', 'roles', input.id, {
|
|
95
|
+
fields: ['description'],
|
|
96
|
+
})
|
|
78
97
|
return ok(result)
|
|
79
98
|
})).route('audit.backup').permission('audit:backup').handle(async ({ input, context }) => {
|
|
80
99
|
const checked = await iam.auth.checkSensitiveOperation(context.accessToken ?? '', 'audit.backup', context.request.headers.get('x-hai-reauthentication') ?? undefined)
|
|
81
100
|
if (!checked.success)
|
|
82
101
|
return checked
|
|
83
102
|
const result = await audit.backup.run({ ...input, signal: context.request.signal })
|
|
84
|
-
return result.success
|
|
103
|
+
return result.success
|
|
104
|
+
? ok({
|
|
105
|
+
...result.data,
|
|
106
|
+
createdAt: result.data.createdAt.toISOString(),
|
|
107
|
+
cutoff: result.data.cutoff.toISOString(),
|
|
108
|
+
})
|
|
109
|
+
: result
|
|
85
110
|
}).route('audit.list').permission('audit:read').handle(async ({ input, context }) => {
|
|
86
111
|
if (input.startDate && input.endDate && input.startDate > input.endDate)
|
|
87
112
|
return err(HaiCommonError.VALIDATION_ERROR, serverM('invalidDateRange', { locale: context.locale }))
|
|
88
|
-
const result = await audit.list({
|
|
113
|
+
const result = await audit.list({
|
|
114
|
+
...input,
|
|
115
|
+
startDate: input.startDate
|
|
116
|
+
? new Date(input.startDate)
|
|
117
|
+
: undefined,
|
|
118
|
+
endDate: input.endDate
|
|
119
|
+
? new Date(input.endDate)
|
|
120
|
+
: undefined,
|
|
121
|
+
})
|
|
89
122
|
if (!result.success)
|
|
90
123
|
return result
|
|
91
|
-
const stats = await audit.getStats(7, {
|
|
124
|
+
const stats = await audit.getStats(7, {
|
|
125
|
+
category: input.category,
|
|
126
|
+
outcome: input.outcome,
|
|
127
|
+
severity: input.severity,
|
|
128
|
+
resource: input.resource,
|
|
129
|
+
scopeId: input.scopeId,
|
|
130
|
+
userId: input.userId,
|
|
131
|
+
})
|
|
92
132
|
if (!stats.success)
|
|
93
133
|
return stats
|
|
94
|
-
return ok({
|
|
134
|
+
return ok({
|
|
135
|
+
...result.data,
|
|
136
|
+
stats: stats.data,
|
|
137
|
+
backupEnabled: AuditInitConfigSchema.parse(core.config.get('audit') ?? {}).backup.enabled,
|
|
138
|
+
items: result.data.items.map(item => ({
|
|
139
|
+
...item,
|
|
140
|
+
createdAt: new Date(item.createdAt).toISOString(),
|
|
141
|
+
})),
|
|
142
|
+
})
|
|
95
143
|
}).build()
|
|
96
144
|
}
|
|
97
145
|
|
|
98
146
|
export function createProfileProcedures() {
|
|
99
147
|
return serv.implement(profileContract).context<ServContext>().route('update').auth().handle(async ({ input, context }) => {
|
|
100
148
|
const checked = await iam.auth.checkSensitiveOperation(context.accessToken ?? '', 'iam.profile.update', context.request.headers.get('x-hai-reauthentication') ?? undefined)
|
|
101
|
-
return checked.success
|
|
149
|
+
return checked.success
|
|
150
|
+
? updateProfile(input, context)
|
|
151
|
+
: checked
|
|
102
152
|
}).route('avatar').auth().handle(async ({ input, context }) => {
|
|
103
153
|
const checked = await iam.auth.checkSensitiveOperation(context.accessToken ?? '', 'iam.profile.avatar', context.request.headers.get('x-hai-reauthentication') ?? undefined)
|
|
104
|
-
return checked.success
|
|
154
|
+
return checked.success
|
|
155
|
+
? uploadAvatar(context.session.userId, input, context)
|
|
156
|
+
: checked
|
|
105
157
|
}).build()
|
|
106
158
|
}
|
|
@@ -22,7 +22,12 @@ export function createAppProcedures(options: CreateAppProceduresOptions) {
|
|
|
22
22
|
.context<ServContext>()
|
|
23
23
|
.route('info', async () => {
|
|
24
24
|
const settings = await readBusinessSettings()
|
|
25
|
-
return settings.success
|
|
25
|
+
return settings.success
|
|
26
|
+
? ok({
|
|
27
|
+
...createAppInfo(options),
|
|
28
|
+
notice: settings.data.notice,
|
|
29
|
+
})
|
|
30
|
+
: settings
|
|
26
31
|
})
|
|
27
32
|
.route('echo', ({ input, context }) => {
|
|
28
33
|
const data: AppEchoOutputData = {
|
package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/procedures/index.ts.hbs
CHANGED
|
@@ -17,7 +17,9 @@ export function createServerProcedures(options: CreateServerProceduresOptions) {
|
|
|
17
17
|
return {
|
|
18
18
|
// @hai-generator:procedures:entries
|
|
19
19
|
app: createAppProcedures(options.app),
|
|
20
|
-
iam: {
|
|
20
|
+
iam: {
|
|
21
|
+
auth: createAuthProcedures(),
|
|
22
|
+
},
|
|
21
23
|
admin: createAdminProcedures(),
|
|
22
24
|
profile: createProfileProcedures(),
|
|
23
25
|
{{#if features.feedback}}
|