vona-cli-set-api 1.1.127 → 1.1.129

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.
@@ -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);
package/dist/index.js CHANGED
@@ -991,15 +991,12 @@ class CliBinTest extends BeanCliBase {
991
991
  }
992
992
  args = args.concat([getImportEsm(), testFile, projectPath, (!!argv.coverage).toString(), patterns.join(',')]);
993
993
  // args = args.concat(['--experimental-transform-types', getImportEsm(), testFile, projectPath, (!!argv.coverage).toString(), patterns.join(',')]);
994
- // ignore error special in windows
995
- await catchError(() => {
996
- return this.helper.spawnExe({
997
- cmd: 'node',
998
- args,
999
- options: {
1000
- cwd: projectPath
1001
- }
1002
- });
994
+ await this.helper.spawnExe({
995
+ cmd: 'node',
996
+ args,
997
+ options: {
998
+ cwd: projectPath
999
+ }
1003
1000
  });
1004
1001
  }
1005
1002
  async _outputCoverageReportViewer(projectPath) {
@@ -59,9 +59,27 @@ async function testRun(projectPath, coverage, patterns) {
59
59
  ];
60
60
  // app
61
61
  const app = await createGeneralApp(projectPath);
62
- // concurrency
63
- const concurrency = await prepareConcurrency(app);
64
- return new Promise(resolve => {
62
+ let testError;
63
+ let closeError;
64
+ let closePromise;
65
+ const closeApplication = async () => {
66
+ const [_, error] = await catchError(() => app.close());
67
+ closeError = error;
68
+ // handles
69
+ if (process.env.TEST_WHYISNODERUNNING === 'true') {
70
+ await sleep(2000);
71
+ const handles = process._getActiveHandles();
72
+ if (handles.length > 3) {
73
+ whyIsNodeRunning();
74
+ }
75
+ }
76
+ };
77
+ const closeApplicationOnce = () => {
78
+ return (closePromise ??= closeApplication());
79
+ };
80
+ try {
81
+ // concurrency
82
+ const concurrency = await prepareConcurrency(app);
65
83
  const testStream = run({
66
84
  isolation: 'none',
67
85
  concurrency,
@@ -76,27 +94,12 @@ async function testRun(projectPath, coverage, patterns) {
76
94
  .on('test:coverage', data => {
77
95
  outputCoverageReport(data.summary.totals);
78
96
  })
79
- .on('test:summary', async () => {
80
- resolve(undefined);
81
- })
82
- .on('test:pass', async (t) => {
97
+ .on('test:pass', t => {
83
98
  if (t.name === '---done---') {
84
- const [_, err] = await catchError(() => {
85
- return app.close();
86
- });
87
- if (err) {
88
- console.error(err);
89
- }
90
- // handles
91
- if (process.env.TEST_WHYISNODERUNNING === 'true') {
92
- await sleep(2000);
93
- const handles = process._getActiveHandles();
94
- if (handles.length > 3) {
95
- whyIsNodeRunning();
96
- }
97
- }
99
+ void closeApplicationOnce();
98
100
  }
99
101
  });
102
+ const summaryPromise = waitForTestSummary(testStream);
100
103
  if (coverage) {
101
104
  const reporterDir = path.join(projectPath, 'coverage');
102
105
  fse.ensureDirSync(reporterDir);
@@ -106,6 +109,32 @@ async function testRun(projectPath, coverage, patterns) {
106
109
  else {
107
110
  testStream.compose(spec).pipe(process.stdout);
108
111
  }
112
+ const summarySuccess = await summaryPromise;
113
+ if (!summarySuccess) {
114
+ throw new Error('node:test reported failed tests');
115
+ }
116
+ }
117
+ catch (error) {
118
+ testError = error;
119
+ }
120
+ finally {
121
+ await closeApplicationOnce();
122
+ }
123
+ if (testError) {
124
+ if (closeError)
125
+ console.error(closeError);
126
+ throw toError(testError);
127
+ }
128
+ if (closeError) {
129
+ throw toError(closeError);
130
+ }
131
+ }
132
+ function toError(error) {
133
+ return error instanceof Error ? error : new Error(String(error));
134
+ }
135
+ function waitForTestSummary(testStream) {
136
+ return new Promise((resolve, reject) => {
137
+ testStream.once('test:summary', summary => resolve(summary.success)).once('error', reject);
109
138
  });
110
139
  }
111
140
  async function prepareConcurrency(app) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "vona-cli-set-api",
3
- "version": "1.1.127",
4
- "gitHead": "f7296d0004da7e5c173bc9149d209c2e57cab1b8",
3
+ "version": "1.1.129",
4
+ "gitHead": "cd41414765dfc186953f59e31d654ecb8ab2ed41",
5
5
  "description": "vona cli-set-api",
6
6
  "keywords": [
7
7
  "framework",