vona-cli-set-api 1.1.149 → 1.1.150

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.
@@ -27,42 +27,42 @@ export interface IControllerOptions<%=argv.resourceNameCapitalize%> extends IDec
27
27
  export class Controller<%=argv.resourceNameCapitalize%> extends BeanBase {
28
28
  @Web.post('', { summary: $locale('<%=argv.resourceNameCapitalize%>Create') })
29
29
  @Api.body(v.tableIdentity())
30
- @Passport.systemAdmin()
30
+ @Passport.rbac()
31
31
  async create(@Arg.body() <%=argv.resourceName%>: Dto<%=argv.resourceNameCapitalize%>Create): Promise<TableIdentity> {
32
32
  return (await this.scope.service.<%=argv.resourceName%>.create(<%=argv.resourceName%>)).id;
33
33
  }
34
34
 
35
35
  @Web.get('', { summary: $locale('<%=argv.resourceNameCapitalize%>Select') })
36
36
  @Api.body(Dto<%=argv.resourceNameCapitalize%>SelectRes)
37
- @Passport.systemAdmin()
37
+ @Passport.rbac()
38
38
  async select(@Arg.filter(Dto<%=argv.resourceNameCapitalize%>SelectReq) params: IQueryParams<Model<%=argv.resourceNameCapitalize%>>): Promise<Dto<%=argv.resourceNameCapitalize%>SelectRes> {
39
39
  return await this.scope.service.<%=argv.resourceName%>.select(params);
40
40
  }
41
41
 
42
42
  @Web.get(':id', { summary: $locale('<%=argv.resourceNameCapitalize%>View') })
43
43
  @Api.body(v.optional(), v.object(Dto<%=argv.resourceNameCapitalize%>View))
44
- @Passport.systemAdmin()
44
+ @Passport.rbac()
45
45
  async view(@Arg.param('id', v.tableIdentity()) id: TableIdentity): Promise<Dto<%=argv.resourceNameCapitalize%>View | undefined> {
46
46
  return await this.scope.service.<%=argv.resourceName%>.view(id);
47
47
  }
48
48
 
49
49
  @Web.patch(':id', { summary: $locale('<%=argv.resourceNameCapitalize%>Update') })
50
50
  @Api.body(z.null())
51
- @Passport.systemAdmin()
51
+ @Passport.rbac()
52
52
  async update(@Arg.param('id', v.tableIdentity()) id: TableIdentity, @Arg.body() <%=argv.resourceName%>: Dto<%=argv.resourceNameCapitalize%>Update): Promise<void> {
53
53
  await this.scope.service.<%=argv.resourceName%>.update(id, <%=argv.resourceName%>);
54
54
  }
55
55
 
56
56
  @Web.delete(':id', { summary: $locale('<%=argv.resourceNameCapitalize%>Delete') })
57
57
  @Api.body(z.null())
58
- @Passport.systemAdmin()
58
+ @Passport.rbac()
59
59
  async delete(@Arg.param('id', v.tableIdentity()) id: TableIdentity): Promise<void> {
60
60
  await this.scope.service.<%=argv.resourceName%>.delete(id);
61
61
  }
62
62
 
63
- @Web.post('bulk/delete', { summary: $locale('BulkDelete') })
63
+ @Web.post('bulk/delete', { summary: $locale('<%=argv.resourceNameCapitalize%>DeleteBulk') })
64
64
  @Api.body(z.null())
65
- @Passport.systemAdmin()
65
+ @Passport.rbac({ actionInherit: 'delete' })
66
66
  async deleteBulk(@Arg.body() command: Dto<%=argv.resourceNameCapitalize%>DeleteBulk): Promise<void> {
67
67
  await this.scope.service.<%=argv.resourceName%>.deleteBulk(command.ids);
68
68
  }
@@ -1,12 +1,74 @@
1
1
  import type { Dto<%=argv.resourceNameCapitalize%>Create, Dto<%=argv.resourceNameCapitalize%>SelectRes, Dto<%=argv.resourceNameCapitalize%>Update, Entity<%=argv.resourceNameCapitalize%> } from 'vona-module-<%=argv.moduleInfo.relativeName%>';
2
+
2
3
  import assert from 'node:assert';
3
4
  import { describe, it } from 'node:test';
5
+ import { appMetadata } from 'vona';
4
6
  import { app } from 'vona-mock';
7
+ import { SymbolOpenApiOptions } from 'vona-module-a-openapiutils';
8
+
9
+ describe('<%=argv.resourceName%>.test.ts', { concurrency: false }, () => {
10
+ it('action:<%=argv.resourceName%>:openapiMetadata', async () => {
11
+ await app.bean.executor.mockCtx(async () => {
12
+ const controllerBeanFullName = '<%=argv.moduleInfo.relativeName%>.controller.<%=argv.resourceName%>';
13
+ const controller = app.bean.onion.controller
14
+ .getOnionsEnabledCached()
15
+ .find(item => item.beanOptions.beanFullName === controllerBeanFullName)
16
+ ?.beanOptions.beanClass;
17
+ if (!controller) throw new Error(`${controllerBeanFullName} not found`);
18
+
19
+ const controllerMetadata = appMetadata.getMetadata<any>(
20
+ SymbolOpenApiOptions,
21
+ controller,
22
+ );
23
+ assert.equal(
24
+ controllerMetadata?.summary?.toString(),
25
+ '<%=argv.moduleInfo.relativeName%>::<%=argv.resourceNameCapitalize%>Controller',
26
+ );
27
+
28
+ const expectedSummaries = {
29
+ create: ['post', '<%=argv.moduleActionPathRaw%>', 'Create <%=argv.resourceNameCapitalize%>'],
30
+ select: ['get', '<%=argv.moduleActionPathRaw%>', 'Query <%=argv.resourceNameCapitalize%> List'],
31
+ view: ['get', '<%=argv.moduleActionPathRaw%>/{id}', 'View <%=argv.resourceNameCapitalize%>'],
32
+ update: ['patch', '<%=argv.moduleActionPathRaw%>/{id}', 'Update <%=argv.resourceNameCapitalize%>'],
33
+ delete: ['delete', '<%=argv.moduleActionPathRaw%>/{id}', 'Delete <%=argv.resourceNameCapitalize%>'],
34
+ deleteBulk: ['post', '<%=argv.moduleActionPathRaw%>/bulk/delete', 'Bulk Delete <%=argv.resourceNameCapitalize%>'],
35
+ } as const;
36
+ for (const [action, [method, path, summary]] of Object.entries(expectedSummaries)) {
37
+ const apiJson = await app.bean.openapi.generateJsonOfControllerAction(
38
+ controller,
39
+ action,
40
+ 'V31',
41
+ );
42
+ const operation = (apiJson.paths as any)?.[path]?.[method];
43
+ assert.ok(operation, `${action} operation is missing`);
44
+ assert.equal(operation.summary?.toJSON(), summary);
45
+ }
46
+ });
47
+ });
48
+
49
+ it('action:<%=argv.resourceName%>:rbacCatalog', async () => {
50
+ await app.bean.executor.mockCtx(async () => {
51
+ const controllerBeanFullName = '<%=argv.moduleInfo.relativeName%>.controller.<%=argv.resourceName%>';
52
+ const catalog = app.bean.rbacCatalog.getCatalog();
53
+ const expectedInherits = {
54
+ create: undefined,
55
+ select: undefined,
56
+ view: undefined,
57
+ update: undefined,
58
+ delete: undefined,
59
+ deleteBulk: `${controllerBeanFullName}#delete`,
60
+ } as const;
61
+ for (const [action, actionInheritKey] of Object.entries(expectedInherits)) {
62
+ const descriptor = catalog.get(`${controllerBeanFullName}#${action}`);
63
+ assert.ok(descriptor, `${action} RBAC action is missing`);
64
+ assert.equal(descriptor.actionInheritKey, actionInheritKey);
65
+ assert.equal(descriptor.options.dataScope, undefined);
66
+ }
67
+ });
68
+ });
5
69
 
6
- describe('<%=argv.resourceName%>.test.ts', () => {
7
70
  it('action:<%=argv.resourceName%>', async () => {
8
71
  await app.bean.executor.mockCtx(async () => {
9
- // data
10
72
  const data: Dto<%=argv.resourceNameCapitalize%>Create = {
11
73
  name: '__Tom__',
12
74
  description: 'This is a test',
@@ -15,48 +77,40 @@ describe('<%=argv.resourceName%>.test.ts', () => {
15
77
  name: '__TomNew__',
16
78
  description: 'This is a test',
17
79
  };
18
- // role-less authenticated users cannot access generated admin actions
80
+ let <%=argv.resourceName%>Id: Entity<%=argv.resourceNameCapitalize%>['id'] | undefined;
81
+ // sign in as an unrestricted test identity
19
82
  await app.bean.passport.signinMock();
20
83
  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));
84
+ // create
85
+ <%=argv.resourceName%>Id = await app.bean.executor.performAction('post', '<%=argv.moduleActionPathRaw%>', { body: data });
86
+ assert.equal(!!<%=argv.resourceName%>Id, true);
87
+ // findMany
88
+ const selectRes: Dto<%=argv.resourceNameCapitalize%>SelectRes = await app.bean.executor.performAction('get', '<%=argv.moduleActionPathRaw%>');
89
+ assert.equal(selectRes.list.findIndex(item => item.name === data.name) > -1, true);
90
+ // update
91
+ const updateRes = await app.bean.executor.performAction('patch', '<%=argv.moduleActionPathRaw%>/:id', {
92
+ params: { id: <%=argv.resourceName%>Id },
93
+ body: dataUpdate,
94
+ });
95
+ assert.equal(updateRes, null);
96
+ // findOne
97
+ let <%=argv.resourceName%>: Entity<%=argv.resourceNameCapitalize%> = await app.bean.executor.performAction('get', '<%=argv.moduleActionPathRaw%>/:id', { params: { id: <%=argv.resourceName%>Id } });
98
+ assert.equal(<%=argv.resourceName%>.name, dataUpdate.name);
99
+ // delete
100
+ const deleteRes = await app.bean.executor.performAction('delete', '<%=argv.moduleActionPathRaw%>/:id', { params: { id: <%=argv.resourceName%>.id } });
101
+ assert.equal(deleteRes, null);
102
+ // findOne
103
+ <%=argv.resourceName%> = await app.bean.executor.performAction('get', '<%=argv.moduleActionPathRaw%>/:id', { params: { id: <%=argv.resourceName%>.id } });
104
+ assert.equal(<%=argv.resourceName%>, undefined);
32
105
  } finally {
106
+ if (<%=argv.resourceName%>Id) {
107
+ await app.scope('<%=argv.moduleInfo.relativeName%>').model.<%=argv.resourceName%>.deleteById(
108
+ <%=argv.resourceName%>Id,
109
+ { disableDeleted: true },
110
+ );
111
+ }
33
112
  await app.bean.passport.signout();
34
113
  }
35
- // login as system admin
36
- await app.bean.passport.signinMock();
37
- // create
38
- const <%=argv.resourceName%>Id = await app.bean.executor.performAction('post', '<%=argv.moduleActionPathRaw%>', { body: data });
39
- assert.equal(!!<%=argv.resourceName%>Id, true);
40
- // findMany
41
- const selectRes: Dto<%=argv.resourceNameCapitalize%>SelectRes = await app.bean.executor.performAction('get', '<%=argv.moduleActionPathRaw%>');
42
- assert.equal(selectRes.list.findIndex(item => item.name === data.name) > -1, true);
43
- // update
44
- const updateRes = await app.bean.executor.performAction('patch', '<%=argv.moduleActionPathRaw%>/:id', {
45
- params: { id: <%=argv.resourceName%>Id },
46
- body: dataUpdate,
47
- });
48
- assert.equal(updateRes, null);
49
- // findOne
50
- let <%=argv.resourceName%>: Entity<%=argv.resourceNameCapitalize%> = await app.bean.executor.performAction('get', '<%=argv.moduleActionPathRaw%>/:id', { params: { id: <%=argv.resourceName%>Id } });
51
- assert.equal(<%=argv.resourceName%>.name, dataUpdate.name);
52
- // delete
53
- const deleteRes = await app.bean.executor.performAction('delete', '<%=argv.moduleActionPathRaw%>/:id', { params: { id: <%=argv.resourceName%>.id } });
54
- assert.equal(deleteRes, null);
55
- // findOne
56
- <%=argv.resourceName%> = await app.bean.executor.performAction('get', '<%=argv.moduleActionPathRaw%>/:id', { params: { id: <%=argv.resourceName%>.id } });
57
- assert.equal(<%=argv.resourceName%>, undefined);
58
- // logout
59
- await app.bean.passport.signout();
60
114
  });
61
115
  });
62
116
  });
@@ -9,7 +9,6 @@ const __resources = {
9
9
  Name: 'Name',
10
10
  Description: 'Description',
11
11
  Operations: 'Operations',
12
- BulkDelete: 'Bulk Delete',
13
12
  };
14
13
 
15
14
  declare module '@cabloy/cli' {
@@ -40,6 +39,7 @@ export default metadataCustomSnippet({
40
39
  [`${resource}View`]: `View ${resource}`,
41
40
  [`${resource}Update`]: `Update ${resource}`,
42
41
  [`${resource}Delete`]: `Delete ${resource}`,
42
+ [`${resource}DeleteBulk`]: `Bulk Delete ${resource}`,
43
43
  };
44
44
  return locale_transform({ ast, argv, resources });
45
45
  },
@@ -7,7 +7,6 @@ const __resources = {
7
7
  Name: '名称',
8
8
  Description: '描述',
9
9
  Operations: '操作',
10
- BulkDelete: '批量删除',
11
10
  };
12
11
 
13
12
  export default metadataCustomSnippet({
@@ -23,6 +22,7 @@ export default metadataCustomSnippet({
23
22
  [`${resource}View`]: `查看${resource}`,
24
23
  [`${resource}Update`]: `更新${resource}`,
25
24
  [`${resource}Delete`]: `删除${resource}`,
25
+ [`${resource}DeleteBulk`]: `批量删除${resource}`,
26
26
  };
27
27
  return locale_transform({ ast, argv, resources });
28
28
  },
@@ -7,7 +7,6 @@ const __resources = {
7
7
  Name: 'Name',
8
8
  Description: 'Description',
9
9
  Operations: 'Operations',
10
- BulkDelete: 'Bulk Delete',
11
10
  };
12
11
  export default metadataCustomSnippet({
13
12
  file: 'src/config/locale/en-us.ts',
@@ -30,6 +29,7 @@ export default metadataCustomSnippet({
30
29
  [`${resource}View`]: `View ${resource}`,
31
30
  [`${resource}Update`]: `Update ${resource}`,
32
31
  [`${resource}Delete`]: `Delete ${resource}`,
32
+ [`${resource}DeleteBulk`]: `Bulk Delete ${resource}`,
33
33
  };
34
34
  return locale_transform({ ast, argv, resources });
35
35
  },
@@ -5,7 +5,6 @@ const __resources = {
5
5
  Name: '名称',
6
6
  Description: '描述',
7
7
  Operations: '操作',
8
- BulkDelete: '批量删除',
9
8
  };
10
9
  export default metadataCustomSnippet({
11
10
  file: 'src/config/locale/zh-cn.ts',
@@ -20,6 +19,7 @@ export default metadataCustomSnippet({
20
19
  [`${resource}View`]: `查看${resource}`,
21
20
  [`${resource}Update`]: `更新${resource}`,
22
21
  [`${resource}Delete`]: `删除${resource}`,
22
+ [`${resource}DeleteBulk`]: `批量删除${resource}`,
23
23
  };
24
24
  return locale_transform({ ast, argv, resources });
25
25
  },
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "vona-cli-set-api",
3
- "version": "1.1.149",
4
- "gitHead": "fa2e21535d7220ab079e8ba9028266d9aa6f5a98",
3
+ "version": "1.1.150",
4
+ "gitHead": "e2843e4cab02a08051be465a8993919c9a37b5f5",
5
5
  "description": "vona cli-set-api",
6
6
  "keywords": [
7
7
  "framework",