cabloy 5.1.103 → 5.1.105
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/.claude/skills/cabloy-backend-scaffold/references/follow-up-checklist.md +1 -1
- package/CHANGELOG.md +27 -0
- package/CLAUDE.md +1 -0
- package/cabloy-docs/backend/controller-aop-guide.md +2 -2
- package/cabloy-docs/backend/field-indexes.md +10 -1
- package/cabloy-docs/backend/menu-guide.md +30 -0
- package/cabloy-docs/frontend/a-model-under-the-hood.md +26 -4
- package/cabloy-docs/frontend/model-architecture.md +1 -1
- package/cabloy-docs/frontend/model-state-guide.md +122 -16
- package/cabloy-docs/frontend/ssr-init-data.md +12 -1
- package/cabloy-docs/frontend/use-state-data-best-practices.md +23 -0
- package/package.json +1 -1
- package/vona/packages-vona/vona/package.json +1 -1
- package/vona/pnpm-lock.yaml +31 -31
- package/vona/src/suite/a-home/modules/home-user/src/bean/eventListener.activate.ts +5 -4
- package/vona/src/suite/a-home/modules/home-user/src/bean/meta.version.ts +10 -6
- package/vona/src/suite/a-home/modules/home-user/src/config/config.ts +1 -1
- package/vona/src/suite/a-home/modules/home-user/src/controller/passport.ts +7 -7
- package/vona/src/suite/a-home/modules/home-user/src/entity/role.ts +4 -0
- package/vona/src/suite/a-home/modules/home-user/src/service/passportAdapter.ts +2 -2
- package/vona/src/suite/a-home/modules/home-user/src/service/roleAdapter.ts +5 -5
- package/vona/src/suite/a-training/modules/training-record/src/bean/ssrMenu.record.ts +1 -0
- package/vona/src/suite/a-training/modules/training-student/src/bean/ssrMenu.student.ts +1 -0
- package/vona/src/suite/cabloy-basic/modules/basic-siteadmin/src/bean/ssrMenu.home.ts +1 -0
- package/vona/src/suite/cabloy-basic/modules/basic-siteadmin/src/bean/ssrSite.admin.ts +5 -1
- package/vona/src/suite/cabloy-basic/modules/basic-siteweb/src/bean/ssrSite.web.ts +5 -1
- package/vona/src/suite-vendor/a-auth/modules/a-auth/package.json +1 -1
- package/vona/src/suite-vendor/a-auth/modules/a-auth/src/service/auth.ts +5 -1
- package/vona/src/suite-vendor/a-auth/package.json +1 -1
- package/vona/src/suite-vendor/a-cabloy/modules/a-ssr/cli/ssrSite/boilerplate/{{sceneName}}.{{beanName}}.ts_ +5 -3
- 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/bean/event.retrieveMenusSite.ts +2 -2
- package/vona/src/suite-vendor/a-cabloy/modules/a-ssr/src/lib/beanSsrSiteBase.ts +33 -8
- package/vona/src/suite-vendor/a-cabloy/modules/a-ssr/src/service/ssr.ts +46 -16
- package/vona/src/suite-vendor/a-cabloy/modules/a-ssr/src/service/ssrHandler.ts +2 -4
- package/vona/src/suite-vendor/a-cabloy/modules/a-ssr/src/types/env.ts +1 -0
- package/vona/src/suite-vendor/a-cabloy/modules/a-ssr/src/types/ssrMenu.ts +10 -1
- package/vona/src/suite-vendor/a-cabloy/modules/a-ssr/src/types/ssrSite.ts +3 -1
- package/vona/src/suite-vendor/a-cabloy/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-permission/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-permission/src/.metadata/index.ts +18 -2
- package/vona/src/suite-vendor/a-vona/modules/a-permission/src/bean/bean.permission.ts +6 -1
- package/vona/src/suite-vendor/a-vona/modules/a-permission/src/bean/event.retrievePermissionAction.ts +16 -0
- 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/bean/bean.passport.ts +2 -2
- package/vona/src/suite-vendor/a-vona/modules/a-user/src/lib/passport.ts +7 -4
- package/vona/src/suite-vendor/a-vona/modules/a-user/src/types/passport.ts +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-user/src/types/role.ts +3 -1
- package/vona/src/suite-vendor/a-vona/package.json +1 -1
- package/zova/env/.env.cabloyBasicAdmin +2 -0
- package/zova/env/.env.cabloyBasicWeb +2 -0
- package/zova/packages-utils/zova-jsx/package.json +2 -2
- package/zova/packages-zova/zova/package.json +3 -3
- package/zova/packages-zova/zova-core/package.json +1 -1
- package/zova/packages-zova/zova-core/src/core/sys/sys.ts +3 -0
- package/zova/packages-zova/zova-core/src/types/utils/env.ts +1 -0
- package/zova/src/suite/a-home/modules/home-api/src/api/homeUserPassport.ts +19 -19
- package/zova/src/suite/a-home/modules/home-api/src/api/openapi/types.ts +11 -10
- package/zova/src/suite/a-home/modules/home-api/src/apiSchema/homeUserPassport.ts +5 -5
- package/zova/src/suite/a-home/modules/home-base/src/.metadata/page/errorAccessDenied.ts +9 -0
- package/zova/src/suite/a-home/modules/home-base/src/page/errorAccessDenied/controller.tsx +40 -0
- package/zova/src/suite/a-home/modules/home-base/src/routes.ts +9 -0
- package/zova/src/suite/a-home/modules/home-base/src/service/routerGuards.ts +21 -12
- package/zova/src/suite/a-home/modules/home-passport/src/model/passport.ts +7 -1
- package/zova/src/suite/a-home/modules/home-passport/src/monkey.ts +17 -3
- package/zova/src/suite-vendor/a-zova/modules/a-model/package.json +1 -1
- package/zova/src/suite-vendor/a-zova/modules/a-model/src/bean/bean.model/bean.model.useState.ts +6 -6
- package/zova/src/suite-vendor/a-zova/modules/a-model/src/bean/bean.model/bean.model.useStateGeneral.ts +5 -5
- package/zova/src/suite-vendor/a-zova/modules/a-model/src/monkey.ts +11 -2
- package/zova/src/suite-vendor/a-zova/modules/a-model/src/types/query.ts +1 -1
- package/zova/src/suite-vendor/a-zova/modules/a-router/package.json +1 -1
- package/zova/src/suite-vendor/a-zova/modules/a-router/src/monkeySys.ts +10 -1
- package/zova/src/suite-vendor/a-zova/modules/a-router/src/types/router.ts +1 -0
- package/zova/src/suite-vendor/a-zova/modules/a-routertabs/package.json +1 -1
- package/zova/src/suite-vendor/a-zova/modules/a-routertabs/src/model/tabs.ts +4 -4
- package/zova/src/suite-vendor/a-zova/modules/a-zova/package.json +2 -2
- package/zova/src/suite-vendor/a-zova/package.json +5 -5
|
@@ -17,10 +17,11 @@ export class EventListenerActivate
|
|
|
17
17
|
next: NextEvent<TypeEventData, TypeEventResult>,
|
|
18
18
|
): Promise<TypeEventResult> {
|
|
19
19
|
const user = data as IUser;
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
await this.
|
|
20
|
+
const roleRegisteredUser = await this.scope.model.role.get({ name: 'registeredUser' });
|
|
21
|
+
await this.bean.role.addUserId(roleRegisteredUser!.id, user.id);
|
|
22
|
+
if (user.name === 'admin' && !this.scope.config.disableBootstrapSystemAdmin) {
|
|
23
|
+
const roleSystemAdmin = await this.scope.model.role.get({ name: 'systemAdmin' });
|
|
24
|
+
await this.bean.role.addUserId(roleSystemAdmin!.id, user.id);
|
|
24
25
|
}
|
|
25
26
|
// next
|
|
26
27
|
return next();
|
|
@@ -18,6 +18,7 @@ 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.json(entityRole.siteIds).comment(entityRole.$comment.siteIds);
|
|
21
22
|
});
|
|
22
23
|
// homeUser
|
|
23
24
|
const entityUser = this.scope.entity.user;
|
|
@@ -47,12 +48,15 @@ export class MetaVersion extends BeanBase implements IMetaVersionUpdate, IMetaVe
|
|
|
47
48
|
|
|
48
49
|
async init(options: IMetaVersionInitOptions) {
|
|
49
50
|
if (options.version === 1) {
|
|
50
|
-
//
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
51
|
+
// roles
|
|
52
|
+
await this.scope.model.role.insert({
|
|
53
|
+
name: 'registeredUser',
|
|
54
|
+
siteIds: ['web'],
|
|
55
|
+
});
|
|
56
|
+
await this.scope.model.role.insert({
|
|
57
|
+
name: 'systemAdmin',
|
|
58
|
+
siteIds: ['web', 'admin'],
|
|
59
|
+
});
|
|
56
60
|
// user: admin
|
|
57
61
|
if (!this.scope.config.disableUserAdmin) {
|
|
58
62
|
await this.bean.auth.authenticate('auth-simple:simple', {
|
|
@@ -111,6 +111,13 @@ export class ControllerPassport extends BeanBase {
|
|
|
111
111
|
return this._combineDtoPassportJwt(jwt);
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
+
@Web.post('refreshAuthToken')
|
|
115
|
+
@Passport.public()
|
|
116
|
+
@Api.body(v.object(DtoJwtToken))
|
|
117
|
+
async refreshAuthToken(@Arg.body('refreshToken') refreshToken: string): Promise<DtoJwtToken> {
|
|
118
|
+
return await this.bean.passport.refreshAuthToken(refreshToken);
|
|
119
|
+
}
|
|
120
|
+
|
|
114
121
|
@Web.post('createPassportJwtFromOauthCode')
|
|
115
122
|
@Passport.public()
|
|
116
123
|
@Api.body(v.object(DtoPassportJwt))
|
|
@@ -119,13 +126,6 @@ export class ControllerPassport extends BeanBase {
|
|
|
119
126
|
return this._combineDtoPassportJwt(jwt);
|
|
120
127
|
}
|
|
121
128
|
|
|
122
|
-
@Web.post('refreshAuthToken')
|
|
123
|
-
@Passport.public()
|
|
124
|
-
@Api.body(v.object(DtoJwtToken))
|
|
125
|
-
async refreshAuthToken(@Arg.body('refreshToken') refreshToken: string): Promise<DtoJwtToken> {
|
|
126
|
-
return await this.bean.passport.refreshAuthToken(refreshToken);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
129
|
@Web.post('createTempAuthToken')
|
|
130
130
|
@Api.body(z.string())
|
|
131
131
|
async createTempAuthToken(@Arg.query('path', v.optional()) path?: string): Promise<string> {
|
|
@@ -3,6 +3,7 @@ import type { IRole } from 'vona-module-a-user';
|
|
|
3
3
|
|
|
4
4
|
import { Api, v } from 'vona-module-a-openapiutils';
|
|
5
5
|
import { Entity, EntityBase } from 'vona-module-a-orm';
|
|
6
|
+
import z from 'zod';
|
|
6
7
|
|
|
7
8
|
import { $locale } from '../.metadata/locales.ts';
|
|
8
9
|
|
|
@@ -12,4 +13,7 @@ export interface IEntityOptionsRole extends IDecoratorEntityOptions {}
|
|
|
12
13
|
export class EntityRole extends EntityBase implements IRole {
|
|
13
14
|
@Api.field(v.title($locale('RoleName')))
|
|
14
15
|
name: string;
|
|
16
|
+
|
|
17
|
+
@Api.field(v.array(z.string()))
|
|
18
|
+
siteIds: string[];
|
|
15
19
|
}
|
|
@@ -7,9 +7,9 @@ import type { IPayloadDataOfPassport } from '../types/jwt.ts';
|
|
|
7
7
|
|
|
8
8
|
@Service()
|
|
9
9
|
export class ServicePassportAdapter extends BeanBase implements IPassportAdapter {
|
|
10
|
-
async
|
|
10
|
+
async isSystemAdmin(passport: IPassport | undefined): Promise<boolean> {
|
|
11
11
|
if (!passport) return false;
|
|
12
|
-
if (passport.roles?.some(role => role.name === '
|
|
12
|
+
if (passport.roles?.some(role => role.name === 'systemAdmin')) return true;
|
|
13
13
|
return false;
|
|
14
14
|
}
|
|
15
15
|
|
|
@@ -20,10 +20,10 @@ export class ServiceRoleAdapter extends BeanBase implements IRoleAdapter {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
async addUserId(id: TableIdentity, userId: TableIdentity): Promise<TableIdentity> {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
roleId: id
|
|
26
|
-
}
|
|
27
|
-
return
|
|
23
|
+
let roleUser = await this.scope.model.roleUser.get({ userId, roleId: id });
|
|
24
|
+
if (!roleUser) {
|
|
25
|
+
roleUser = await this.scope.model.roleUser.insert({ userId, roleId: id });
|
|
26
|
+
}
|
|
27
|
+
return roleUser.id;
|
|
28
28
|
}
|
|
29
29
|
}
|
|
@@ -4,6 +4,10 @@ import type { IIconRecord, IPagePathRecord } from 'zova-rest-cabloy-basic-admin'
|
|
|
4
4
|
import { BeanSsrSiteBase, SsrSite } from 'vona-module-a-ssr';
|
|
5
5
|
|
|
6
6
|
declare module 'vona-module-a-ssr' {
|
|
7
|
+
export interface ISsrSiteIdRecord {
|
|
8
|
+
admin: never;
|
|
9
|
+
}
|
|
10
|
+
|
|
7
11
|
export interface ISsrSitePublicPathRecord {
|
|
8
12
|
admin: never;
|
|
9
13
|
}
|
|
@@ -20,10 +24,10 @@ export interface ISsrSiteOptionsAdmin extends IDecoratorSsrSiteOptions<
|
|
|
20
24
|
> {}
|
|
21
25
|
|
|
22
26
|
@SsrSite<ISsrSiteOptionsAdmin>({
|
|
27
|
+
siteId: 'admin',
|
|
23
28
|
publicPath: 'admin',
|
|
24
29
|
bundlePath: 'ssr-cabloyBasicAdmin-5.0.0',
|
|
25
30
|
diagnostics: {
|
|
26
|
-
siteName: 'admin',
|
|
27
31
|
buildCommand: 'npm run build:zova:admin',
|
|
28
32
|
},
|
|
29
33
|
})
|
|
@@ -4,6 +4,10 @@ import type { IIconRecord, IPagePathRecord } from 'zova-rest-cabloy-basic-web';
|
|
|
4
4
|
import { BeanSsrSiteBase, SsrSite } from 'vona-module-a-ssr';
|
|
5
5
|
|
|
6
6
|
declare module 'vona-module-a-ssr' {
|
|
7
|
+
export interface ISsrSiteIdRecord {
|
|
8
|
+
web: never;
|
|
9
|
+
}
|
|
10
|
+
|
|
7
11
|
export interface ISsrSitePublicPathRecord {
|
|
8
12
|
'': never;
|
|
9
13
|
}
|
|
@@ -20,10 +24,10 @@ export interface ISsrSiteOptionsWeb extends IDecoratorSsrSiteOptions<
|
|
|
20
24
|
> {}
|
|
21
25
|
|
|
22
26
|
@SsrSite<ISsrSiteOptionsWeb>({
|
|
27
|
+
siteId: 'web',
|
|
23
28
|
publicPath: '',
|
|
24
29
|
bundlePath: 'ssr-cabloyBasicWeb-5.0.0',
|
|
25
30
|
diagnostics: {
|
|
26
|
-
siteName: 'web',
|
|
27
31
|
buildCommand: 'npm run build:zova:web',
|
|
28
32
|
},
|
|
29
33
|
})
|
|
@@ -60,7 +60,7 @@ export class ServiceAuth extends BeanBase {
|
|
|
60
60
|
state?: IAuthenticateStrategyState,
|
|
61
61
|
): Promise<IPassport> {
|
|
62
62
|
// event: issuePassport
|
|
63
|
-
|
|
63
|
+
const passport = await this.scope.event.issuePassport.emit(
|
|
64
64
|
{ profileUser, entityAuthProvider, clientOptions, state },
|
|
65
65
|
async ({ profileUser, entityAuthProvider, clientOptions, state }) => {
|
|
66
66
|
return await this._issuePassportInner(
|
|
@@ -71,6 +71,10 @@ export class ServiceAuth extends BeanBase {
|
|
|
71
71
|
);
|
|
72
72
|
},
|
|
73
73
|
);
|
|
74
|
+
if (passport.user) {
|
|
75
|
+
passport.roles = await this.bean.role.findAllByUserId(passport.user.id);
|
|
76
|
+
}
|
|
77
|
+
return passport;
|
|
74
78
|
}
|
|
75
79
|
|
|
76
80
|
private async _issuePassportInner(
|
|
@@ -4,6 +4,10 @@ import type { IIconRecord, IPagePathRecord } from '<%=argv.ssrSiteRestNpm%>';
|
|
|
4
4
|
import { BeanSsrSiteBase, SsrSite } from 'vona-module-a-ssr';
|
|
5
5
|
|
|
6
6
|
declare module 'vona-module-a-ssr' {
|
|
7
|
+
export interface ISsrSiteIdRecord {
|
|
8
|
+
some: never;
|
|
9
|
+
}
|
|
10
|
+
|
|
7
11
|
export interface ISsrSitePublicPathRecord {
|
|
8
12
|
some: never;
|
|
9
13
|
}
|
|
@@ -16,10 +20,8 @@ export interface ISsrSiteOptions<%=argv.beanNameCapitalize%>PagesData {}
|
|
|
16
20
|
export interface ISsrSiteOptions<%=argv.beanNameCapitalize%> extends IDecoratorSsrSiteOptions<ISsrSiteOptions<%=argv.beanNameCapitalize%>Pages, ISsrSiteOptions<%=argv.beanNameCapitalize%>PagesData, IIconRecord> {}
|
|
17
21
|
|
|
18
22
|
@SsrSite<ISsrSiteOptions<%=argv.beanNameCapitalize%>>({
|
|
23
|
+
siteId: 'some',
|
|
19
24
|
publicPath: 'some',
|
|
20
25
|
bundlePath: 'front-bundle-path',
|
|
21
|
-
diagnostics: {
|
|
22
|
-
siteName: 'some',
|
|
23
|
-
},
|
|
24
26
|
})
|
|
25
27
|
export class SsrSite<%=argv.beanNameCapitalize%> extends BeanSsrSiteBase<ISsrSiteOptions<%=argv.beanNameCapitalize%>> {}
|
|
@@ -3,14 +3,14 @@ import type { ILocaleRecord } from 'vona';
|
|
|
3
3
|
import { BeanEventBase, Event } from 'vona-module-a-event';
|
|
4
4
|
|
|
5
5
|
import type { BeanSsrSiteBase } from '../lib/beanSsrSiteBase.ts';
|
|
6
|
-
import type {
|
|
6
|
+
import type { ISsrMenusPrepared } from '../types/ssrMenu.ts';
|
|
7
7
|
|
|
8
8
|
export interface TypeEventRetrieveMenusSiteData {
|
|
9
9
|
ssrSite: BeanSsrSiteBase;
|
|
10
10
|
locale: keyof ILocaleRecord;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
export type TypeEventRetrieveMenusSiteResult =
|
|
13
|
+
export type TypeEventRetrieveMenusSiteResult = ISsrMenusPrepared;
|
|
14
14
|
|
|
15
15
|
@Event()
|
|
16
16
|
export class EventRetrieveMenusSite extends BeanEventBase<
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ZovaMetaMode } from '@cabloy/module-info';
|
|
2
2
|
import type { IParamsAndQuery } from '@cabloy/utils';
|
|
3
3
|
import type { ILocaleRecord } from 'vona';
|
|
4
|
-
import type { IMenuGroup
|
|
4
|
+
import type { IMenuGroup } from 'vona-module-a-menu';
|
|
5
5
|
import type { IOnionSlice } from 'vona-module-a-onion';
|
|
6
6
|
import type { TypeEventResolvePathData, TypeEventResolvePathResult } from 'vona-module-a-static';
|
|
7
7
|
|
|
@@ -16,7 +16,12 @@ import { BeanBase, cast, deepExtend, SymbolModuleName } from 'vona';
|
|
|
16
16
|
import { checkErrorJwtExpiredAndThrow } from 'vona-module-a-jwt';
|
|
17
17
|
|
|
18
18
|
import type { TypeEventRetrieveMenusResult } from '../bean/event.retrieveMenus.ts';
|
|
19
|
-
import type {
|
|
19
|
+
import type { TypeEventRetrieveMenusSiteResult } from '../bean/event.retrieveMenusSite.ts';
|
|
20
|
+
import type {
|
|
21
|
+
IDecoratorSsrMenuOptions,
|
|
22
|
+
ISsrMenuItemPrepared,
|
|
23
|
+
ISsrMenuRecord,
|
|
24
|
+
} from '../types/ssrMenu.ts';
|
|
20
25
|
import type { ISsrMenuGroupRecord } from '../types/ssrMenuGroup.ts';
|
|
21
26
|
import type {
|
|
22
27
|
IDecoratorSsrSiteOptions,
|
|
@@ -139,7 +144,15 @@ export class BeanSsrSiteBase<
|
|
|
139
144
|
}
|
|
140
145
|
|
|
141
146
|
async retrieveMenus(): Promise<TypeEventRetrieveMenusResult | undefined> {
|
|
142
|
-
|
|
147
|
+
const menusPrepared = await this._getMenusCache(this.ctx.locale);
|
|
148
|
+
if (!menusPrepared) return;
|
|
149
|
+
const menus = menusPrepared.menus
|
|
150
|
+
?.filter(menu => !menu.roles?.length || this.bean.passport.checkRoleName(menu.roles))
|
|
151
|
+
.map(menu => {
|
|
152
|
+
const { roles: _roles, ...menuPublic } = menu;
|
|
153
|
+
return menuPublic;
|
|
154
|
+
});
|
|
155
|
+
return { menus, groups: menusPrepared.groups };
|
|
143
156
|
}
|
|
144
157
|
|
|
145
158
|
public get siteOptions() {
|
|
@@ -168,6 +181,14 @@ export class BeanSsrSiteBase<
|
|
|
168
181
|
},
|
|
169
182
|
this.$scope.ssr.config.site.default,
|
|
170
183
|
onionOptions,
|
|
184
|
+
{
|
|
185
|
+
envServer: {
|
|
186
|
+
SITE_ID: onionOptions.siteId,
|
|
187
|
+
},
|
|
188
|
+
envClient: {
|
|
189
|
+
SITE_ID: onionOptions.siteId,
|
|
190
|
+
},
|
|
191
|
+
},
|
|
171
192
|
);
|
|
172
193
|
}
|
|
173
194
|
return this._siteOptions;
|
|
@@ -253,9 +274,11 @@ export class BeanSsrSiteBase<
|
|
|
253
274
|
};
|
|
254
275
|
}
|
|
255
276
|
|
|
256
|
-
private async _getMenusCache(
|
|
277
|
+
private async _getMenusCache(
|
|
278
|
+
locale: keyof ILocaleRecord,
|
|
279
|
+
): Promise<TypeEventRetrieveMenusSiteResult | undefined> {
|
|
257
280
|
if (!this.app.meta[SymbolCacheMenus]) this.app.meta[SymbolCacheMenus] = {};
|
|
258
|
-
const cacheMenus: Record<keyof ILocaleRecord,
|
|
281
|
+
const cacheMenus: Record<keyof ILocaleRecord, TypeEventRetrieveMenusSiteResult> =
|
|
259
282
|
this.app.meta[SymbolCacheMenus];
|
|
260
283
|
const beanFullName = this.$beanFullName;
|
|
261
284
|
const instanceName = this.ctx.instanceName;
|
|
@@ -268,7 +291,9 @@ export class BeanSsrSiteBase<
|
|
|
268
291
|
return cacheMenus[beanFullName][cacheKey];
|
|
269
292
|
}
|
|
270
293
|
|
|
271
|
-
private async _prepareMenus(
|
|
294
|
+
private async _prepareMenus(
|
|
295
|
+
locale: keyof ILocaleRecord,
|
|
296
|
+
): Promise<TypeEventRetrieveMenusSiteResult | undefined> {
|
|
272
297
|
return await this.$scope.ssr.event.retrieveMenusSite.emit(
|
|
273
298
|
{ ssrSite: this, locale },
|
|
274
299
|
async () => {
|
|
@@ -287,7 +312,7 @@ export class BeanSsrSiteBase<
|
|
|
287
312
|
private _prepareMenusOrGroups<T extends keyof ISsrMenuRecord>(
|
|
288
313
|
locale: keyof ILocaleRecord,
|
|
289
314
|
ssrMenus: IOnionSlice<ISsrMenuRecord, T, unknown>[],
|
|
290
|
-
):
|
|
315
|
+
): ISsrMenuItemPrepared[];
|
|
291
316
|
private _prepareMenusOrGroups<T extends keyof ISsrMenuGroupRecord>(
|
|
292
317
|
locale: keyof ILocaleRecord,
|
|
293
318
|
ssrMenus: IOnionSlice<ISsrMenuGroupRecord, T, unknown>[],
|
|
@@ -298,7 +323,7 @@ export class BeanSsrSiteBase<
|
|
|
298
323
|
) {
|
|
299
324
|
const siteOnionName = this.$onionName;
|
|
300
325
|
// menus
|
|
301
|
-
const menus:
|
|
326
|
+
const menus: ISsrMenuItemPrepared[] = [];
|
|
302
327
|
for (const ssrMenu of ssrMenus) {
|
|
303
328
|
const siteMenuOptions = ssrMenu.beanOptions
|
|
304
329
|
.options as IDecoratorSsrMenuOptions<IDecoratorSsrSiteOptions>;
|
|
@@ -16,22 +16,52 @@ export class ServiceSsr extends BeanBase {
|
|
|
16
16
|
if (!this.app.meta[SymbolCacheSites]) this.app.meta[SymbolCacheSites] = {};
|
|
17
17
|
if (!this.app.meta[SymbolCacheSites][cacheKey]) {
|
|
18
18
|
// check sites
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
19
|
+
const sites = this.bean.onion.ssrSite.getOnionsEnabled(this.ctx.instanceName!).toReversed();
|
|
20
|
+
const siteIds = new Map<string, string>();
|
|
21
|
+
const publicPaths = new Map<string, string>();
|
|
22
|
+
for (const site of sites) {
|
|
23
|
+
const siteOptions = site.beanOptions.options as IDecoratorSsrSiteOptions;
|
|
24
|
+
const beanFullName = site.beanOptions.beanFullName;
|
|
25
|
+
const siteId = siteOptions.siteId;
|
|
26
|
+
if (typeof siteId !== 'string' || !siteId.trim()) {
|
|
27
|
+
throw new Error(
|
|
28
|
+
`Should specify non-empty siteId for SSR site: ${beanFullName} (${cacheKey})`,
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
const siteIdDuplicate = siteIds.get(siteId);
|
|
32
|
+
if (siteIdDuplicate) {
|
|
33
|
+
throw new Error(
|
|
34
|
+
`Duplicate SSR siteId "${siteId}" for ${siteIdDuplicate} and ${beanFullName} (${cacheKey})`,
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
siteIds.set(siteId, beanFullName);
|
|
38
|
+
const publicPath = siteOptions.publicPath;
|
|
39
|
+
if (typeof publicPath !== 'string') {
|
|
40
|
+
throw new TypeError(
|
|
41
|
+
`Should specify publicPath for SSR site: ${beanFullName} (${cacheKey})`,
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
const publicPathDuplicate = publicPaths.get(publicPath);
|
|
45
|
+
if (publicPathDuplicate) {
|
|
46
|
+
throw new Error(
|
|
47
|
+
`Duplicate SSR publicPath "${publicPath}" for ${publicPathDuplicate} and ${beanFullName} (${cacheKey})`,
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
publicPaths.set(publicPath, beanFullName);
|
|
51
|
+
}
|
|
52
|
+
this.app.meta[SymbolCacheSites][cacheKey] = sites.sort((site1, site2) => {
|
|
53
|
+
const siteOptions1 = site1.beanOptions.options as IDecoratorSsrSiteOptions;
|
|
54
|
+
const siteOptions2 = site2.beanOptions.options as IDecoratorSsrSiteOptions;
|
|
55
|
+
const publicPath1 = `/${siteOptions1.publicPath}`;
|
|
56
|
+
const publicPath2 = `/${siteOptions2.publicPath}`;
|
|
57
|
+
return publicPath1 === publicPath2
|
|
58
|
+
? 0
|
|
59
|
+
: publicPath1.startsWith(publicPath2)
|
|
60
|
+
? -1
|
|
61
|
+
: publicPath2.startsWith(publicPath1)
|
|
62
|
+
? 1
|
|
63
|
+
: 0;
|
|
64
|
+
});
|
|
35
65
|
}
|
|
36
66
|
return this.app.meta[SymbolCacheSites][cacheKey];
|
|
37
67
|
}
|
|
@@ -68,10 +68,8 @@ export class ServiceSsrHandler extends BeanBase {
|
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
private _createMissingBundleMessage() {
|
|
71
|
-
const
|
|
72
|
-
const siteName = diagnostics?.siteName ?? String(this._siteOptions.publicPath || 'unknown');
|
|
73
|
-
const buildCommand = diagnostics?.buildCommand;
|
|
71
|
+
const buildCommand = this._siteOptions.diagnostics?.buildCommand;
|
|
74
72
|
const commandHint = buildCommand ? ` Run "${buildCommand}" at the repository root.` : '';
|
|
75
|
-
return `Missing SSR bundle for site ${
|
|
73
|
+
return `Missing SSR bundle for site ${this._siteOptions.siteId}: ${this._siteAssetDir}.${commandHint}`;
|
|
76
74
|
}
|
|
77
75
|
}
|
|
@@ -4,6 +4,7 @@ export interface ZovaConfigEnv {
|
|
|
4
4
|
META_FLAVOR: ZovaMetaFlavor;
|
|
5
5
|
META_MODE: ZovaMetaMode;
|
|
6
6
|
META_APP_MODE: ZovaMetaAppMode;
|
|
7
|
+
SITE_ID: string;
|
|
7
8
|
APP_ROUTER_MODE: 'hash' | 'history' | 'abstract' | undefined;
|
|
8
9
|
APP_ROUTER_BASE: string | undefined;
|
|
9
10
|
APP_PUBLIC_PATH: string | undefined;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ILocaleRecord, OmitNever } from 'vona';
|
|
2
|
-
import type { IMenuItem } from 'vona-module-a-menu';
|
|
2
|
+
import type { IMenuGroup, IMenuItem } from 'vona-module-a-menu';
|
|
3
3
|
import type { IOnionOptionsEnable, ServiceOnion } from 'vona-module-a-onion';
|
|
4
|
+
import type { IRoleNameRecord } from 'vona-module-a-user';
|
|
4
5
|
|
|
5
6
|
import type { ISsrMenuGroupRecord } from './ssrMenuGroup.ts';
|
|
6
7
|
import type { IDecoratorSsrSiteOptions, ISsrSiteRecord } from './ssrSite.ts';
|
|
@@ -12,6 +13,14 @@ export interface ISsrMenuItem<Pages extends {} = {}, Icons extends {} = {}> exte
|
|
|
12
13
|
'name' | 'group'
|
|
13
14
|
> {
|
|
14
15
|
group?: keyof ISsrMenuGroupRecord | (keyof ISsrMenuGroupRecord)[];
|
|
16
|
+
roles?: (keyof IRoleNameRecord)[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type ISsrMenuItemPrepared = IMenuItem<any, any> & Pick<ISsrMenuItem<any, any>, 'roles'>;
|
|
20
|
+
|
|
21
|
+
export interface ISsrMenusPrepared {
|
|
22
|
+
menus?: ISsrMenuItemPrepared[];
|
|
23
|
+
groups?: IMenuGroup[];
|
|
15
24
|
}
|
|
16
25
|
|
|
17
26
|
// should not set default generic = IDecoratorSsrSiteOptions
|
|
@@ -46,12 +46,13 @@ export interface SSRContextState {
|
|
|
46
46
|
pageData?: unknown;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
export interface ISsrSiteIdRecord {}
|
|
50
|
+
|
|
49
51
|
export interface ISsrSitePublicPathRecord {}
|
|
50
52
|
|
|
51
53
|
export interface ISsrSiteRecord {}
|
|
52
54
|
|
|
53
55
|
export interface ISsrSiteDiagnosticsOptions {
|
|
54
|
-
siteName?: string;
|
|
55
56
|
buildCommand?: string;
|
|
56
57
|
}
|
|
57
58
|
|
|
@@ -68,6 +69,7 @@ export interface IDecoratorSsrSiteOptions<
|
|
|
68
69
|
extends
|
|
69
70
|
IOnionOptionsEnable,
|
|
70
71
|
IOnionOptionsMatch<TypeOnionOptionsMatchRule<keyof IInstanceRecord>> {
|
|
72
|
+
siteId: keyof ISsrSiteIdRecord;
|
|
71
73
|
publicPath: keyof ISsrSitePublicPathRecord;
|
|
72
74
|
bundlePath: string;
|
|
73
75
|
diagnostics?: ISsrSiteDiagnosticsOptions;
|
|
@@ -27,6 +27,7 @@ declare module 'vona' {
|
|
|
27
27
|
}
|
|
28
28
|
/** bean: end */
|
|
29
29
|
/** event: begin */
|
|
30
|
+
export * from '../bean/event.retrievePermissionAction.ts';
|
|
30
31
|
export * from '../bean/event.retrievePermissions.ts';
|
|
31
32
|
|
|
32
33
|
import 'vona';
|
|
@@ -36,6 +37,17 @@ declare module 'vona' {
|
|
|
36
37
|
}
|
|
37
38
|
declare module 'vona-module-a-permission' {
|
|
38
39
|
|
|
40
|
+
export interface EventRetrievePermissionAction {
|
|
41
|
+
/** @internal */
|
|
42
|
+
get scope(): ScopeModuleAPermission;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface EventRetrievePermissionAction {
|
|
46
|
+
get $beanFullName(): 'a-permission.event.retrievePermissionAction';
|
|
47
|
+
get $onionName(): 'a-permission:retrievePermissionAction';
|
|
48
|
+
|
|
49
|
+
}
|
|
50
|
+
|
|
39
51
|
export interface EventRetrievePermissions {
|
|
40
52
|
/** @internal */
|
|
41
53
|
get scope(): ScopeModuleAPermission;
|
|
@@ -49,17 +61,21 @@ declare module 'vona-module-a-permission' {
|
|
|
49
61
|
}
|
|
50
62
|
/** event: end */
|
|
51
63
|
/** event: begin */
|
|
64
|
+
import type { EventRetrievePermissionAction } from '../bean/event.retrievePermissionAction.ts';
|
|
52
65
|
import type { EventRetrievePermissions } from '../bean/event.retrievePermissions.ts';
|
|
53
66
|
export interface IModuleEvent {
|
|
54
|
-
'
|
|
67
|
+
'retrievePermissionAction': EventRetrievePermissionAction;
|
|
68
|
+
'retrievePermissions': EventRetrievePermissions;
|
|
55
69
|
}
|
|
56
70
|
/** event: end */
|
|
57
71
|
/** event: begin */
|
|
72
|
+
import type { TypeEventRetrievePermissionActionData, TypeEventRetrievePermissionActionResult } from '../bean/event.retrievePermissionAction.ts';
|
|
58
73
|
import type { TypeEventRetrievePermissionsData, TypeEventRetrievePermissionsResult } from '../bean/event.retrievePermissions.ts';
|
|
59
74
|
import type { EventOn } from 'vona-module-a-event';
|
|
60
75
|
declare module 'vona-module-a-event' {
|
|
61
76
|
export interface IEventRecord {
|
|
62
|
-
'a-permission:
|
|
77
|
+
'a-permission:retrievePermissionAction': EventOn<TypeEventRetrievePermissionActionData, TypeEventRetrievePermissionActionResult>;
|
|
78
|
+
'a-permission:retrievePermissions': EventOn<TypeEventRetrievePermissionsData, TypeEventRetrievePermissionsResult>;
|
|
63
79
|
}
|
|
64
80
|
}
|
|
65
81
|
/** event: end */
|
|
@@ -88,7 +88,12 @@ export class BeanPermission extends BeanBase {
|
|
|
88
88
|
const route = this._getControllerActionRoute(resource, actionKey);
|
|
89
89
|
if (!route?.route?.meta) return false;
|
|
90
90
|
if (!this._matchPassportMeta(route.route.meta)) return false;
|
|
91
|
-
return await this.
|
|
91
|
+
return await this.scope.event.retrievePermissionAction.emit(
|
|
92
|
+
{ resource, actionKey },
|
|
93
|
+
async () => {
|
|
94
|
+
return await this.retrievePermissionActionByRoles(resource, actionKey);
|
|
95
|
+
},
|
|
96
|
+
);
|
|
92
97
|
}
|
|
93
98
|
|
|
94
99
|
@Caching.get({
|
package/vona/src/suite-vendor/a-vona/modules/a-permission/src/bean/event.retrievePermissionAction.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { IResourceRecord } from 'vona-module-a-openapi';
|
|
2
|
+
|
|
3
|
+
import { BeanEventBase, Event } from 'vona-module-a-event';
|
|
4
|
+
|
|
5
|
+
export interface TypeEventRetrievePermissionActionData {
|
|
6
|
+
resource: keyof IResourceRecord;
|
|
7
|
+
actionKey: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export type TypeEventRetrievePermissionActionResult = boolean;
|
|
11
|
+
|
|
12
|
+
@Event()
|
|
13
|
+
export class EventRetrievePermissionAction extends BeanEventBase<
|
|
14
|
+
TypeEventRetrievePermissionActionData,
|
|
15
|
+
TypeEventRetrievePermissionActionResult
|
|
16
|
+
> {}
|