cabloy 5.1.105 → 5.1.107

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.
Files changed (55) hide show
  1. package/.cabloy-version +1 -1
  2. package/CHANGELOG.md +18 -0
  3. package/cabloy-docs/backend/backend-resource-module-contract-chain.md +1 -0
  4. package/cabloy-docs/backend/controller-aop-guide.md +12 -0
  5. package/cabloy-docs/backend/controller-guide.md +45 -3
  6. package/cabloy-docs/backend/crud-workflow.md +14 -0
  7. package/cabloy-docs/backend/unit-testing.md +8 -2
  8. package/cabloy-docs/fullstack/openapi-to-sdk.md +1 -0
  9. package/cabloy-docs/fullstack/tutorial-2-first-crud.md +3 -0
  10. package/package.json +1 -1
  11. package/vona/packages-cli/cli/package.json +1 -1
  12. package/vona/packages-cli/cli-set-api/cli/templates/tools/crud/boilerplate/src/bean/ssrMenu.{{resourceName}}.ts_ +1 -0
  13. package/vona/packages-cli/cli-set-api/cli/templates/tools/crudBasic/boilerplate/src/controller/{{resourceName}}.ts_ +13 -4
  14. package/vona/packages-cli/cli-set-api/cli/templates/tools/crudBasic/boilerplate/test/{{resourceName}}.test.ts_ +22 -3
  15. package/vona/packages-cli/cli-set-api/cli/templates/tools/crudStart/boilerplate/src/controller/{{resourceName}}.ts_ +13 -4
  16. package/vona/packages-cli/cli-set-api/cli/templates/tools/crudStart/boilerplate/test/{{resourceName}}.test.ts_ +22 -3
  17. package/vona/packages-cli/cli-set-api/package.json +1 -1
  18. package/vona/packages-vona/vona/package.json +1 -1
  19. package/vona/patches/{zova-core@5.1.77.patch → zova-core@5.1.78.patch} +2 -2
  20. package/vona/pnpm-lock.yaml +37 -37
  21. package/vona/pnpm-workspace.yaml +1 -1
  22. package/vona/src/suite/a-home/modules/home-user/src/.metadata/index.ts +2 -2
  23. package/vona/src/suite/a-home/modules/home-user/src/bean/meta.version.ts +10 -8
  24. package/vona/src/suite/a-home/modules/home-user/src/config/config.ts +6 -0
  25. package/vona/src/suite/a-home/modules/home-user/src/config/locale/en-us.ts +2 -0
  26. package/vona/src/suite/a-home/modules/home-user/src/config/locale/zh-cn.ts +2 -0
  27. package/vona/src/suite/a-home/modules/home-user/src/config/roles.ts +24 -0
  28. package/vona/src/suite/a-home/modules/home-user/src/entity/role.ts +7 -0
  29. package/vona/src/suite/a-home/modules/home-user/test/role.test.ts +45 -0
  30. package/vona/src/suite/a-training/modules/training-record/src/controller/record.ts +13 -4
  31. package/vona/src/suite/a-training/modules/training-record/test/record.test.ts +25 -2
  32. package/vona/src/suite/a-training/modules/training-student/src/controller/student.ts +18 -6
  33. package/vona/src/suite/a-training/modules/training-student/test/student.test.ts +33 -5
  34. package/vona/src/suite-vendor/a-cabloy/modules/a-ssr/cli/ssrMenuAdmin/boilerplate/{{sceneName}}.{{beanName}}.ts_ +1 -0
  35. package/vona/src/suite-vendor/a-cabloy/modules/a-ssr/package.json +1 -1
  36. package/vona/src/suite-vendor/a-cabloy/modules/a-ssr/src/service/ssrHandler.ts +1 -1
  37. package/vona/src/suite-vendor/a-cabloy/package.json +1 -1
  38. package/vona/src/suite-vendor/a-vona/modules/a-user/package.json +1 -1
  39. package/vona/src/suite-vendor/a-vona/modules/a-user/src/types/role.ts +3 -0
  40. package/vona/src/suite-vendor/a-vona/package.json +1 -1
  41. package/zova/packages-cli/cli/package.json +2 -2
  42. package/zova/packages-cli/cli-set-front/package.json +1 -1
  43. package/zova/packages-cli/cli-set-front/src/lib/bean/cli.openapi.generate.ts +4 -0
  44. package/zova/packages-zova/zova/package.json +2 -2
  45. package/zova/pnpm-lock.yaml +9 -9
  46. package/zova/src/suite/a-home/modules/home-api/mock/passport.fake.ts +50 -1
  47. package/zova/src/suite/a-home/modules/home-api/src/api/openapi/types.ts +15 -7
  48. package/zova/src/suite/a-home/modules/home-base/src/.metadata/index.ts +10 -0
  49. package/zova/src/suite/a-home/modules/home-base/src/page/errorAccessDenied/controller.tsx +1 -1
  50. package/zova/src/suite/a-training/modules/training-student/src/api/openapi/types.ts +26 -17
  51. package/zova/src/suite/cabloy-basic/modules/basic-file/src/api/openapi/types.ts +26 -17
  52. package/zova/src/suite/cabloy-basic/modules/basic-image/src/api/openapi/types.ts +26 -17
  53. package/zova/src/suite-vendor/a-zova/modules/a-ssrserver/package.json +1 -1
  54. package/zova/src/suite-vendor/a-zova/modules/a-ssrserver/src/service/ssrHandler.ts +1 -1
  55. package/zova/src/suite-vendor/a-zova/package.json +2 -2
package/.cabloy-version CHANGED
@@ -1 +1 @@
1
- 5.1.105
1
+ 5.1.107
package/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # Changelog
2
2
 
3
+ ## 5.1.107
4
+
5
+ ### Features
6
+
7
+ - Localize persisted home roles.
8
+ - Define response contracts for resource mutations, including null responses.
9
+ - Secure generated CRUD actions.
10
+
11
+ ### Improvements
12
+
13
+ - Clarify built-in role configuration.
14
+
15
+ ## 5.1.106
16
+
17
+ ### Improvements
18
+
19
+ - Refresh the Zova lockfile and update the Vona Zova Core patch.
20
+
3
21
  ## 5.1.105
4
22
 
5
23
  ### Features
@@ -143,6 +143,7 @@ Representative source facts from this file:
143
143
  - the class is decorated with `@Controller<IControllerOptionsStudent>('student')`
144
144
  - the class is also decorated with `@Resource()`
145
145
  - CRUD actions are declared with `@Web.post()`, `@Web.get()`, `@Web.get(':id')`, `@Web.patch(':id')`, and `@Web.delete(':id')`
146
+ - the standard CRUD actions and the `summary` / `deleteForce` management actions explicitly use `@Passport.systemAdmin()`
146
147
  - request shapes use `@Arg.body(...)`, `@Arg.param(...)`, and `@Arg.filter(...)`
147
148
  - actions delegate to `this.scope.service.student`
148
149
 
@@ -99,6 +99,18 @@ Typical jobs include:
99
99
 
100
100
  These shorthands still map back to the generic aspect model.
101
101
 
102
+ ### Passport inheritance and action policy
103
+
104
+ The global Passport guard is the baseline for controller actions: without a local Passport decorator, an action requires an authenticated and activated user. It is not public.
105
+
106
+ Use a local Passport or domain guard when an action needs a policy beyond that baseline:
107
+
108
+ - use `@Passport.public()` only when anonymous access is intentional
109
+ - use `@Passport.systemAdmin()`, `@Passport.roleName(...)`, or a domain-specific guard when access needs additional restriction
110
+ - leave an action without a local Passport guard when authenticated-and-activated access is intentionally sufficient
111
+
112
+ The CRUD generator applies `@Passport.systemAdmin()` to each generated CRUD action rather than to the controller class. This keeps the administrative default explicit while allowing a future custom action to deliberately inherit the baseline or define its own policy.
113
+
102
114
  For the underlying auth, passport, and user-access model, see [Auth Guide](/backend/auth-guide) and [User Access Guide](/backend/user-access-guide).
103
115
 
104
116
  ## Interceptor
@@ -148,11 +148,11 @@ Representative pattern:
148
148
 
149
149
  ```typescript
150
150
  @Web.patch('updateUser/:id')
151
- updateUser(
151
+ async updateUser(
152
152
  @Arg.param('id') id: TableIdentity,
153
153
  @Arg.body(v.object(DtoUserUpdate)) user: DtoUserUpdate,
154
- ) {
155
- return this.scope.model.user.update(user, { where: { id } });
154
+ ): Promise<void> {
155
+ await this.scope.service.user.update(id, user);
156
156
  }
157
157
  ```
158
158
 
@@ -208,6 +208,48 @@ A practical rule is:
208
208
  - use return-type inference when the contract is obvious and simple
209
209
  - use explicit `@Api.body(...)` when the response shape needs more control or the inference boundary becomes unclear
210
210
 
211
+ ## Resource mutation response contract
212
+
213
+ Standard resource `update` and `delete` actions are command-style actions. Their public contract is successful completion, not an implicit persistence-model payload.
214
+
215
+ Representative pattern:
216
+
217
+ ```typescript
218
+ import { z } from 'zod';
219
+
220
+ @Web.patch(':id')
221
+ @Api.body(z.null())
222
+ @Passport.systemAdmin()
223
+ async update(
224
+ @Arg.param('id', v.tableIdentity()) id: TableIdentity,
225
+ @Arg.body() student: DtoStudentUpdate,
226
+ ): Promise<void> {
227
+ await this.scope.service.student.update(id, student);
228
+ }
229
+ ```
230
+
231
+ Use `await` without `return` at the controller boundary. A service or model may still return mutation data for internal orchestration, but that data is not automatically a stable public representation of the resource.
232
+
233
+ `Promise<void>` expresses the controller's command semantics, while `@Api.body(z.null())` explicitly supplies the response schema that runtime reflection cannot recover from `Promise<void>`. It makes the existing no-payload response visible to emitted OpenAPI and generated SDK consumers.
234
+
235
+ For a standard JSON action, Vona maps this no-result controller completion to its normal HTTP `200` success wrapper:
236
+
237
+ ```typescript
238
+ {
239
+ code: 0,
240
+ message: 'success',
241
+ data: null,
242
+ }
243
+ ```
244
+
245
+ This is Vona's default resource-mutation convention. It does not use HTTP `204 No Content`, so consumers can keep one uniform success-envelope protocol.
246
+
247
+ ### When a mutation should return data
248
+
249
+ Return data only when it is an intentional consumer-facing contract, such as an updated response DTO, a version token, or a job handle. Declare that shape explicitly with `@Api.body(...)`; add `@Core.serializer()` when the response depends on serializer transforms, exclusions, getters, or replacements. Do not forward raw ORM or service mutation results merely because the delegated method returns a value.
250
+
251
+ `create` and read actions commonly expose an identity or DTO and therefore use their own explicit response contracts. For generated resource defaults, see [CRUD Workflow](/backend/crud-workflow#generated-mutation-response-default). For action-level verification, see [Unit Testing](/backend/unit-testing#testing-controllers-through-actions). When a declared response shape changes, follow the [Backend OpenAPI to Frontend SDK](/fullstack/openapi-to-sdk) contract loop.
252
+
211
253
  ## Response wrapper behavior
212
254
 
213
255
  By default, Vona wraps the response body in a standard wrapper object.
@@ -43,6 +43,20 @@ The generator creates a connected set of files, typically including:
43
43
 
44
44
  This is exactly why this generator should be the default starting point. It gives a consistent starting shape across the backend thread.
45
45
 
46
+ ### Generated authorization default
47
+
48
+ The standard generated CRUD actions (`create`, `select`, `view`, `update`, and `delete`) each use `@Passport.systemAdmin()`. This is an explicit administrative whitelist for the generated Admin resource API, matching the generated SSR menu's `systemAdmin` visibility policy.
49
+
50
+ The guard is attached to individual actions rather than the controller class. When you add a custom action later, no local Passport guard intentionally means the global authenticated-and-activated baseline applies; it does not mean the action is public. Use `@Passport.public()` to intentionally allow anonymous access, or add a role/domain guard when an action needs stronger restriction.
51
+
52
+ SSR menu roles control navigation disclosure only. They never authorize a controller action, API, or resource; keep the action guard as the server-side authorization boundary. See [Controller AOP Guide](/backend/controller-aop-guide) and [Menu Guide](/backend/menu-guide).
53
+
54
+ ### Generated mutation response default
55
+
56
+ Generated resource `update` and `delete` actions are command-style actions. Their controllers use `Promise<void>`, await the service call without forwarding its result, and declare `@Api.body(z.null())` so OpenAPI and generated SDKs describe the normal HTTP `200` success wrapper accurately as `data: null`.
57
+
58
+ This controller-facing default does not require the service or model mutation method to return `void`. Those lower layers may retain mutation data for internal orchestration. See [Controller Guide](/backend/controller-guide#resource-mutation-response-contract) when a mutation deliberately needs a consumer-facing response payload.
59
+
46
60
  A practical generated-output checklist usually includes:
47
61
 
48
62
  - controller
@@ -121,13 +121,19 @@ A practical rule is:
121
121
  A representative contract-verification pattern is:
122
122
 
123
123
  ```typescript
124
- await app.bean.executor.performAction('patch', '/test/rest/product/:id', {
124
+ const updateRes = await app.bean.executor.performAction('patch', '/test/rest/product/:id', {
125
125
  params: { id: productId },
126
126
  body: dataUpdate,
127
127
  });
128
+ assert.equal(updateRes, null);
129
+
130
+ const deleteRes = await app.bean.executor.performAction('delete', '/test/rest/product/:id', {
131
+ params: { id: productId },
132
+ });
133
+ assert.equal(deleteRes, null);
128
134
  ```
129
135
 
130
- This is a good default because the same test can exercise params, body, route wiring, validation, and response behavior together.
136
+ For standard resource command mutations, these assertions verify the controller-facing no-payload runtime contract. Follow them with a query/read-back assertion to verify the persisted update or deletion. When the API is generated for frontend consumers, add a focused OpenAPI structural assertion that its wrapped response schema declares `data: null`; this verifies SDK-contract fidelity separately from responder behavior. The same test then exercises params, body, route wiring, validation, response behavior, and persistence effects together.
131
137
 
132
138
  ## Authentication simulation
133
139
 
@@ -68,6 +68,7 @@ For the deeper frontend perspective, see:
68
68
  If you want the shortest accurate mental model, use this forward chain:
69
69
 
70
70
  1. backend controller signatures define request and response entry surfaces
71
+ - command-style mutations that complete with Vona's wrapped `data: null` must declare `@Api.body(z.null())`; `Promise<void>` alone cannot preserve that generic type through runtime reflection
71
72
  2. backend DTO and entity fields shape named and shared contract structure
72
73
  3. validation rules and `v` helpers refine the machine-readable contract
73
74
  4. Vona emits OpenAPI output from those backend declarations
@@ -95,6 +95,9 @@ As you inspect the generated files, pay attention to the division of responsibil
95
95
 
96
96
  This is the backend contract thread that later tutorials will extend with `level`, `mobile`, render metadata, OpenAPI output, and row actions.
97
97
 
98
+ > [!NOTE]
99
+ > The generated Admin CRUD actions explicitly require the `systemAdmin` role. If this resource should serve another audience, adjust the relevant controller action guards as well as any SSR menu visibility; changing the menu alone does not authorize an API.
100
+
98
101
  ## Verification
99
102
 
100
103
  1. make sure the local dev workflow is running:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cabloy",
3
- "version": "5.1.105",
3
+ "version": "5.1.107",
4
4
  "gitHead": "2c5c19284bab738e492856189acb6fad74b8a7b7",
5
5
  "description": "A Node.js fullstack framework",
6
6
  "keywords": [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vona-cli",
3
- "version": "1.1.129",
3
+ "version": "1.1.130",
4
4
  "gitHead": "a79189b882c17af5911573896a781bbb0046d37d",
5
5
  "description": "vona cli",
6
6
  "keywords": [
@@ -20,6 +20,7 @@ export interface ISsrMenuOptions<%=argv.resourceNameCapitalize%> extends IDecora
20
20
  },
21
21
  },
22
22
  group: '<%=argv.ssrSiteGroupName%>',
23
+ roles: ['systemAdmin'],
23
24
  },
24
25
  },
25
26
  site: ['<%=argv.ssrSiteOnionName%>'],
@@ -4,6 +4,8 @@ import type { IDecoratorControllerOptions } from 'vona-module-a-web';
4
4
 
5
5
  import { BeanBase } from 'vona';
6
6
  import { Api, Resource, v } from 'vona-module-a-openapiutils';
7
+ import { z } from 'zod';
8
+ import { Passport } from 'vona-module-a-user';
7
9
  import { Arg, Controller, Web } from 'vona-module-a-web';
8
10
 
9
11
  import type { Model<%=argv.resourceNameCapitalize%> } from '../model/<%=argv.resourceName%>.ts';
@@ -21,29 +23,36 @@ export interface IControllerOptions<%=argv.resourceNameCapitalize%> extends IDec
21
23
  export class Controller<%=argv.resourceNameCapitalize%> extends BeanBase {
22
24
  @Web.post()
23
25
  @Api.body(v.tableIdentity())
26
+ @Passport.systemAdmin()
24
27
  async create(@Arg.body() <%=argv.resourceName%>: Dto<%=argv.resourceNameCapitalize%>Create): Promise<TableIdentity> {
25
28
  return (await this.scope.service.<%=argv.resourceName%>.create(<%=argv.resourceName%>)).id;
26
29
  }
27
30
 
28
31
  @Web.get()
29
32
  @Api.body(Dto<%=argv.resourceNameCapitalize%>SelectRes)
33
+ @Passport.systemAdmin()
30
34
  async select(@Arg.filter(Dto<%=argv.resourceNameCapitalize%>SelectReq) params: IQueryParams<Model<%=argv.resourceNameCapitalize%>>): Promise<Dto<%=argv.resourceNameCapitalize%>SelectRes> {
31
35
  return await this.scope.service.<%=argv.resourceName%>.select(params);
32
36
  }
33
37
 
34
38
  @Web.get(':id')
35
39
  @Api.body(v.optional(), v.object(Dto<%=argv.resourceNameCapitalize%>View))
40
+ @Passport.systemAdmin()
36
41
  async view(@Arg.param('id', v.tableIdentity()) id: TableIdentity): Promise<Dto<%=argv.resourceNameCapitalize%>View | undefined> {
37
42
  return await this.scope.service.<%=argv.resourceName%>.view(id);
38
43
  }
39
44
 
40
45
  @Web.patch(':id')
41
- async update(@Arg.param('id', v.tableIdentity()) id: TableIdentity, @Arg.body() <%=argv.resourceName%>: Dto<%=argv.resourceNameCapitalize%>Update) {
42
- return await this.scope.service.<%=argv.resourceName%>.update(id, <%=argv.resourceName%>);
46
+ @Api.body(z.null())
47
+ @Passport.systemAdmin()
48
+ async update(@Arg.param('id', v.tableIdentity()) id: TableIdentity, @Arg.body() <%=argv.resourceName%>: Dto<%=argv.resourceNameCapitalize%>Update): Promise<void> {
49
+ await this.scope.service.<%=argv.resourceName%>.update(id, <%=argv.resourceName%>);
43
50
  }
44
51
 
45
52
  @Web.delete(':id')
46
- async delete(@Arg.param('id', v.tableIdentity()) id: TableIdentity) {
47
- return await this.scope.service.<%=argv.resourceName%>.delete(id);
53
+ @Api.body(z.null())
54
+ @Passport.systemAdmin()
55
+ async delete(@Arg.param('id', v.tableIdentity()) id: TableIdentity): Promise<void> {
56
+ await this.scope.service.<%=argv.resourceName%>.delete(id);
48
57
  }
49
58
  }
@@ -15,7 +15,24 @@ describe('<%=argv.resourceName%>.test.ts', () => {
15
15
  name: '__TomNew__',
16
16
  description: 'This is a test',
17
17
  };
18
- // login
18
+ // role-less authenticated users cannot access generated admin actions
19
+ await app.bean.passport.signinMock();
20
+ try {
21
+ app.bean.passport.current!.roles = [];
22
+ const actions = ['create', 'select', 'view', 'update', 'delete'];
23
+ const permissions = await Promise.all(
24
+ actions.map(action =>
25
+ app.bean.permission.retrievePermissionAction(
26
+ '<%=argv.moduleInfo.relativeName%>:<%=argv.resourceName%>',
27
+ action,
28
+ ),
29
+ ),
30
+ );
31
+ assert.deepEqual(permissions, actions.map(() => false));
32
+ } finally {
33
+ await app.bean.passport.signout();
34
+ }
35
+ // login as system admin
19
36
  await app.bean.passport.signinMock();
20
37
  // create
21
38
  const <%=argv.resourceName%>Id = await app.bean.executor.performAction('post', '<%=argv.moduleActionPathRaw%>', { body: data });
@@ -24,15 +41,17 @@ describe('<%=argv.resourceName%>.test.ts', () => {
24
41
  const selectRes: Dto<%=argv.resourceNameCapitalize%>SelectRes = await app.bean.executor.performAction('get', '<%=argv.moduleActionPathRaw%>');
25
42
  assert.equal(selectRes.list.findIndex(item => item.name === data.name) > -1, true);
26
43
  // update
27
- await app.bean.executor.performAction('patch', '<%=argv.moduleActionPathRaw%>/:id', {
44
+ const updateRes = await app.bean.executor.performAction('patch', '<%=argv.moduleActionPathRaw%>/:id', {
28
45
  params: { id: <%=argv.resourceName%>Id },
29
46
  body: dataUpdate,
30
47
  });
48
+ assert.equal(updateRes, null);
31
49
  // findOne
32
50
  let <%=argv.resourceName%>: Entity<%=argv.resourceNameCapitalize%> = await app.bean.executor.performAction('get', '<%=argv.moduleActionPathRaw%>/:id', { params: { id: <%=argv.resourceName%>Id } });
33
51
  assert.equal(<%=argv.resourceName%>.name, dataUpdate.name);
34
52
  // delete
35
- await app.bean.executor.performAction('delete', '<%=argv.moduleActionPathRaw%>/:id', { params: { id: <%=argv.resourceName%>.id } });
53
+ const deleteRes = await app.bean.executor.performAction('delete', '<%=argv.moduleActionPathRaw%>/:id', { params: { id: <%=argv.resourceName%>.id } });
54
+ assert.equal(deleteRes, null);
36
55
  // findOne
37
56
  <%=argv.resourceName%> = await app.bean.executor.performAction('get', '<%=argv.moduleActionPathRaw%>/:id', { params: { id: <%=argv.resourceName%>.id } });
38
57
  assert.equal(<%=argv.resourceName%>, undefined);
@@ -4,6 +4,8 @@ import type { IDecoratorControllerOptions } from 'vona-module-a-web';
4
4
 
5
5
  import { BeanBase } from 'vona';
6
6
  import { Api, Resource, v } from 'vona-module-a-openapiutils';
7
+ import { z } from 'zod';
8
+ import { Passport } from 'vona-module-a-user';
7
9
  import { Arg, Controller, Web } from 'vona-module-a-web';
8
10
 
9
11
  import type { Model<%=argv.resourceNameCapitalize%> } from '../model/<%=argv.resourceName%>.ts';
@@ -21,29 +23,36 @@ export interface IControllerOptions<%=argv.resourceNameCapitalize%> extends IDec
21
23
  export class Controller<%=argv.resourceNameCapitalize%> extends BeanBase {
22
24
  @Web.post()
23
25
  @Api.body(v.tableIdentity())
26
+ @Passport.systemAdmin()
24
27
  async create(@Arg.body() <%=argv.resourceName%>: Dto<%=argv.resourceNameCapitalize%>Create): Promise<TableIdentity> {
25
28
  return (await this.scope.service.<%=argv.resourceName%>.create(<%=argv.resourceName%>)).id;
26
29
  }
27
30
 
28
31
  @Web.get()
29
32
  @Api.body(Dto<%=argv.resourceNameCapitalize%>SelectRes)
33
+ @Passport.systemAdmin()
30
34
  async select(@Arg.filter(Dto<%=argv.resourceNameCapitalize%>SelectReq) params: IQueryParams<Model<%=argv.resourceNameCapitalize%>>): Promise<Dto<%=argv.resourceNameCapitalize%>SelectRes> {
31
35
  return await this.scope.service.<%=argv.resourceName%>.select(params);
32
36
  }
33
37
 
34
38
  @Web.get(':id')
35
39
  @Api.body(v.optional(), v.object(Dto<%=argv.resourceNameCapitalize%>View))
40
+ @Passport.systemAdmin()
36
41
  async view(@Arg.param('id', v.tableIdentity()) id: TableIdentity): Promise<Dto<%=argv.resourceNameCapitalize%>View | undefined> {
37
42
  return await this.scope.service.<%=argv.resourceName%>.view(id);
38
43
  }
39
44
 
40
45
  @Web.patch(':id')
41
- async update(@Arg.param('id', v.tableIdentity()) id: TableIdentity, @Arg.body() <%=argv.resourceName%>: Dto<%=argv.resourceNameCapitalize%>Update) {
42
- return await this.scope.service.<%=argv.resourceName%>.update(id, <%=argv.resourceName%>);
46
+ @Api.body(z.null())
47
+ @Passport.systemAdmin()
48
+ async update(@Arg.param('id', v.tableIdentity()) id: TableIdentity, @Arg.body() <%=argv.resourceName%>: Dto<%=argv.resourceNameCapitalize%>Update): Promise<void> {
49
+ await this.scope.service.<%=argv.resourceName%>.update(id, <%=argv.resourceName%>);
43
50
  }
44
51
 
45
52
  @Web.delete(':id')
46
- async delete(@Arg.param('id', v.tableIdentity()) id: TableIdentity) {
47
- return await this.scope.service.<%=argv.resourceName%>.delete(id);
53
+ @Api.body(z.null())
54
+ @Passport.systemAdmin()
55
+ async delete(@Arg.param('id', v.tableIdentity()) id: TableIdentity): Promise<void> {
56
+ await this.scope.service.<%=argv.resourceName%>.delete(id);
48
57
  }
49
58
  }
@@ -15,7 +15,24 @@ describe('<%=argv.resourceName%>.test.ts', () => {
15
15
  name: '__TomNew__',
16
16
  description: 'This is a test',
17
17
  };
18
- // login
18
+ // role-less authenticated users cannot access generated admin actions
19
+ await app.bean.passport.signinMock();
20
+ try {
21
+ app.bean.passport.current!.roles = [];
22
+ const actions = ['create', 'select', 'view', 'update', 'delete'];
23
+ const permissions = await Promise.all(
24
+ actions.map(action =>
25
+ app.bean.permission.retrievePermissionAction(
26
+ '<%=argv.moduleInfo.relativeName%>:<%=argv.resourceName%>',
27
+ action,
28
+ ),
29
+ ),
30
+ );
31
+ assert.deepEqual(permissions, actions.map(() => false));
32
+ } finally {
33
+ await app.bean.passport.signout();
34
+ }
35
+ // login as system admin
19
36
  await app.bean.passport.signinMock();
20
37
  // create
21
38
  const <%=argv.resourceName%>Id = await app.bean.executor.performAction('post', '<%=argv.moduleActionPathRaw%>', { body: data });
@@ -24,15 +41,17 @@ describe('<%=argv.resourceName%>.test.ts', () => {
24
41
  const selectRes: Dto<%=argv.resourceNameCapitalize%>SelectRes = await app.bean.executor.performAction('get', '<%=argv.moduleActionPathRaw%>');
25
42
  assert.equal(selectRes.list.findIndex(item => item.name === data.name) > -1, true);
26
43
  // update
27
- await app.bean.executor.performAction('patch', '<%=argv.moduleActionPathRaw%>/:id', {
44
+ const updateRes = await app.bean.executor.performAction('patch', '<%=argv.moduleActionPathRaw%>/:id', {
28
45
  params: { id: <%=argv.resourceName%>Id },
29
46
  body: dataUpdate,
30
47
  });
48
+ assert.equal(updateRes, null);
31
49
  // findOne
32
50
  let <%=argv.resourceName%>: Entity<%=argv.resourceNameCapitalize%> = await app.bean.executor.performAction('get', '<%=argv.moduleActionPathRaw%>/:id', { params: { id: <%=argv.resourceName%>Id } });
33
51
  assert.equal(<%=argv.resourceName%>.name, dataUpdate.name);
34
52
  // delete
35
- await app.bean.executor.performAction('delete', '<%=argv.moduleActionPathRaw%>/:id', { params: { id: <%=argv.resourceName%>.id } });
53
+ const deleteRes = await app.bean.executor.performAction('delete', '<%=argv.moduleActionPathRaw%>/:id', { params: { id: <%=argv.resourceName%>.id } });
54
+ assert.equal(deleteRes, null);
36
55
  // findOne
37
56
  <%=argv.resourceName%> = await app.bean.executor.performAction('get', '<%=argv.moduleActionPathRaw%>/:id', { params: { id: <%=argv.resourceName%>.id } });
38
57
  assert.equal(<%=argv.resourceName%>, undefined);
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vona-cli-set-api",
3
- "version": "1.1.127",
3
+ "version": "1.1.128",
4
4
  "gitHead": "a79189b882c17af5911573896a781bbb0046d37d",
5
5
  "description": "vona cli-set-api",
6
6
  "keywords": [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vona",
3
- "version": "5.1.74",
3
+ "version": "5.1.75",
4
4
  "gitHead": "a79189b882c17af5911573896a781bbb0046d37d",
5
5
  "description": "Vona is an intuitive, elegant and powerful Node.js framework for rapidly developing enterprise applications of any size",
6
6
  "keywords": [
@@ -32,10 +32,10 @@ index 838e299407218b7bfac469d42a12d6ab59820380..e8b61cc231e39ee3a270ee513ab43ff5
32
32
  //# sourceMappingURL=module.d.ts.map
33
33
 
34
34
  diff --git a/dist/types/utils/env.d.ts b/dist/types/utils/env.d.ts
35
- index 708c0a584dfec65b8c7ad2d049156f900de49928..a8caacb276794e3ac70591b974f14d6eee3ac302 100644
35
+ index 7eb5fa0c4d5c23e046b1cebb64cf8ce85c0302fa..5f6aa470d4a88262e2108ff086923c673fdfbf67 100644
36
36
  --- a/dist/types/utils/env.d.ts
37
37
  +++ b/dist/types/utils/env.d.ts
38
- @@ -34,19 +34,4 @@ export interface ZovaConfigEnv {
38
+ @@ -35,19 +35,4 @@ export interface ZovaConfigEnv {
39
39
  MOCK_BUILD: string | undefined;
40
40
  MOCK_BUILD_PORT: string | undefined;
41
41
  }
@@ -16,7 +16,7 @@ overrides:
16
16
  zod: npm:@cabloy/zod@4.3.8
17
17
 
18
18
  patchedDependencies:
19
- zova-core@5.1.77: 0f4164c4d2bbb16d671beb5c26759dff37769be0709df941bc7b90b94261ac54
19
+ zova-core@5.1.78: ff659aa5f659be32501f56841b72f148c24acf1546f79cce551586e27fcde2ce
20
20
 
21
21
  importers:
22
22
 
@@ -501,11 +501,11 @@ importers:
501
501
  specifier: npm:@cabloy/zod@4.3.8
502
502
  version: '@cabloy/zod@4.3.8'
503
503
  zova:
504
- specifier: ^5.1.134
505
- version: 5.1.134(typescript@5.9.3)(vue@3.5.39(typescript@5.9.3))
504
+ specifier: ^5.1.135
505
+ version: 5.1.135(typescript@5.9.3)(vue@3.5.39(typescript@5.9.3))
506
506
  zova-jsx:
507
- specifier: ^1.1.77
508
- version: 1.1.77(typescript@5.9.3)
507
+ specifier: ^1.1.78
508
+ version: 1.1.78(typescript@5.9.3)
509
509
  zova-module-a-api:
510
510
  specifier: ^5.1.21
511
511
  version: 5.1.21
@@ -534,8 +534,8 @@ importers:
534
534
  specifier: ^5.1.43
535
535
  version: 5.1.43
536
536
  zova-module-a-router:
537
- specifier: ^5.1.29
538
- version: 5.1.29
537
+ specifier: ^5.1.30
538
+ version: 5.1.30
539
539
  zova-module-a-routertabs:
540
540
  specifier: ^5.1.33
541
541
  version: 5.1.33
@@ -573,11 +573,11 @@ importers:
573
573
  specifier: npm:@cabloy/zod@4.3.8
574
574
  version: '@cabloy/zod@4.3.8'
575
575
  zova:
576
- specifier: ^5.1.134
577
- version: 5.1.134(typescript@5.9.3)(vue@3.5.39(typescript@5.9.3))
576
+ specifier: ^5.1.135
577
+ version: 5.1.135(typescript@5.9.3)(vue@3.5.39(typescript@5.9.3))
578
578
  zova-jsx:
579
- specifier: ^1.1.77
580
- version: 1.1.77(typescript@5.9.3)
579
+ specifier: ^1.1.78
580
+ version: 1.1.78(typescript@5.9.3)
581
581
  zova-module-a-api:
582
582
  specifier: ^5.1.21
583
583
  version: 5.1.21
@@ -606,8 +606,8 @@ importers:
606
606
  specifier: ^5.1.43
607
607
  version: 5.1.43
608
608
  zova-module-a-router:
609
- specifier: ^5.1.29
610
- version: 5.1.29
609
+ specifier: ^5.1.30
610
+ version: 5.1.30
611
611
  zova-module-a-routertabs:
612
612
  specifier: ^5.1.33
613
613
  version: 5.1.33
@@ -7948,11 +7948,11 @@ packages:
7948
7948
  peerDependencies:
7949
7949
  zod: ^3.25.28 || ^4
7950
7950
 
7951
- zova-core@5.1.77:
7952
- resolution: {integrity: sha512-PfB5yaHVZ8U8Fjeyju3m9OGe1PjXH26HTjkA9jvl+KK9l3t/lzdEVP4s03/4zEggvPz7pkwek1heLuMaQRmX/Q==}
7951
+ zova-core@5.1.78:
7952
+ resolution: {integrity: sha512-PWvgOujRI1k7TpsJy1G8/Wf7W+/Rm6QbGHPNwcRjgDz4PhTg9z8UN1hfIO1Gg37IAI2hIrK8kqHhFRayROQHgQ==}
7953
7953
 
7954
- zova-jsx@1.1.77:
7955
- resolution: {integrity: sha512-TnjesMVt7wSvwjP4XDOg9m2MacbL5xgMrdLtU9XhhPHm3gYuNV/hzr2vQ788k/z+WWQdHsqRvTy6Ngr/GDr6zg==}
7954
+ zova-jsx@1.1.78:
7955
+ resolution: {integrity: sha512-KToLi7h2uElT4ULd5QSXfLNqSA3PaeRKJ4IHNMEJU1xiH02j2u0KCd7iVYK54mQC1QYphan3Z/YXFuW0odPx8w==}
7956
7956
 
7957
7957
  zova-module-a-api@5.1.21:
7958
7958
  resolution: {integrity: sha512-qjm/hfjC4/+7Ap/uzJfBzV0PwRlp2idEmEKwCZYxquaXq6QtJBbTWTznw1iukT4lkJZip2KFkjOvXTzdvZA2UA==}
@@ -7999,8 +7999,8 @@ packages:
7999
7999
  zova-module-a-openapi@5.1.43:
8000
8000
  resolution: {integrity: sha512-xJKvSPl1AZnH7EpgEd9a3u3IEpJfnou19DKre/7TL1KdiTdfNThLrmmkdP1UNYlUHUgCczcGUwZDdMiE9gdxwA==}
8001
8001
 
8002
- zova-module-a-router@5.1.29:
8003
- resolution: {integrity: sha512-7vIXOf0aPUXvZAFC9hM1JQyNwmQAG+EtSbQ1et8+iP6roff8yUhhq50Dw8PFU5eb7hjk1TTxpUFhCTvev5JZqQ==}
8002
+ zova-module-a-router@5.1.30:
8003
+ resolution: {integrity: sha512-pfKeZdZUMTfkjEVZfLHj21UKjKBhTd79rfF4Mr+OLwV4avK8sDPAudseaAInVvh+T/0Ayq5w9fLhzFYeU1If8A==}
8004
8004
 
8005
8005
  zova-module-a-routerstack@5.1.26:
8006
8006
  resolution: {integrity: sha512-uu4tFw2DUruanPR7cojM5p6rvyCIh+tnwPmq7s0jZdg+5XBr+YMy1RGjnE8vPFSLHW8C71hlNbps6VpKvKMiiQ==}
@@ -8026,17 +8026,17 @@ packages:
8026
8026
  zova-module-a-zod@5.1.36:
8027
8027
  resolution: {integrity: sha512-4gEjxJBelVOA84aP2XWSNSmzQr+AYh14Ha5QCj9yZIpSnEajSgYLoyLw4dwiVcscuEw7xAAOOnWbZS9uXSpJzg==}
8028
8028
 
8029
- zova-module-a-zova@5.1.92:
8030
- resolution: {integrity: sha512-RxHSKnBDMuBKwx0cJoPOIvq/DV6Xhji2yZ/lBgJTMrWOYVsHux/PbDc+vlAU+Aie2K0RiZ2ytR3mPBpMxpmexg==}
8029
+ zova-module-a-zova@5.1.93:
8030
+ resolution: {integrity: sha512-pZ7JGNvDoSLdgbQZLj/LtppDWinpd6W+u0WYQvXLuJH3Hnf0Satcpz5fQS+zseJLxm5jhXjmG3ngoYoOfv72RQ==}
8031
8031
 
8032
8032
  zova-module-rest-resource@5.1.41:
8033
8033
  resolution: {integrity: sha512-7Y8rLmHdw0pynlWqsyHPWuCWFLwr/0HsNvfef64cPw91LRujf/7OlulVWqH8ZoFNioZZUpfumB+t/+mICoYfFg==}
8034
8034
 
8035
- zova-suite-a-zova@5.1.133:
8036
- resolution: {integrity: sha512-N7YSmamP2eV3MBV4RnFXXUlDg9yEcvYkHDxfcgQQFjR1iQyxNbCLO1Ud4Bjcinray2KNrnOF41e+0gn7A6jzRQ==}
8035
+ zova-suite-a-zova@5.1.134:
8036
+ resolution: {integrity: sha512-XwmEGAM6po9GjzGLryGVCKhkVjtI3MXNY85AhQVxxMUOU6S+3dBtF399LzzDdhlKYXbvfwuHB/yDQiS1PbfS5w==}
8037
8037
 
8038
- zova@5.1.134:
8039
- resolution: {integrity: sha512-nnieXjXy0+94FKZCT05dGvwASJQ7PiM387r+dToLAuSFRCqgQwaLvYyrOXN2RhKt8Z5nefWDMwA9LPWgBksnLg==}
8038
+ zova@5.1.135:
8039
+ resolution: {integrity: sha512-Avk0YcjocSYpdRUO4A58/yQmeGUoo+bgzbtKZNLCNDit7PSEGMBJZi9p47ct7SNc6LDybhBm7xPf/xioL0Ozdg==}
8040
8040
 
8041
8041
  zwitch@1.0.5:
8042
8042
  resolution: {integrity: sha512-V50KMwwzqJV0NpZIZFwfOD5/lyny3WlSzRiXgA0G7VUnRlqttta1L6UQIHzd6EuBY/cHGfwTIck7w1yH6Q5zUw==}
@@ -13855,7 +13855,7 @@ snapshots:
13855
13855
  dependencies:
13856
13856
  zod: '@cabloy/zod@4.3.8'
13857
13857
 
13858
- zova-core@5.1.77(patch_hash=0f4164c4d2bbb16d671beb5c26759dff37769be0709df941bc7b90b94261ac54)(typescript@5.9.3):
13858
+ zova-core@5.1.78(patch_hash=ff659aa5f659be32501f56841b72f148c24acf1546f79cce551586e27fcde2ce)(typescript@5.9.3):
13859
13859
  dependencies:
13860
13860
  '@cabloy/compose': link:packages-utils/compose
13861
13861
  '@cabloy/extend': link:packages-utils/extend
@@ -13884,14 +13884,14 @@ snapshots:
13884
13884
  transitivePeerDependencies:
13885
13885
  - typescript
13886
13886
 
13887
- zova-jsx@1.1.77(typescript@5.9.3):
13887
+ zova-jsx@1.1.78(typescript@5.9.3):
13888
13888
  dependencies:
13889
13889
  '@cabloy/compose': link:packages-utils/compose
13890
13890
  '@cabloy/utils': link:packages-utils/utils
13891
13891
  '@cabloy/word-utils': 2.1.14
13892
13892
  typestyle: 2.4.0
13893
13893
  vue: 3.5.39(typescript@5.9.3)
13894
- zova-core: 5.1.77(patch_hash=0f4164c4d2bbb16d671beb5c26759dff37769be0709df941bc7b90b94261ac54)(typescript@5.9.3)
13894
+ zova-core: 5.1.78(patch_hash=ff659aa5f659be32501f56841b72f148c24acf1546f79cce551586e27fcde2ce)(typescript@5.9.3)
13895
13895
  transitivePeerDependencies:
13896
13896
  - typescript
13897
13897
 
@@ -13953,7 +13953,7 @@ snapshots:
13953
13953
  openapi3-ts: 4.6.0
13954
13954
  typestyle: 2.4.0
13955
13955
 
13956
- zova-module-a-router@5.1.29: {}
13956
+ zova-module-a-router@5.1.30: {}
13957
13957
 
13958
13958
  zova-module-a-routerstack@5.1.26: {}
13959
13959
 
@@ -13992,7 +13992,7 @@ snapshots:
13992
13992
  '@cabloy/zod-query': link:packages-utils/zod-query
13993
13993
  zod: '@cabloy/zod@4.3.8'
13994
13994
 
13995
- zova-module-a-zova@5.1.92(typescript@5.9.3)(vue@3.5.39(typescript@5.9.3)):
13995
+ zova-module-a-zova@5.1.93(typescript@5.9.3)(vue@3.5.39(typescript@5.9.3)):
13996
13996
  dependencies:
13997
13997
  '@cabloy/compose': link:packages-utils/compose
13998
13998
  '@cabloy/deps': link:packages-utils/deps
@@ -14003,14 +14003,14 @@ snapshots:
14003
14003
  '@cabloy/word-utils': 2.1.14
14004
14004
  defu: 6.1.7
14005
14005
  luxon: 3.7.2
14006
- zova-jsx: 1.1.77(typescript@5.9.3)
14006
+ zova-jsx: 1.1.78(typescript@5.9.3)
14007
14007
  transitivePeerDependencies:
14008
14008
  - typescript
14009
14009
  - vue
14010
14010
 
14011
14011
  zova-module-rest-resource@5.1.41: {}
14012
14012
 
14013
- zova-suite-a-zova@5.1.133(typescript@5.9.3)(vue@3.5.39(typescript@5.9.3)):
14013
+ zova-suite-a-zova@5.1.134(typescript@5.9.3)(vue@3.5.39(typescript@5.9.3)):
14014
14014
  dependencies:
14015
14015
  zova-module-a-api: 5.1.21
14016
14016
  zova-module-a-app: 5.1.24
@@ -14027,7 +14027,7 @@ snapshots:
14027
14027
  zova-module-a-meta: 5.1.21
14028
14028
  zova-module-a-model: 5.1.33(vue@3.5.39(typescript@5.9.3))
14029
14029
  zova-module-a-openapi: 5.1.43
14030
- zova-module-a-router: 5.1.29
14030
+ zova-module-a-router: 5.1.30
14031
14031
  zova-module-a-routerstack: 5.1.26
14032
14032
  zova-module-a-routertabs: 5.1.33
14033
14033
  zova-module-a-ssr: 5.1.29
@@ -14036,7 +14036,7 @@ snapshots:
14036
14036
  zova-module-a-style: 5.1.32
14037
14037
  zova-module-a-table: 5.1.37(vue@3.5.39(typescript@5.9.3))
14038
14038
  zova-module-a-zod: 5.1.36
14039
- zova-module-a-zova: 5.1.92(typescript@5.9.3)(vue@3.5.39(typescript@5.9.3))
14039
+ zova-module-a-zova: 5.1.93(typescript@5.9.3)(vue@3.5.39(typescript@5.9.3))
14040
14040
  transitivePeerDependencies:
14041
14041
  - '@vue/composition-api'
14042
14042
  - debug
@@ -14044,10 +14044,10 @@ snapshots:
14044
14044
  - typescript
14045
14045
  - vue
14046
14046
 
14047
- zova@5.1.134(typescript@5.9.3)(vue@3.5.39(typescript@5.9.3)):
14047
+ zova@5.1.135(typescript@5.9.3)(vue@3.5.39(typescript@5.9.3)):
14048
14048
  dependencies:
14049
- zova-core: 5.1.77(patch_hash=0f4164c4d2bbb16d671beb5c26759dff37769be0709df941bc7b90b94261ac54)(typescript@5.9.3)
14050
- zova-suite-a-zova: 5.1.133(typescript@5.9.3)(vue@3.5.39(typescript@5.9.3))
14049
+ zova-core: 5.1.78(patch_hash=ff659aa5f659be32501f56841b72f148c24acf1546f79cce551586e27fcde2ce)(typescript@5.9.3)
14050
+ zova-suite-a-zova: 5.1.134(typescript@5.9.3)(vue@3.5.39(typescript@5.9.3))
14051
14051
  transitivePeerDependencies:
14052
14052
  - '@vue/composition-api'
14053
14053
  - debug