cabloy 5.1.106 → 5.1.108
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 +22 -0
- package/cabloy-docs/.vitepress/config.mjs +6 -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/multi-instance-and-instance-resolution.md +18 -0
- package/cabloy-docs/backend/runtime-and-flavors.md +2 -2
- package/cabloy-docs/backend/scripts.md +3 -2
- package/cabloy-docs/backend/unit-testing.md +8 -2
- package/cabloy-docs/frontend/ssr-env.md +20 -0
- package/cabloy-docs/fullstack/deploy-cloudflare-docker.md +136 -0
- 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/docker-compose-original/config/nginx/conf.d/nginx.conf +1 -1
- package/vona/env/.env +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/src/backend/config/config/config.ts +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/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
|
@@ -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": [
|
|
@@ -49,7 +49,7 @@ export default async function (app: VonaApplication) {
|
|
|
49
49
|
|
|
50
50
|
// server
|
|
51
51
|
const publicDir = env.SERVER_PUBLICDIR || getPublicPathPhysicalRoot(app);
|
|
52
|
-
const subdomainOffset = Number.parseInt(env.SERVER_SUBDOMAINOFFSET || '
|
|
52
|
+
const subdomainOffset = Number.parseInt(env.SERVER_SUBDOMAINOFFSET || '2');
|
|
53
53
|
const workers = Number.parseInt(env.SERVER_WORKERS!);
|
|
54
54
|
config.server = {
|
|
55
55
|
keys: (env.SERVER_KEYS || '').split(','),
|
|
@@ -144,7 +144,7 @@ import { SymbolKeyEntity, SymbolKeyEntityMeta, SymbolKeyModelOptions } from 'von
|
|
|
144
144
|
declare module 'vona-module-home-user' {
|
|
145
145
|
export interface IModelOptionsUser {
|
|
146
146
|
relations: {
|
|
147
|
-
roles: IModelRelationBelongsToMany<
|
|
147
|
+
roles: IModelRelationBelongsToMany<'home-user:roleUser', 'home-user:role', false, undefined,undefined,undefined,undefined>;
|
|
148
148
|
};
|
|
149
149
|
}
|
|
150
150
|
export interface ModelRole {
|
|
@@ -511,8 +511,8 @@ export interface IApiPathPostRecord{
|
|
|
511
511
|
'/home/user/passport/logout': undefined;
|
|
512
512
|
'/home/user/passport/register': undefined;
|
|
513
513
|
'/home/user/passport/login': undefined;
|
|
514
|
-
'/home/user/passport/createPassportJwtFromOauthCode': undefined;
|
|
515
514
|
'/home/user/passport/refreshAuthToken': undefined;
|
|
515
|
+
'/home/user/passport/createPassportJwtFromOauthCode': undefined;
|
|
516
516
|
'/home/user/passport/createTempAuthToken': undefined;
|
|
517
517
|
}
|
|
518
518
|
|
|
@@ -18,6 +18,8 @@ export class MetaVersion extends BeanBase implements IMetaVersionUpdate, IMetaVe
|
|
|
18
18
|
table.comment(entityRole.$comment.$table);
|
|
19
19
|
table.basicFields();
|
|
20
20
|
table.string(entityRole.name, 255).comment(entityRole.$comment.name);
|
|
21
|
+
table.string(entityRole.title, 255).comment(entityRole.$comment.title);
|
|
22
|
+
table.json(entityRole.locales).comment(entityRole.$comment.locales);
|
|
21
23
|
table.json(entityRole.siteIds).comment(entityRole.$comment.siteIds);
|
|
22
24
|
});
|
|
23
25
|
// homeUser
|
|
@@ -49,14 +51,14 @@ export class MetaVersion extends BeanBase implements IMetaVersionUpdate, IMetaVe
|
|
|
49
51
|
async init(options: IMetaVersionInitOptions) {
|
|
50
52
|
if (options.version === 1) {
|
|
51
53
|
// roles
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
54
|
+
for (const [name, role] of Object.entries(this.scope.config.builtinRoles)) {
|
|
55
|
+
await this.scope.model.role.insert({
|
|
56
|
+
name,
|
|
57
|
+
title: role.title,
|
|
58
|
+
locales: role.locales ?? {},
|
|
59
|
+
siteIds: role.siteIds,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
60
62
|
// user: admin
|
|
61
63
|
if (!this.scope.config.disableUserAdmin) {
|
|
62
64
|
await this.bean.auth.authenticate('auth-simple:simple', {
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import type { VonaApplication } from 'vona';
|
|
2
2
|
|
|
3
|
+
import { builtinRoles } from './roles.ts';
|
|
4
|
+
|
|
5
|
+
export { builtinRoles } from './roles.ts';
|
|
6
|
+
export type { IRoleConfig } from './roles.ts';
|
|
7
|
+
|
|
3
8
|
export function config(_app: VonaApplication) {
|
|
4
9
|
return {
|
|
5
10
|
passwordDefault: {
|
|
@@ -7,5 +12,6 @@ export function config(_app: VonaApplication) {
|
|
|
7
12
|
},
|
|
8
13
|
disableBootstrapSystemAdmin: false,
|
|
9
14
|
disableUserAdmin: false,
|
|
15
|
+
builtinRoles,
|
|
10
16
|
};
|
|
11
17
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { ILocaleRecord } from 'vona';
|
|
2
|
+
|
|
3
|
+
export interface IRoleConfig {
|
|
4
|
+
title: string;
|
|
5
|
+
locales?: Partial<Record<keyof ILocaleRecord, string>>;
|
|
6
|
+
siteIds: string[];
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const builtinRoles: Record<string, IRoleConfig> = {
|
|
10
|
+
registeredUser: {
|
|
11
|
+
title: 'Registered User',
|
|
12
|
+
locales: {
|
|
13
|
+
'zh-cn': '注册用户',
|
|
14
|
+
},
|
|
15
|
+
siteIds: ['web'],
|
|
16
|
+
},
|
|
17
|
+
systemAdmin: {
|
|
18
|
+
title: 'System Administrator',
|
|
19
|
+
locales: {
|
|
20
|
+
'zh-cn': '系统管理员',
|
|
21
|
+
},
|
|
22
|
+
siteIds: ['web', 'admin'],
|
|
23
|
+
},
|
|
24
|
+
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ILocaleRecord } from 'vona';
|
|
1
2
|
import type { IDecoratorEntityOptions } from 'vona-module-a-orm';
|
|
2
3
|
import type { IRole } from 'vona-module-a-user';
|
|
3
4
|
|
|
@@ -14,6 +15,12 @@ export class EntityRole extends EntityBase implements IRole {
|
|
|
14
15
|
@Api.field(v.title($locale('RoleName')))
|
|
15
16
|
name: string;
|
|
16
17
|
|
|
18
|
+
@Api.field(v.title($locale('RoleTitle')))
|
|
19
|
+
title: string;
|
|
20
|
+
|
|
21
|
+
@Api.field(v.title($locale('RoleLocales')), v.optional(), z.record(z.string(), z.string()))
|
|
22
|
+
locales?: Partial<Record<keyof ILocaleRecord, string>>;
|
|
23
|
+
|
|
17
24
|
@Api.field(v.array(z.string()))
|
|
18
25
|
siteIds: string[];
|
|
19
26
|
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import assert from 'node:assert';
|
|
2
|
+
import { describe, it } from 'node:test';
|
|
3
|
+
import { app } from 'vona-mock';
|
|
4
|
+
|
|
5
|
+
describe('role.test.ts', () => {
|
|
6
|
+
it('action:roleSeedAndPassport', async () => {
|
|
7
|
+
await app.bean.executor.mockCtx(async () => {
|
|
8
|
+
const scope = app.scope('home-user');
|
|
9
|
+
const roleRegisteredUser = await scope.model.role.getByName('registeredUser');
|
|
10
|
+
assert.equal(roleRegisteredUser?.title, 'Registered User');
|
|
11
|
+
assert.deepEqual(roleRegisteredUser?.locales, { 'zh-cn': '注册用户' });
|
|
12
|
+
assert.deepEqual(roleRegisteredUser?.siteIds, ['web']);
|
|
13
|
+
|
|
14
|
+
const roleSystemAdmin = await scope.model.role.getByName('systemAdmin');
|
|
15
|
+
assert.equal(roleSystemAdmin?.title, 'System Administrator');
|
|
16
|
+
assert.deepEqual(roleSystemAdmin?.locales, { 'zh-cn': '系统管理员' });
|
|
17
|
+
assert.deepEqual(roleSystemAdmin?.siteIds, ['web', 'admin']);
|
|
18
|
+
|
|
19
|
+
await app.bean.passport.signinMock();
|
|
20
|
+
const passport = await app.bean.executor.performAction('get', '/home/user/passport/current');
|
|
21
|
+
assert.deepEqual(
|
|
22
|
+
passport.roles.map(role => ({
|
|
23
|
+
name: role.name,
|
|
24
|
+
title: role.title,
|
|
25
|
+
locales: role.locales,
|
|
26
|
+
siteIds: role.siteIds,
|
|
27
|
+
})),
|
|
28
|
+
[
|
|
29
|
+
{
|
|
30
|
+
name: 'registeredUser',
|
|
31
|
+
title: 'Registered User',
|
|
32
|
+
locales: { 'zh-cn': '注册用户' },
|
|
33
|
+
siteIds: ['web'],
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
name: 'systemAdmin',
|
|
37
|
+
title: 'System Administrator',
|
|
38
|
+
locales: { 'zh-cn': '系统管理员' },
|
|
39
|
+
siteIds: ['web', 'admin'],
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
);
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
});
|
|
@@ -5,7 +5,9 @@ import type { IDecoratorControllerOptions } from 'vona-module-a-web';
|
|
|
5
5
|
import { BeanBase } from 'vona';
|
|
6
6
|
import { Core } from 'vona-module-a-core';
|
|
7
7
|
import { Api, Resource, v } from 'vona-module-a-openapiutils';
|
|
8
|
+
import { Passport } from 'vona-module-a-user';
|
|
8
9
|
import { Arg, Controller, Web } from 'vona-module-a-web';
|
|
10
|
+
import { z } from 'zod';
|
|
9
11
|
|
|
10
12
|
import type { ModelRecord } from '../model/record.ts';
|
|
11
13
|
|
|
@@ -22,6 +24,7 @@ export interface IControllerOptionsRecord extends IDecoratorControllerOptions {}
|
|
|
22
24
|
export class ControllerRecord extends BeanBase {
|
|
23
25
|
@Web.post()
|
|
24
26
|
@Api.body(v.tableIdentity())
|
|
27
|
+
@Passport.systemAdmin()
|
|
25
28
|
async create(@Arg.body() record: DtoRecordCreate): Promise<TableIdentity> {
|
|
26
29
|
return (await this.scope.service.record.create(record)).id;
|
|
27
30
|
}
|
|
@@ -29,6 +32,7 @@ export class ControllerRecord extends BeanBase {
|
|
|
29
32
|
@Web.get()
|
|
30
33
|
@Api.body(DtoRecordSelectRes)
|
|
31
34
|
@Core.serializer()
|
|
35
|
+
@Passport.systemAdmin()
|
|
32
36
|
async select(
|
|
33
37
|
@Arg.filter(DtoRecordSelectReq) params: IQueryParams<ModelRecord>,
|
|
34
38
|
): Promise<DtoRecordSelectRes> {
|
|
@@ -38,6 +42,7 @@ export class ControllerRecord extends BeanBase {
|
|
|
38
42
|
@Web.get(':id')
|
|
39
43
|
@Api.body(v.optional(), v.object(DtoRecordView))
|
|
40
44
|
@Core.serializer()
|
|
45
|
+
@Passport.systemAdmin()
|
|
41
46
|
async view(
|
|
42
47
|
@Arg.param('id', v.tableIdentity()) id: TableIdentity,
|
|
43
48
|
): Promise<DtoRecordView | undefined> {
|
|
@@ -45,15 +50,19 @@ export class ControllerRecord extends BeanBase {
|
|
|
45
50
|
}
|
|
46
51
|
|
|
47
52
|
@Web.patch(':id')
|
|
53
|
+
@Api.body(z.null())
|
|
54
|
+
@Passport.systemAdmin()
|
|
48
55
|
async update(
|
|
49
56
|
@Arg.param('id', v.tableIdentity()) id: TableIdentity,
|
|
50
57
|
@Arg.body() record: DtoRecordUpdate,
|
|
51
|
-
) {
|
|
52
|
-
|
|
58
|
+
): Promise<void> {
|
|
59
|
+
await this.scope.service.record.update(id, record);
|
|
53
60
|
}
|
|
54
61
|
|
|
55
62
|
@Web.delete(':id')
|
|
56
|
-
|
|
57
|
-
|
|
63
|
+
@Api.body(z.null())
|
|
64
|
+
@Passport.systemAdmin()
|
|
65
|
+
async delete(@Arg.param('id', v.tableIdentity()) id: TableIdentity): Promise<void> {
|
|
66
|
+
await this.scope.service.record.delete(id);
|
|
58
67
|
}
|
|
59
68
|
}
|
|
@@ -160,10 +160,11 @@ describe('record.test.ts', () => {
|
|
|
160
160
|
},
|
|
161
161
|
],
|
|
162
162
|
} as any as DtoRecordUpdate;
|
|
163
|
-
await app.bean.executor.performAction('patch', '/training/record/:id', {
|
|
163
|
+
const updateRes = await app.bean.executor.performAction('patch', '/training/record/:id', {
|
|
164
164
|
params: { id: recordId },
|
|
165
165
|
body: dataUpdate,
|
|
166
166
|
});
|
|
167
|
+
assert.equal(updateRes, null);
|
|
167
168
|
|
|
168
169
|
record = await app.bean.executor.performAction('get', '/training/record/:id', {
|
|
169
170
|
params: { id: recordId },
|
|
@@ -200,9 +201,10 @@ describe('record.test.ts', () => {
|
|
|
200
201
|
assert.equal(studentRecord?.dossierFiles?.length, 2);
|
|
201
202
|
assert.equal(studentRecord?.trainingRecordSubjects?.length, 2);
|
|
202
203
|
|
|
203
|
-
await app.bean.executor.performAction('delete', '/training/record/:id', {
|
|
204
|
+
const deleteRes = await app.bean.executor.performAction('delete', '/training/record/:id', {
|
|
204
205
|
params: { id: recordId },
|
|
205
206
|
});
|
|
207
|
+
assert.equal(deleteRes, null);
|
|
206
208
|
|
|
207
209
|
record = await app.bean.executor.performAction('get', '/training/record/:id', {
|
|
208
210
|
params: { id: recordId },
|
|
@@ -243,4 +245,25 @@ describe('record.test.ts', () => {
|
|
|
243
245
|
}
|
|
244
246
|
});
|
|
245
247
|
});
|
|
248
|
+
|
|
249
|
+
it('action:record:systemAdmin', async () => {
|
|
250
|
+
await app.bean.executor.mockCtx(async () => {
|
|
251
|
+
await app.bean.passport.signinMock();
|
|
252
|
+
try {
|
|
253
|
+
app.bean.passport.current!.roles = [];
|
|
254
|
+
const actions = ['create', 'select', 'view', 'update', 'delete'];
|
|
255
|
+
const permissions = await Promise.all(
|
|
256
|
+
actions.map(action =>
|
|
257
|
+
app.bean.permission.retrievePermissionAction('training-record:record', action),
|
|
258
|
+
),
|
|
259
|
+
);
|
|
260
|
+
assert.deepEqual(
|
|
261
|
+
permissions,
|
|
262
|
+
actions.map(() => false),
|
|
263
|
+
);
|
|
264
|
+
} finally {
|
|
265
|
+
await app.bean.passport.signout();
|
|
266
|
+
}
|
|
267
|
+
});
|
|
268
|
+
});
|
|
246
269
|
});
|
|
@@ -5,7 +5,9 @@ import type { IDecoratorControllerOptions } from 'vona-module-a-web';
|
|
|
5
5
|
import { BeanBase } from 'vona';
|
|
6
6
|
import { Core } from 'vona-module-a-core';
|
|
7
7
|
import { Api, Resource, v } from 'vona-module-a-openapiutils';
|
|
8
|
+
import { Passport } from 'vona-module-a-user';
|
|
8
9
|
import { Arg, Controller, Web } from 'vona-module-a-web';
|
|
10
|
+
import { z } from 'zod';
|
|
9
11
|
|
|
10
12
|
import type { ModelStudent } from '../model/student.ts';
|
|
11
13
|
|
|
@@ -23,6 +25,7 @@ export interface IControllerOptionsStudent extends IDecoratorControllerOptions {
|
|
|
23
25
|
export class ControllerStudent extends BeanBase {
|
|
24
26
|
@Web.post()
|
|
25
27
|
@Api.body(v.tableIdentity())
|
|
28
|
+
@Passport.systemAdmin()
|
|
26
29
|
async create(@Arg.body() student: DtoStudentCreate): Promise<TableIdentity> {
|
|
27
30
|
return (await this.scope.service.student.create(student)).id;
|
|
28
31
|
}
|
|
@@ -30,6 +33,7 @@ export class ControllerStudent extends BeanBase {
|
|
|
30
33
|
@Web.get()
|
|
31
34
|
@Api.body(DtoStudentSelectRes)
|
|
32
35
|
@Core.serializer()
|
|
36
|
+
@Passport.systemAdmin()
|
|
33
37
|
async select(
|
|
34
38
|
@Arg.filter(DtoStudentSelectReq) params: IQueryParams<ModelStudent>,
|
|
35
39
|
): Promise<DtoStudentSelectRes> {
|
|
@@ -39,6 +43,7 @@ export class ControllerStudent extends BeanBase {
|
|
|
39
43
|
@Web.get(':id')
|
|
40
44
|
@Api.body(v.optional(), v.object(DtoStudentView))
|
|
41
45
|
@Core.serializer()
|
|
46
|
+
@Passport.systemAdmin()
|
|
42
47
|
async view(
|
|
43
48
|
@Arg.param('id', v.tableIdentity()) id: TableIdentity,
|
|
44
49
|
): Promise<DtoStudentView | undefined> {
|
|
@@ -46,16 +51,19 @@ export class ControllerStudent extends BeanBase {
|
|
|
46
51
|
}
|
|
47
52
|
|
|
48
53
|
@Web.patch(':id')
|
|
54
|
+
@Api.body(z.null())
|
|
55
|
+
@Passport.systemAdmin()
|
|
49
56
|
async update(
|
|
50
57
|
@Arg.param('id', v.tableIdentity()) id: TableIdentity,
|
|
51
58
|
@Arg.body() student: DtoStudentUpdate,
|
|
52
|
-
) {
|
|
53
|
-
|
|
59
|
+
): Promise<void> {
|
|
60
|
+
await this.scope.service.student.update(id, student);
|
|
54
61
|
}
|
|
55
62
|
|
|
56
63
|
@Web.get('summary/:id')
|
|
57
64
|
@Api.body(v.optional(), v.object(DtoStudentSummary))
|
|
58
65
|
@Core.serializer()
|
|
66
|
+
@Passport.systemAdmin()
|
|
59
67
|
async summary(
|
|
60
68
|
@Arg.param('id', v.tableIdentity()) id: TableIdentity,
|
|
61
69
|
): Promise<DtoStudentSummary | undefined> {
|
|
@@ -63,12 +71,16 @@ export class ControllerStudent extends BeanBase {
|
|
|
63
71
|
}
|
|
64
72
|
|
|
65
73
|
@Web.delete(':id')
|
|
66
|
-
|
|
67
|
-
|
|
74
|
+
@Api.body(z.null())
|
|
75
|
+
@Passport.systemAdmin()
|
|
76
|
+
async delete(@Arg.param('id', v.tableIdentity()) id: TableIdentity): Promise<void> {
|
|
77
|
+
await this.scope.service.student.delete(id);
|
|
68
78
|
}
|
|
69
79
|
|
|
70
80
|
@Web.delete('deleteForce/:id')
|
|
71
|
-
|
|
72
|
-
|
|
81
|
+
@Api.body(z.null())
|
|
82
|
+
@Passport.systemAdmin()
|
|
83
|
+
async deleteForce(@Arg.param('id', v.tableIdentity()) id: TableIdentity): Promise<void> {
|
|
84
|
+
await this.scope.service.student.deleteForce(id);
|
|
73
85
|
}
|
|
74
86
|
}
|
|
@@ -123,10 +123,11 @@ describe('student.test.ts', () => {
|
|
|
123
123
|
},
|
|
124
124
|
],
|
|
125
125
|
} as any as DtoStudentUpdate;
|
|
126
|
-
await app.bean.executor.performAction('patch', '/training/student/:id', {
|
|
126
|
+
const updateRes = await app.bean.executor.performAction('patch', '/training/student/:id', {
|
|
127
127
|
params: { id: studentId },
|
|
128
128
|
body: dataUpdate,
|
|
129
129
|
});
|
|
130
|
+
assert.equal(updateRes, null);
|
|
130
131
|
// findOne after nested update
|
|
131
132
|
student = await app.bean.executor.performAction('get', '/training/student/:id', {
|
|
132
133
|
params: { id: studentId },
|
|
@@ -168,9 +169,10 @@ describe('student.test.ts', () => {
|
|
|
168
169
|
assert.equal(typeof summary.levelTitle, 'string');
|
|
169
170
|
assert.equal(typeof summary.summaryText, 'string');
|
|
170
171
|
// delete
|
|
171
|
-
await app.bean.executor.performAction('delete', '/training/student/:id', {
|
|
172
|
+
const deleteRes = await app.bean.executor.performAction('delete', '/training/student/:id', {
|
|
172
173
|
params: { id: student.id },
|
|
173
174
|
});
|
|
175
|
+
assert.equal(deleteRes, null);
|
|
174
176
|
// findOne
|
|
175
177
|
student = await app.bean.executor.performAction('get', '/training/student/:id', {
|
|
176
178
|
params: { id: student.id },
|
|
@@ -180,9 +182,14 @@ describe('student.test.ts', () => {
|
|
|
180
182
|
const studentIdForce = await app.bean.executor.performAction('post', '/training/student', {
|
|
181
183
|
body: data,
|
|
182
184
|
});
|
|
183
|
-
await app.bean.executor.performAction(
|
|
184
|
-
|
|
185
|
-
|
|
185
|
+
const deleteForceRes = await app.bean.executor.performAction(
|
|
186
|
+
'delete',
|
|
187
|
+
'/training/student/deleteForce/:id',
|
|
188
|
+
{
|
|
189
|
+
params: { id: studentIdForce },
|
|
190
|
+
},
|
|
191
|
+
);
|
|
192
|
+
assert.equal(deleteForceRes, null);
|
|
186
193
|
const studentForce: EntityStudent | undefined = await app.bean
|
|
187
194
|
.scope('training-student')
|
|
188
195
|
.model.student.getById(studentIdForce, {
|
|
@@ -194,6 +201,27 @@ describe('student.test.ts', () => {
|
|
|
194
201
|
});
|
|
195
202
|
});
|
|
196
203
|
|
|
204
|
+
it('action:student:systemAdmin', async () => {
|
|
205
|
+
await app.bean.executor.mockCtx(async () => {
|
|
206
|
+
await app.bean.passport.signinMock();
|
|
207
|
+
try {
|
|
208
|
+
app.bean.passport.current!.roles = [];
|
|
209
|
+
const actions = ['create', 'select', 'view', 'update', 'summary', 'delete', 'deleteForce'];
|
|
210
|
+
const permissions = await Promise.all(
|
|
211
|
+
actions.map(action =>
|
|
212
|
+
app.bean.permission.retrievePermissionAction('training-student:student', action),
|
|
213
|
+
),
|
|
214
|
+
);
|
|
215
|
+
assert.deepEqual(
|
|
216
|
+
permissions,
|
|
217
|
+
actions.map(() => false),
|
|
218
|
+
);
|
|
219
|
+
} finally {
|
|
220
|
+
await app.bean.passport.signout();
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
|
|
197
225
|
it('action:student:level invalid', async () => {
|
|
198
226
|
await app.bean.executor.mockCtx(async () => {
|
|
199
227
|
await app.bean.passport.signinMock();
|
|
@@ -33,7 +33,7 @@ export class ServiceSsrHandler extends BeanBase {
|
|
|
33
33
|
return await ssrHandler.resolvePath(filename);
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
public async render(options: ISsrHandlerRenderOptionsInner) {
|
|
36
|
+
public async render(options: ISsrHandlerRenderOptionsInner): Promise<true | string | undefined> {
|
|
37
37
|
const ssrHandler = await this.ensureReady();
|
|
38
38
|
return await ssrHandler.render(options);
|
|
39
39
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { TableIdentity } from 'table-identity';
|
|
2
|
+
import type { ILocaleRecord } from 'vona';
|
|
2
3
|
|
|
3
4
|
export interface IRoleNameRecord {
|
|
4
5
|
registeredUser: never;
|
|
@@ -10,6 +11,8 @@ export interface IRoleIdRecord {}
|
|
|
10
11
|
export interface IRole {
|
|
11
12
|
id: TableIdentity;
|
|
12
13
|
name: string;
|
|
14
|
+
title: string;
|
|
15
|
+
locales?: Partial<Record<keyof ILocaleRecord, string>>;
|
|
13
16
|
siteIds: string[];
|
|
14
17
|
}
|
|
15
18
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zova-cli",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.98",
|
|
4
4
|
"gitHead": "6f675a8cc46d596142c591c28a40cc4d82fcc6cc",
|
|
5
5
|
"description": "zova cli",
|
|
6
6
|
"keywords": [
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"@cabloy/process-helper": "^3.1.8",
|
|
45
45
|
"fs-extra": "^11.3.5",
|
|
46
46
|
"semver": "^7.6.2",
|
|
47
|
-
"zova-cli-set-front": "^1.2.
|
|
47
|
+
"zova-cli-set-front": "^1.2.96"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"clean-package": "^2.2.0",
|
|
@@ -606,6 +606,10 @@ function _patchOpenapiTSOptions(options?: OpenAPITSOptions) {
|
|
|
606
606
|
const res = transformCustom(schemaObject, options);
|
|
607
607
|
if (res !== undefined) return res;
|
|
608
608
|
}
|
|
609
|
+
// // null
|
|
610
|
+
// if (schemaObject.type === 'null') {
|
|
611
|
+
// return NULL;
|
|
612
|
+
// }
|
|
609
613
|
// multipart
|
|
610
614
|
if (schemaObject.format === 'binary') {
|
|
611
615
|
if (
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zova",
|
|
3
|
-
"version": "5.1.
|
|
3
|
+
"version": "5.1.136",
|
|
4
4
|
"gitHead": "2c5c19284bab738e492856189acb6fad74b8a7b7",
|
|
5
5
|
"description": "A vue3 framework with ioc",
|
|
6
6
|
"keywords": [
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
48
|
"zova-core": "^5.1.78",
|
|
49
|
-
"zova-suite-a-zova": "^5.1.
|
|
49
|
+
"zova-suite-a-zova": "^5.1.135"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
52
|
"clean-package": "^2.2.0",
|