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.
- package/.cabloy-version +1 -1
- package/CHANGELOG.md +18 -0
- package/cabloy-docs/backend/backend-resource-module-contract-chain.md +1 -0
- package/cabloy-docs/backend/controller-aop-guide.md +12 -0
- package/cabloy-docs/backend/controller-guide.md +45 -3
- package/cabloy-docs/backend/crud-workflow.md +14 -0
- package/cabloy-docs/backend/unit-testing.md +8 -2
- package/cabloy-docs/fullstack/openapi-to-sdk.md +1 -0
- package/cabloy-docs/fullstack/tutorial-2-first-crud.md +3 -0
- package/package.json +1 -1
- package/vona/packages-cli/cli/package.json +1 -1
- package/vona/packages-cli/cli-set-api/cli/templates/tools/crud/boilerplate/src/bean/ssrMenu.{{resourceName}}.ts_ +1 -0
- package/vona/packages-cli/cli-set-api/cli/templates/tools/crudBasic/boilerplate/src/controller/{{resourceName}}.ts_ +13 -4
- package/vona/packages-cli/cli-set-api/cli/templates/tools/crudBasic/boilerplate/test/{{resourceName}}.test.ts_ +22 -3
- package/vona/packages-cli/cli-set-api/cli/templates/tools/crudStart/boilerplate/src/controller/{{resourceName}}.ts_ +13 -4
- package/vona/packages-cli/cli-set-api/cli/templates/tools/crudStart/boilerplate/test/{{resourceName}}.test.ts_ +22 -3
- package/vona/packages-cli/cli-set-api/package.json +1 -1
- package/vona/packages-vona/vona/package.json +1 -1
- package/vona/patches/{zova-core@5.1.77.patch → zova-core@5.1.78.patch} +2 -2
- package/vona/pnpm-lock.yaml +37 -37
- package/vona/pnpm-workspace.yaml +1 -1
- package/vona/src/suite/a-home/modules/home-user/src/.metadata/index.ts +2 -2
- package/vona/src/suite/a-home/modules/home-user/src/bean/meta.version.ts +10 -8
- package/vona/src/suite/a-home/modules/home-user/src/config/config.ts +6 -0
- package/vona/src/suite/a-home/modules/home-user/src/config/locale/en-us.ts +2 -0
- package/vona/src/suite/a-home/modules/home-user/src/config/locale/zh-cn.ts +2 -0
- package/vona/src/suite/a-home/modules/home-user/src/config/roles.ts +24 -0
- package/vona/src/suite/a-home/modules/home-user/src/entity/role.ts +7 -0
- package/vona/src/suite/a-home/modules/home-user/test/role.test.ts +45 -0
- package/vona/src/suite/a-training/modules/training-record/src/controller/record.ts +13 -4
- package/vona/src/suite/a-training/modules/training-record/test/record.test.ts +25 -2
- package/vona/src/suite/a-training/modules/training-student/src/controller/student.ts +18 -6
- package/vona/src/suite/a-training/modules/training-student/test/student.test.ts +33 -5
- package/vona/src/suite-vendor/a-cabloy/modules/a-ssr/cli/ssrMenuAdmin/boilerplate/{{sceneName}}.{{beanName}}.ts_ +1 -0
- package/vona/src/suite-vendor/a-cabloy/modules/a-ssr/package.json +1 -1
- package/vona/src/suite-vendor/a-cabloy/modules/a-ssr/src/service/ssrHandler.ts +1 -1
- package/vona/src/suite-vendor/a-cabloy/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-user/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-user/src/types/role.ts +3 -0
- package/vona/src/suite-vendor/a-vona/package.json +1 -1
- package/zova/packages-cli/cli/package.json +2 -2
- package/zova/packages-cli/cli-set-front/package.json +1 -1
- package/zova/packages-cli/cli-set-front/src/lib/bean/cli.openapi.generate.ts +4 -0
- package/zova/packages-zova/zova/package.json +2 -2
- package/zova/pnpm-lock.yaml +9 -9
- package/zova/src/suite/a-home/modules/home-api/mock/passport.fake.ts +50 -1
- package/zova/src/suite/a-home/modules/home-api/src/api/openapi/types.ts +15 -7
- package/zova/src/suite/a-home/modules/home-base/src/.metadata/index.ts +10 -0
- package/zova/src/suite/a-home/modules/home-base/src/page/errorAccessDenied/controller.tsx +1 -1
- package/zova/src/suite/a-training/modules/training-student/src/api/openapi/types.ts +26 -17
- package/zova/src/suite/cabloy-basic/modules/basic-file/src/api/openapi/types.ts +26 -17
- package/zova/src/suite/cabloy-basic/modules/basic-image/src/api/openapi/types.ts +26 -17
- package/zova/src/suite-vendor/a-zova/modules/a-ssrserver/package.json +1 -1
- package/zova/src/suite-vendor/a-zova/modules/a-ssrserver/src/service/ssrHandler.ts +1 -1
- package/zova/src/suite-vendor/a-zova/package.json +2 -2
package/.cabloy-version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
5.1.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
@@ -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
|
-
|
|
42
|
-
|
|
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
|
-
|
|
47
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
42
|
-
|
|
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
|
-
|
|
47
|
-
|
|
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
|
-
//
|
|
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",
|
|
3
|
-
"version": "5.1.
|
|
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
|
|
35
|
+
index 7eb5fa0c4d5c23e046b1cebb64cf8ce85c0302fa..5f6aa470d4a88262e2108ff086923c673fdfbf67 100644
|
|
36
36
|
--- a/dist/types/utils/env.d.ts
|
|
37
37
|
+++ b/dist/types/utils/env.d.ts
|
|
38
|
-
@@ -
|
|
38
|
+
@@ -35,19 +35,4 @@ export interface ZovaConfigEnv {
|
|
39
39
|
MOCK_BUILD: string | undefined;
|
|
40
40
|
MOCK_BUILD_PORT: string | undefined;
|
|
41
41
|
}
|
package/vona/pnpm-lock.yaml
CHANGED
|
@@ -16,7 +16,7 @@ overrides:
|
|
|
16
16
|
zod: npm:@cabloy/zod@4.3.8
|
|
17
17
|
|
|
18
18
|
patchedDependencies:
|
|
19
|
-
zova-core@5.1.
|
|
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.
|
|
505
|
-
version: 5.1.
|
|
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.
|
|
508
|
-
version: 1.1.
|
|
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.
|
|
538
|
-
version: 5.1.
|
|
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.
|
|
577
|
-
version: 5.1.
|
|
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.
|
|
580
|
-
version: 1.1.
|
|
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.
|
|
610
|
-
version: 5.1.
|
|
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.
|
|
7952
|
-
resolution: {integrity: sha512-
|
|
7951
|
+
zova-core@5.1.78:
|
|
7952
|
+
resolution: {integrity: sha512-PWvgOujRI1k7TpsJy1G8/Wf7W+/Rm6QbGHPNwcRjgDz4PhTg9z8UN1hfIO1Gg37IAI2hIrK8kqHhFRayROQHgQ==}
|
|
7953
7953
|
|
|
7954
|
-
zova-jsx@1.1.
|
|
7955
|
-
resolution: {integrity: sha512-
|
|
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.
|
|
8003
|
-
resolution: {integrity: sha512-
|
|
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.
|
|
8030
|
-
resolution: {integrity: sha512-
|
|
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.
|
|
8036
|
-
resolution: {integrity: sha512-
|
|
8035
|
+
zova-suite-a-zova@5.1.134:
|
|
8036
|
+
resolution: {integrity: sha512-XwmEGAM6po9GjzGLryGVCKhkVjtI3MXNY85AhQVxxMUOU6S+3dBtF399LzzDdhlKYXbvfwuHB/yDQiS1PbfS5w==}
|
|
8037
8037
|
|
|
8038
|
-
zova@5.1.
|
|
8039
|
-
resolution: {integrity: sha512-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
14050
|
-
zova-suite-a-zova: 5.1.
|
|
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
|