cabloy 5.1.134 → 5.1.136
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/SKILL.md +9 -0
- package/.claude/skills/cabloy-zova-source-reading/SKILL.md +24 -1
- package/.claude/skills/cabloy-zova-source-reading/references/analysis-modes.md +12 -0
- package/.claude/skills/cabloy-zova-source-reading/references/core-reading-paths.md +28 -0
- package/.github/workflows/playwright-e2e.yml +2 -2
- package/.github/workflows/vona-test-pg.yml +1 -1
- package/.github/workflows/vona-test-sqlite3.yml +1 -1
- package/CHANGELOG.md +38 -0
- package/CLAUDE.md +1 -0
- package/cabloy-docs/.vitepress/config.mjs +7 -0
- package/cabloy-docs/backend/markdown-guide.md +189 -0
- package/cabloy-docs/frontend/component-guide.md +6 -0
- package/cabloy-docs/frontend/css-in-js-guide.md +6 -0
- package/cabloy-docs/frontend/markdown-guide.md +258 -0
- package/cabloy-docs/frontend/page-guide.md +48 -0
- package/cabloy-docs/frontend/page-params-guide.md +4 -2
- package/cabloy-docs/frontend/page-route-guide.md +2 -2
- package/cabloy-docs/frontend/zova-router-under-the-hood.md +2 -0
- package/cabloy-docs/frontend/zova-source-reading-map.md +53 -9
- package/cabloy-docs/fullstack/a-pay-payment-suite.md +333 -0
- package/cabloy-docs/fullstack/payment-sandbox-configuration.md +285 -0
- package/e2e/specs/a-commerce/commerce.spec.ts +503 -47
- package/e2e/specs/cabloy-basic/basic.spec.ts +30 -0
- package/package.json +1 -1
- package/vona/env/.env +2 -0
- package/vona/packages-cli/cli/package.json +1 -1
- package/vona/packages-cli/cli-set-api/cli/templates/init/error/boilerplate/config/errors.ts +6 -1
- package/vona/packages-cli/cli-set-api/cli/templates/tools/crudBasic/boilerplate/src/entity/{{resourceName}}.tsx_ +2 -0
- package/vona/packages-cli/cli-set-api/cli/templates/tools/crudStart/boilerplate/src/entity/{{resourceName}}.tsx_ +2 -0
- package/vona/packages-cli/cli-set-api/package.json +1 -1
- package/vona/packages-utils/localeutil/package.json +1 -1
- package/vona/packages-utils/localeutil/src/util.ts +1 -1
- package/vona/packages-vona/vona/package.json +1 -1
- package/vona/packages-vona/vona-core/package.json +1 -1
- package/vona/packages-vona/vona-mock/package.json +1 -1
- package/vona/patches/{zova-core@5.1.82.patch → zova-core@5.1.84.patch} +2 -2
- package/vona/pnpm-lock.yaml +826 -270
- package/vona/pnpm-workspace.yaml +1 -1
- package/vona/src/suite/a-commerce/modules/commerce-catalog/package.json +1 -1
- package/vona/src/suite/a-commerce/modules/commerce-catalog/src/.metadata/index.ts +99 -1
- package/vona/src/suite/a-commerce/modules/commerce-catalog/src/bean/meta.index.ts +1 -0
- package/vona/src/suite/a-commerce/modules/commerce-catalog/src/bean/meta.version.ts +19 -0
- package/vona/src/suite/a-commerce/modules/commerce-catalog/src/config/locale/en-us.ts +3 -0
- package/vona/src/suite/a-commerce/modules/commerce-catalog/src/config/locale/zh-cn.ts +3 -0
- package/vona/src/suite/a-commerce/modules/commerce-catalog/src/controller/product.ts +3 -3
- package/vona/src/suite/a-commerce/modules/commerce-catalog/src/dto/productContentUpdate.tsx +14 -0
- package/vona/src/suite/a-commerce/modules/commerce-catalog/src/dto/productContentView.tsx +19 -0
- package/vona/src/suite/a-commerce/modules/commerce-catalog/src/dto/productCreate.tsx +48 -3
- package/vona/src/suite/a-commerce/modules/commerce-catalog/src/dto/productPublicDetail.tsx +15 -0
- package/vona/src/suite/a-commerce/modules/commerce-catalog/src/dto/productUpdate.tsx +48 -3
- package/vona/src/suite/a-commerce/modules/commerce-catalog/src/dto/productView.tsx +52 -3
- package/vona/src/suite/a-commerce/modules/commerce-catalog/src/entity/productContent.tsx +36 -0
- package/vona/src/suite/a-commerce/modules/commerce-catalog/src/model/product.ts +6 -0
- package/vona/src/suite/a-commerce/modules/commerce-catalog/src/model/productContent.ts +22 -0
- package/vona/src/suite/a-commerce/modules/commerce-catalog/src/service/product.ts +82 -6
- package/vona/src/suite/a-commerce/modules/commerce-catalog/test/productContent.test.ts +271 -0
- package/vona/src/suite/a-commerce/modules/commerce-catalog/test/productPresentation.test.ts +81 -2
- package/vona/src/suite/a-commerce/modules/commerce-payment/package.json +4 -1
- package/vona/src/suite/a-commerce/modules/commerce-payment/src/bean/payScene.commerceOrder.ts +32 -15
- package/vona/src/suite/a-commerce/modules/commerce-payment/test/paymentAttempt.test.ts +8 -2
- package/vona/src/suite/a-commerce/modules/commerce-siteweb/src/.metadata/index.ts +8 -2
- package/vona/src/suite/a-commerce/modules/commerce-siteweb/src/.metadata/locales.ts +18 -0
- package/vona/src/suite/a-commerce/modules/commerce-siteweb/src/bean/ssrMenu.home.ts +3 -1
- package/vona/src/suite/a-commerce/modules/commerce-siteweb/src/config/locale/en-us.ts +3 -0
- package/vona/src/suite/a-commerce/modules/commerce-siteweb/src/config/locale/zh-cn.ts +3 -0
- package/vona/src/suite/a-commerce/modules/commerce-siteweb/src/index.ts +1 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/controller/checkout.ts +13 -2
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/lib/providerReference.ts +18 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/service/order.ts +43 -8
- package/vona/src/suite/a-commerce/modules/commerce-trade/test/paymentOutcome.test.ts +74 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/test/providerReference.test.ts +33 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/test/refundLifecycle.test.ts +73 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/test/reservationExpiry.test.ts +2 -0
- package/vona/src/suite/a-home/modules/home-user/src/.metadata/index.ts +30 -0
- package/vona/src/suite/a-home/modules/home-user/src/bean/meta.version.ts +7 -2
- package/vona/src/suite/a-home/modules/home-user/src/config/locale/en-us.ts +4 -1
- package/vona/src/suite/a-home/modules/home-user/src/config/locale/zh-cn.ts +4 -1
- package/vona/src/suite/a-home/modules/home-user/src/config/roles.ts +4 -4
- package/vona/src/suite/a-home/modules/home-user/src/entity/role.ts +2 -2
- package/vona/src/suite/a-home/modules/home-user/src/entity/user.ts +8 -1
- package/vona/src/suite/a-home/modules/home-user/src/service/userAdapter.ts +6 -2
- package/vona/src/suite/a-home/modules/home-user/test/passportTest.test.ts +1 -0
- package/vona/src/suite/a-home/modules/home-user/test/role.test.ts +4 -4
- package/vona/src/suite-vendor/a-cabloy/modules/a-markdown/package.json +64 -0
- package/vona/src/suite-vendor/a-cabloy/modules/a-markdown/src/.metadata/index.ts +54 -0
- package/vona/src/suite-vendor/a-cabloy/modules/a-markdown/src/.metadata/this.ts +2 -0
- package/vona/src/suite-vendor/a-cabloy/modules/a-markdown/src/bean/bean.markdown.ts +137 -0
- package/vona/src/suite-vendor/a-cabloy/modules/a-markdown/src/index.ts +1 -0
- package/vona/src/suite-vendor/a-cabloy/modules/a-markdown/tsconfig.build.json +11 -0
- package/vona/src/suite-vendor/a-cabloy/modules/a-markdown/tsconfig.json +7 -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/bean/bean.ssr.ts +17 -0
- package/vona/src/suite-vendor/a-cabloy/modules/a-ssr/src/lib/beanSsrSiteBase.ts +11 -3
- package/vona/src/suite-vendor/a-cabloy/package.json +2 -1
- package/vona/src/suite-vendor/a-cabloy/tsconfig.json +3 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/package.json +1 -1
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/bean/meta.version.ts +1 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/entity/providerOperation.tsx +8 -1
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/lib/index.ts +1 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/lib/providerOperationFailure.ts +8 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/service/providerOperation.ts +187 -29
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/types/payment.ts +4 -0
- package/vona/src/suite-vendor/a-pay/modules/pay-mock/package.json +1 -1
- package/vona/src/suite-vendor/a-pay/modules/pay-paypal/package.json +1 -1
- package/vona/src/suite-vendor/a-pay/modules/pay-paypal/src/bean/payProvider.paypal.ts +0 -6
- package/vona/src/suite-vendor/a-pay/modules/pay-paypal/src/index.ts +1 -0
- package/vona/src/suite-vendor/a-pay/modules/pay-stripe/package.json +2 -1
- package/vona/src/suite-vendor/a-pay/modules/pay-stripe/src/bean/payProvider.stripe.ts +458 -22
- package/vona/src/suite-vendor/a-pay/modules/pay-stripe/src/index.ts +1 -0
- package/vona/src/suite-vendor/a-pay/modules/pay-stripe/src/lib/stripeGateway.ts +85 -0
- package/vona/src/suite-vendor/a-pay/modules/pay-stripe/src/types/index.ts +2 -0
- package/vona/src/suite-vendor/a-pay/package.json +5 -5
- package/vona/src/suite-vendor/a-vona/modules/a-core/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/bean/bean.permission.ts +1 -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 +14 -0
- package/vona/src/suite-vendor/a-vona/modules/a-user/src/bean/guard.passport.ts +4 -0
- package/vona/src/suite-vendor/a-vona/modules/a-user/src/types/role.ts +3 -2
- package/vona/src/suite-vendor/a-vona/modules/a-user/src/types/user.ts +4 -0
- package/vona/src/suite-vendor/a-vona/package.json +1 -1
- package/zova/packages-utils/logger/package.json +2 -2
- 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 +3 -3
- package/zova/packages-zova/zova-core/src/core/sys/resource.ts +1 -1
- package/zova/packages-zova/zova-core/src/core/sys/util.ts +8 -0
- package/zova/pnpm-lock.yaml +570 -11
- package/zova/src/front/config/config/config.cabloyCommerce.ts +0 -6
- package/zova/src/suite/a-commerce/modules/commerce-catalog/package.json +3 -1
- package/zova/src/suite/a-commerce/modules/commerce-catalog/src/api/openapi/schemas.ts +236 -36
- package/zova/src/suite/a-commerce/modules/commerce-catalog/src/api/openapi/types.ts +4962 -2402
- package/zova/src/suite/a-commerce/modules/commerce-catalog/src/config/locale/en-us.ts +1 -0
- package/zova/src/suite/a-commerce/modules/commerce-catalog/src/config/locale/zh-cn.ts +1 -0
- package/zova/src/suite/a-commerce/modules/commerce-catalog/src/page/product/controller.tsx +8 -3
- package/zova/src/suite/a-commerce/modules/commerce-member/src/.metadata/index.ts +6 -7
- package/zova/src/suite/a-commerce/modules/commerce-member/src/page/address/controller.tsx +1 -3
- package/zova/src/suite/a-commerce/modules/commerce-member/src/routes.ts +2 -3
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/.metadata/index.ts +13 -20
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/page/cart/controller.tsx +3 -9
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/page/checkout/controller.tsx +9 -11
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/page/order/controller.tsx +0 -1
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/page/orders/controller.tsx +6 -4
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/page/payment/controller.tsx +78 -17
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/routes.ts +10 -13
- package/zova/src/suite/a-home/modules/home-api/mock/passport.fake.ts +3 -3
- package/zova/src/suite/a-home/modules/home-api/src/.metadata/index.ts +31 -1
- package/zova/src/suite/a-home/modules/home-api/src/api/homeBaseSiteCatalog.ts +24 -0
- package/zova/src/suite/a-home/modules/home-api/src/api/openapi/schemas.ts +329 -17
- package/zova/src/suite/a-home/modules/home-api/src/api/openapi/types.ts +5866 -1829
- package/zova/src/suite/a-home/modules/home-api/src/apiSchema/homeBaseSiteCatalog.ts +13 -0
- package/zova/src/suite/a-home/modules/home-base/src/component/page/controller.tsx +1 -0
- package/zova/src/suite/a-home/modules/home-layoutweb/src/component/layoutWeb/render.header.tsx +0 -1
- package/zova/src/suite/cabloy-basic/modules/basic-details/src/types/detail.ts +1 -0
- package/zova/src/suite/cabloy-basic/modules/basic-markdown/package.json +61 -0
- package/zova/src/suite/cabloy-basic/modules/basic-markdown/src/.metadata/component/formFieldMarkdown.ts +36 -0
- package/zova/src/suite/cabloy-basic/modules/basic-markdown/src/.metadata/component/markdownHtml.ts +36 -0
- package/zova/src/suite/cabloy-basic/modules/basic-markdown/src/.metadata/index.ts +147 -0
- package/zova/src/suite/cabloy-basic/modules/basic-markdown/src/.metadata/this.ts +2 -0
- package/zova/src/suite/cabloy-basic/modules/basic-markdown/src/component/formFieldMarkdown/controller.tsx +111 -0
- package/zova/src/suite/cabloy-basic/modules/basic-markdown/src/component/formFieldMarkdown/render.tsx +40 -0
- package/zova/src/suite/cabloy-basic/modules/basic-markdown/src/component/formFieldMarkdown/style.ts +13 -0
- package/zova/src/suite/cabloy-basic/modules/basic-markdown/src/component/markdownHtml/controller.tsx +17 -0
- package/zova/src/suite/cabloy-basic/modules/basic-markdown/src/component/markdownHtml/render.tsx +9 -0
- package/zova/src/suite/cabloy-basic/modules/basic-markdown/src/component/markdownHtml/style.ts +13 -0
- package/zova/src/suite/cabloy-basic/modules/basic-markdown/src/index.ts +1 -0
- package/zova/src/suite/cabloy-basic/modules/basic-markdown/src/lib/richTextContentStyle.ts +142 -0
- package/zova/src/suite/cabloy-basic/modules/basic-markdown/tsconfig.build.json +13 -0
- package/zova/src/suite/cabloy-basic/modules/basic-markdown/tsconfig.json +5 -0
- package/zova/src/suite/cabloy-basic/modules/basic-page/src/component/blockPage/controller.tsx +25 -3
- package/zova/src/suite/cabloy-basic/modules/basic-page/src/types/page.ts +1 -0
- package/zova/src/suite/cabloy-basic/modules/basic-pageentry/src/types/pageEntry.ts +1 -0
- package/zova/src/suite/cabloy-basic/package.json +1 -0
- package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/package.json +1 -1
- package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/model/resource.ts +7 -0
- package/zova/src/suite-vendor/a-cabloy/package.json +2 -2
- package/zova/src/suite-vendor/a-zova/modules/a-logger/package.json +2 -2
- 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/bean/sys.router.ts +3 -2
- package/zova/src/suite-vendor/a-zova/modules/a-ssr/package.json +1 -1
- package/zova/src/suite-vendor/a-zova/modules/a-ssr/test/lib/ssrProfile.test.ts +88 -0
- 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
- package/.claude/scheduled_tasks.lock +0 -1
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { TableIdentity } from 'table-identity';
|
|
2
|
+
import type { IDecoratorEntityOptions } from 'vona-module-a-orm';
|
|
3
|
+
|
|
4
|
+
import { $makeMetadata, Api, v } from 'vona-module-a-openapiutils';
|
|
5
|
+
import { Entity, EntityBase } from 'vona-module-a-orm';
|
|
6
|
+
import { ZovaRender } from 'zova-rest-cabloy-basic-admin';
|
|
7
|
+
|
|
8
|
+
import { $locale } from '../.metadata/locales.ts';
|
|
9
|
+
|
|
10
|
+
export interface IEntityOptionsProductContent extends IDecoratorEntityOptions {}
|
|
11
|
+
|
|
12
|
+
@Entity<IEntityOptionsProductContent>('commerceCatalogProductContent', {
|
|
13
|
+
openapi: { title: $locale('ProductContent') },
|
|
14
|
+
fields: {
|
|
15
|
+
id: $makeMetadata(ZovaRender.visible(false)),
|
|
16
|
+
iid: $makeMetadata(ZovaRender.visible(false)),
|
|
17
|
+
deleted: $makeMetadata(ZovaRender.visible(false)),
|
|
18
|
+
createdAt: $makeMetadata(ZovaRender.visible(false)),
|
|
19
|
+
updatedAt: $makeMetadata(ZovaRender.visible(false)),
|
|
20
|
+
},
|
|
21
|
+
})
|
|
22
|
+
export class EntityProductContent extends EntityBase {
|
|
23
|
+
@Api.field(v.tableIdentity(), ZovaRender.visible(false))
|
|
24
|
+
productId: TableIdentity;
|
|
25
|
+
|
|
26
|
+
@Api.field(
|
|
27
|
+
v.title($locale('DescriptionMarkdown')),
|
|
28
|
+
v.optional(),
|
|
29
|
+
ZovaRender.order(1),
|
|
30
|
+
ZovaRender.field('basic-markdown:formFieldMarkdown'),
|
|
31
|
+
)
|
|
32
|
+
descriptionMarkdown?: string;
|
|
33
|
+
|
|
34
|
+
@Api.field(v.title($locale('DescriptionHtml')), v.optional(), ZovaRender.visible(false))
|
|
35
|
+
descriptionHtml?: string;
|
|
36
|
+
}
|
|
@@ -15,6 +15,12 @@ export interface IModelOptionsProduct extends IDecoratorModelOptions<EntityProdu
|
|
|
15
15
|
'categoryId',
|
|
16
16
|
{ columns: ['id', 'name', 'published'] },
|
|
17
17
|
),
|
|
18
|
+
productContent: $relation.hasOne('commerce-catalog:productContent', 'productId', {
|
|
19
|
+
columns: ['id', 'productId', 'descriptionMarkdown', 'descriptionHtml'],
|
|
20
|
+
}),
|
|
21
|
+
productContentForm: $relation.hasOne('commerce-catalog:productContent', 'productId', {
|
|
22
|
+
columns: ['id', 'productId', 'descriptionMarkdown'],
|
|
23
|
+
}),
|
|
18
24
|
skus: $relation.hasMany('commerce-catalog:sku', 'productId'),
|
|
19
25
|
skuAvailables: $relation.hasMany(
|
|
20
26
|
'commerce-catalog:sku',
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { IDecoratorModelOptions } from 'vona-module-a-orm';
|
|
2
|
+
|
|
3
|
+
import { $relation, BeanModelBase, Model } from 'vona-module-a-orm';
|
|
4
|
+
|
|
5
|
+
import { EntityProductContent } from '../entity/productContent.tsx';
|
|
6
|
+
|
|
7
|
+
export interface IModelOptionsProductContent extends IDecoratorModelOptions<EntityProductContent> {}
|
|
8
|
+
|
|
9
|
+
@Model<IModelOptionsProductContent>({
|
|
10
|
+
entity: EntityProductContent,
|
|
11
|
+
relations: {
|
|
12
|
+
product: $relation.belongsTo(
|
|
13
|
+
'commerce-catalog:productContent',
|
|
14
|
+
'commerce-catalog:product',
|
|
15
|
+
'productId',
|
|
16
|
+
),
|
|
17
|
+
},
|
|
18
|
+
cache: {
|
|
19
|
+
modelsClear: 'commerce-catalog:product',
|
|
20
|
+
},
|
|
21
|
+
})
|
|
22
|
+
export class ModelProductContent extends BeanModelBase<EntityProductContent> {}
|
|
@@ -4,9 +4,11 @@ import type { IQueryParams } from 'vona-module-a-orm';
|
|
|
4
4
|
|
|
5
5
|
import { BeanBase } from 'vona';
|
|
6
6
|
import { Service } from 'vona-module-a-bean';
|
|
7
|
+
import { Core } from 'vona-module-a-core';
|
|
7
8
|
|
|
8
9
|
import type { DtoProductCreate } from '../dto/productCreate.tsx';
|
|
9
10
|
import type { DtoProductPublic } from '../dto/productPublic.tsx';
|
|
11
|
+
import type { DtoProductPublicDetail } from '../dto/productPublicDetail.tsx';
|
|
10
12
|
import type { DtoProductPublicSelectRes } from '../dto/productPublicSelectRes.tsx';
|
|
11
13
|
import type { DtoProductSelectRes } from '../dto/productSelectRes.tsx';
|
|
12
14
|
import type { DtoProductUpdate } from '../dto/productUpdate.tsx';
|
|
@@ -14,11 +16,35 @@ import type { DtoProductView } from '../dto/productView.tsx';
|
|
|
14
16
|
import type { EntityProduct } from '../entity/product.tsx';
|
|
15
17
|
import type { ModelProduct } from '../model/product.ts';
|
|
16
18
|
|
|
19
|
+
const serializationRetryOptions = {
|
|
20
|
+
retries: 3,
|
|
21
|
+
factor: 1,
|
|
22
|
+
minTimeout: 10,
|
|
23
|
+
maxTimeout: 10,
|
|
24
|
+
randomize: false,
|
|
25
|
+
errorCodes: [
|
|
26
|
+
'40001',
|
|
27
|
+
'ER_LOCK_DEADLOCK',
|
|
28
|
+
'ER_LOCK_WAIT_TIMEOUT',
|
|
29
|
+
'SQLITE_BUSY',
|
|
30
|
+
'SQLITE_BUSY_SNAPSHOT',
|
|
31
|
+
],
|
|
32
|
+
ownerOnly: true,
|
|
33
|
+
};
|
|
34
|
+
|
|
17
35
|
@Service()
|
|
18
36
|
export class ServiceProduct extends BeanBase {
|
|
37
|
+
@Core.transaction()
|
|
19
38
|
async create(product: DtoProductCreate): Promise<EntityProduct> {
|
|
20
39
|
await this._ensureCategoryExists(product.categoryId);
|
|
21
|
-
|
|
40
|
+
const { productContentForm, ...productFields } = product as DtoProductCreate & {
|
|
41
|
+
productContentForm?: { descriptionMarkdown?: string };
|
|
42
|
+
};
|
|
43
|
+
const created = await this.scope.model.product.insert(productFields);
|
|
44
|
+
if (productContentForm !== undefined) {
|
|
45
|
+
await this._updateProductContent(created.id, productContentForm.descriptionMarkdown);
|
|
46
|
+
}
|
|
47
|
+
return created;
|
|
22
48
|
}
|
|
23
49
|
|
|
24
50
|
async select(params?: IQueryParams<ModelProduct>): Promise<DtoProductSelectRes> {
|
|
@@ -54,15 +80,19 @@ export class ServiceProduct extends BeanBase {
|
|
|
54
80
|
|
|
55
81
|
async view(id: TableIdentity): Promise<DtoProductView | undefined> {
|
|
56
82
|
return await this.scope.model.product.getById(id, {
|
|
57
|
-
include: {
|
|
83
|
+
include: {
|
|
84
|
+
category: true,
|
|
85
|
+
productContentForm: { columns: ['id', 'productId', 'descriptionMarkdown'] },
|
|
86
|
+
},
|
|
58
87
|
});
|
|
59
88
|
}
|
|
60
89
|
|
|
61
|
-
async viewPublic(id: TableIdentity): Promise<
|
|
90
|
+
async viewPublic(id: TableIdentity): Promise<DtoProductPublicDetail | undefined> {
|
|
62
91
|
const product = await this.scope.model.product.get(this._getPublicProductWhere({ id }), {
|
|
63
92
|
columns: ['id', 'title', 'description', 'categoryId'],
|
|
64
93
|
include: {
|
|
65
94
|
category: { columns: ['id', 'name'] },
|
|
95
|
+
productContent: { columns: ['descriptionHtml'] },
|
|
66
96
|
skuAvailables: true,
|
|
67
97
|
},
|
|
68
98
|
});
|
|
@@ -70,16 +100,37 @@ export class ServiceProduct extends BeanBase {
|
|
|
70
100
|
const balancesBySkuId = await this._getBalancesBySkuId(
|
|
71
101
|
product.skuAvailables.map(sku => sku.id),
|
|
72
102
|
);
|
|
73
|
-
|
|
103
|
+
const publicProduct = this._toPublicProduct(product, balancesBySkuId);
|
|
104
|
+
if (!publicProduct) return undefined;
|
|
105
|
+
return {
|
|
106
|
+
...publicProduct,
|
|
107
|
+
descriptionHtml: product.productContent?.descriptionHtml,
|
|
108
|
+
};
|
|
74
109
|
}
|
|
75
110
|
|
|
111
|
+
@Core.transaction()
|
|
112
|
+
@Core.retryable(serializationRetryOptions)
|
|
76
113
|
async update(id: TableIdentity, product: DtoProductUpdate) {
|
|
114
|
+
const productForUpdate = await this.scope.model.product.getByIdForUpdate(id);
|
|
115
|
+
if (!productForUpdate) {
|
|
116
|
+
this.app.throw(404, 'Product not found');
|
|
117
|
+
}
|
|
77
118
|
if (product.categoryId !== undefined) await this._ensureCategoryExists(product.categoryId);
|
|
78
|
-
|
|
119
|
+
const { productContentForm, ...productFields } = product as DtoProductUpdate & {
|
|
120
|
+
productContentForm?: { descriptionMarkdown?: string };
|
|
121
|
+
};
|
|
122
|
+
await this.scope.model.product.updateById(id, productFields);
|
|
123
|
+
if (productContentForm !== undefined) {
|
|
124
|
+
await this._updateProductContent(id, productContentForm.descriptionMarkdown);
|
|
125
|
+
}
|
|
79
126
|
}
|
|
80
127
|
|
|
128
|
+
@Core.transaction()
|
|
81
129
|
async delete(id: TableIdentity) {
|
|
82
|
-
|
|
130
|
+
const product = await this.scope.model.product.getByIdForUpdate(id);
|
|
131
|
+
if (!product) return;
|
|
132
|
+
await this.scope.model.productContent.delete({ productId: id });
|
|
133
|
+
return await this.scope.model.product.deleteById(product.id);
|
|
83
134
|
}
|
|
84
135
|
|
|
85
136
|
private _getPublicProductWhere(where?: Record<string, unknown>) {
|
|
@@ -159,6 +210,31 @@ export class ServiceProduct extends BeanBase {
|
|
|
159
210
|
};
|
|
160
211
|
}
|
|
161
212
|
|
|
213
|
+
private async _updateProductContent(
|
|
214
|
+
productId: TableIdentity,
|
|
215
|
+
descriptionMarkdown?: string,
|
|
216
|
+
): Promise<void> {
|
|
217
|
+
const markdown = descriptionMarkdown?.trim();
|
|
218
|
+
const productContent = await this.scope.model.productContent.getForUpdate({ productId });
|
|
219
|
+
if (!markdown) {
|
|
220
|
+
if (productContent) await this.scope.model.productContent.deleteById(productContent.id);
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
const descriptionHtml = this.bean.markdown.renderHtml(markdown);
|
|
224
|
+
if (productContent) {
|
|
225
|
+
await this.scope.model.productContent.updateById(productContent.id, {
|
|
226
|
+
descriptionMarkdown: markdown,
|
|
227
|
+
descriptionHtml,
|
|
228
|
+
});
|
|
229
|
+
} else {
|
|
230
|
+
await this.scope.model.productContent.insert({
|
|
231
|
+
productId,
|
|
232
|
+
descriptionMarkdown: markdown,
|
|
233
|
+
descriptionHtml,
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
162
238
|
private async _ensureCategoryExists(categoryId: TableIdentity) {
|
|
163
239
|
if (!(await this.scope.model.category.getById(categoryId))) {
|
|
164
240
|
this.app.throw(404, 'Product category not found');
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import assert from 'node:assert';
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
3
|
+
import { after, before, describe, it } from 'node:test';
|
|
4
|
+
import { acquireTestLock, app } from 'vona-mock';
|
|
5
|
+
|
|
6
|
+
interface IFixture {
|
|
7
|
+
categoryId?: number | string;
|
|
8
|
+
productIds: Array<number | string>;
|
|
9
|
+
skuIds: Array<number | string>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async function cleanupFixture(fixture: IFixture) {
|
|
13
|
+
const scopeCatalog = app.scope('commerce-catalog');
|
|
14
|
+
const scopeTrade = app.scope('commerce-trade');
|
|
15
|
+
for (const skuId of fixture.skuIds) {
|
|
16
|
+
await scopeTrade.model.stockAudit.delete({ skuId });
|
|
17
|
+
await scopeTrade.model.stockReservation.delete({ skuId });
|
|
18
|
+
await scopeTrade.model.stockBalance.delete({ skuId });
|
|
19
|
+
await scopeCatalog.model.sku.delete({ id: skuId });
|
|
20
|
+
}
|
|
21
|
+
for (const productId of fixture.productIds) {
|
|
22
|
+
await scopeCatalog.model.productContent.delete({ productId });
|
|
23
|
+
await scopeCatalog.model.product.delete({ id: productId });
|
|
24
|
+
}
|
|
25
|
+
if (fixture.categoryId !== undefined) {
|
|
26
|
+
await scopeCatalog.model.category.delete({ id: fixture.categoryId });
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function runInMockCtx<TResult>(operation: () => Promise<TResult>): Promise<TResult> {
|
|
31
|
+
return await app.bean.executor.mockCtx(async () => {
|
|
32
|
+
await app.bean.passport.signinMock();
|
|
33
|
+
try {
|
|
34
|
+
return await operation();
|
|
35
|
+
} finally {
|
|
36
|
+
await app.bean.passport.signout();
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
describe('productContent.test.ts', { concurrency: false }, () => {
|
|
42
|
+
let releaseTestLock: (() => void) | undefined;
|
|
43
|
+
|
|
44
|
+
before(async () => {
|
|
45
|
+
releaseTestLock = await acquireTestLock('a-commerce');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
after(() => {
|
|
49
|
+
releaseTestLock?.();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('stores Markdown and server-rendered safe HTML through the Admin contract', async () => {
|
|
53
|
+
await app.bean.executor.mockCtx(async () => {
|
|
54
|
+
const fixture: IFixture = { productIds: [], skuIds: [] };
|
|
55
|
+
const suffix = randomUUID().slice(0, 12);
|
|
56
|
+
const scopeCatalog = app.scope('commerce-catalog');
|
|
57
|
+
await app.bean.passport.signinMock();
|
|
58
|
+
try {
|
|
59
|
+
const category = await scopeCatalog.model.category.insert({
|
|
60
|
+
name: `__ProductContentCategory-${suffix}__`,
|
|
61
|
+
published: true,
|
|
62
|
+
});
|
|
63
|
+
fixture.categoryId = category.id;
|
|
64
|
+
const product = await scopeCatalog.model.product.insert({
|
|
65
|
+
categoryId: category.id,
|
|
66
|
+
title: `__ProductContent-${suffix}__`,
|
|
67
|
+
description: 'Short summary',
|
|
68
|
+
published: true,
|
|
69
|
+
});
|
|
70
|
+
fixture.productIds.push(product.id);
|
|
71
|
+
const sku = await scopeCatalog.model.sku.insert({
|
|
72
|
+
code: `__ProductContentSku-${suffix}__`,
|
|
73
|
+
productId: product.id,
|
|
74
|
+
priceCents: 1999,
|
|
75
|
+
lifecycle: 'active',
|
|
76
|
+
});
|
|
77
|
+
fixture.skuIds.push(sku.id);
|
|
78
|
+
await app.scope('commerce-trade').service.stockBalance.adjustStock({
|
|
79
|
+
skuId: sku.id,
|
|
80
|
+
delta: 1,
|
|
81
|
+
reason: 'product content fixture',
|
|
82
|
+
correlationId: `product-content-${suffix}`,
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
const markdown =
|
|
86
|
+
'# Product details\n\nA **durable** product.\n\n- [ ] Open task\n- [x] Completed task\n\n<a href="javascript:alert(\'unsafe\')">unsafe</a>';
|
|
87
|
+
const updateResult = await app.bean.executor.performAction(
|
|
88
|
+
'patch',
|
|
89
|
+
'/commerce/catalog/product/:id',
|
|
90
|
+
{
|
|
91
|
+
params: { id: product.id },
|
|
92
|
+
body: {
|
|
93
|
+
title: product.title,
|
|
94
|
+
categoryId: product.categoryId,
|
|
95
|
+
published: product.published,
|
|
96
|
+
productContentForm: { descriptionMarkdown: markdown },
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
);
|
|
100
|
+
assert.equal(updateResult, null);
|
|
101
|
+
|
|
102
|
+
const stored = await scopeCatalog.model.productContent.get({ productId: product.id });
|
|
103
|
+
assert.ok(stored);
|
|
104
|
+
assert.equal(stored.descriptionMarkdown, markdown);
|
|
105
|
+
assert.equal(stored.descriptionHtml, app.bean.markdown.renderHtml(markdown));
|
|
106
|
+
assert.match(stored.descriptionHtml!, /<h1>Product details<\/h1>/);
|
|
107
|
+
assert.match(stored.descriptionHtml!, /<strong>durable<\/strong>/);
|
|
108
|
+
assert.match(stored.descriptionHtml!, /<ul data-type="taskList">/);
|
|
109
|
+
assert.match(stored.descriptionHtml!, /<input type="checkbox" disabled="disabled" \/>/);
|
|
110
|
+
assert.match(
|
|
111
|
+
stored.descriptionHtml!,
|
|
112
|
+
/<input type="checkbox" checked="checked" disabled="disabled" \/>/,
|
|
113
|
+
);
|
|
114
|
+
assert.doesNotMatch(stored.descriptionHtml!, /<a[^>]+javascript:/i);
|
|
115
|
+
|
|
116
|
+
const adminProduct: any = await app.bean.executor.performAction(
|
|
117
|
+
'get',
|
|
118
|
+
'/commerce/catalog/product/:id',
|
|
119
|
+
{ params: { id: product.id } },
|
|
120
|
+
);
|
|
121
|
+
assert.equal(adminProduct.productContentForm.descriptionMarkdown, markdown);
|
|
122
|
+
|
|
123
|
+
const publicDetailWithTasks: any = await app.bean.executor.performAction(
|
|
124
|
+
'get',
|
|
125
|
+
'/commerce/catalog/product/public/:id',
|
|
126
|
+
{ params: { id: product.id }, innerAccess: false },
|
|
127
|
+
);
|
|
128
|
+
assert.equal(publicDetailWithTasks.descriptionMarkdown, undefined);
|
|
129
|
+
assert.equal(publicDetailWithTasks.descriptionHtml, stored.descriptionHtml);
|
|
130
|
+
|
|
131
|
+
await scopeCatalog.service.product.update(product.id, {
|
|
132
|
+
productContentForm: {
|
|
133
|
+
descriptionMarkdown: '## Server-derived HTML',
|
|
134
|
+
descriptionHtml: '<script>forged</script>',
|
|
135
|
+
},
|
|
136
|
+
} as any);
|
|
137
|
+
const forgedHtmlIgnored = await scopeCatalog.model.productContent.get({
|
|
138
|
+
productId: product.id,
|
|
139
|
+
});
|
|
140
|
+
assert.equal(forgedHtmlIgnored?.descriptionHtml, '<h2>Server-derived HTML</h2>');
|
|
141
|
+
|
|
142
|
+
const publicDetail: any = await app.bean.executor.performAction(
|
|
143
|
+
'get',
|
|
144
|
+
'/commerce/catalog/product/public/:id',
|
|
145
|
+
{ params: { id: product.id }, innerAccess: false },
|
|
146
|
+
);
|
|
147
|
+
assert.equal(publicDetail.descriptionMarkdown, undefined);
|
|
148
|
+
assert.equal(publicDetail.descriptionHtml, '<h2>Server-derived HTML</h2>');
|
|
149
|
+
assert.equal(publicDetail.description, 'Short summary');
|
|
150
|
+
|
|
151
|
+
const publicList: any = await app.bean.executor.performAction(
|
|
152
|
+
'get',
|
|
153
|
+
'/commerce/catalog/product/public',
|
|
154
|
+
{ innerAccess: false },
|
|
155
|
+
);
|
|
156
|
+
const publicListItem = publicList.list.find(
|
|
157
|
+
(item: any) => String(item.id) === String(product.id),
|
|
158
|
+
);
|
|
159
|
+
assert.ok(publicListItem);
|
|
160
|
+
assert.equal(publicListItem.descriptionHtml, undefined);
|
|
161
|
+
|
|
162
|
+
await app.bean.executor.performAction('patch', '/commerce/catalog/product/:id', {
|
|
163
|
+
params: { id: product.id },
|
|
164
|
+
body: {
|
|
165
|
+
title: product.title,
|
|
166
|
+
categoryId: product.categoryId,
|
|
167
|
+
published: product.published,
|
|
168
|
+
productContentForm: { descriptionMarkdown: ' \n\t' },
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
assert.equal(
|
|
172
|
+
await scopeCatalog.model.productContent.get({ productId: product.id }),
|
|
173
|
+
undefined,
|
|
174
|
+
);
|
|
175
|
+
const productWithoutContent: any = await app.bean.executor.performAction(
|
|
176
|
+
'get',
|
|
177
|
+
'/commerce/catalog/product/:id',
|
|
178
|
+
{ params: { id: product.id } },
|
|
179
|
+
);
|
|
180
|
+
assert.equal(productWithoutContent.productContentForm, undefined);
|
|
181
|
+
} finally {
|
|
182
|
+
await cleanupFixture(fixture);
|
|
183
|
+
await app.bean.passport.signout();
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('removes ProductContent when its Product is deleted', async () => {
|
|
189
|
+
await app.bean.executor.mockCtx(async () => {
|
|
190
|
+
const fixture: IFixture = { productIds: [], skuIds: [] };
|
|
191
|
+
const suffix = randomUUID().slice(0, 12);
|
|
192
|
+
const scopeCatalog = app.scope('commerce-catalog');
|
|
193
|
+
await app.bean.passport.signinMock();
|
|
194
|
+
try {
|
|
195
|
+
const category = await scopeCatalog.model.category.insert({
|
|
196
|
+
name: `__ProductContentDeleteCategory-${suffix}__`,
|
|
197
|
+
published: false,
|
|
198
|
+
});
|
|
199
|
+
fixture.categoryId = category.id;
|
|
200
|
+
const product = await scopeCatalog.model.product.insert({
|
|
201
|
+
categoryId: category.id,
|
|
202
|
+
title: `__ProductContentDelete-${suffix}__`,
|
|
203
|
+
published: false,
|
|
204
|
+
});
|
|
205
|
+
fixture.productIds.push(product.id);
|
|
206
|
+
await scopeCatalog.service.product.update(product.id, {
|
|
207
|
+
productContentForm: { descriptionMarkdown: 'Content to delete' },
|
|
208
|
+
} as any);
|
|
209
|
+
|
|
210
|
+
await app.bean.executor.performAction('delete', '/commerce/catalog/product/:id', {
|
|
211
|
+
params: { id: product.id },
|
|
212
|
+
});
|
|
213
|
+
fixture.productIds = fixture.productIds.filter(id => String(id) !== String(product.id));
|
|
214
|
+
assert.equal(
|
|
215
|
+
await scopeCatalog.model.productContent.get({ productId: product.id }),
|
|
216
|
+
undefined,
|
|
217
|
+
);
|
|
218
|
+
} finally {
|
|
219
|
+
await cleanupFixture(fixture);
|
|
220
|
+
await app.bean.passport.signout();
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it('keeps the final Markdown and HTML pair consistent across competing updates', async () => {
|
|
226
|
+
const fixture: IFixture = { productIds: [], skuIds: [] };
|
|
227
|
+
const suffix = randomUUID().slice(0, 12);
|
|
228
|
+
const markdowns = ['# First update', '## Second update'];
|
|
229
|
+
try {
|
|
230
|
+
const productId = await runInMockCtx(async () => {
|
|
231
|
+
const scopeCatalog = app.scope('commerce-catalog');
|
|
232
|
+
const category = await scopeCatalog.model.category.insert({
|
|
233
|
+
name: `__ProductContentRaceCategory-${suffix}__`,
|
|
234
|
+
published: false,
|
|
235
|
+
});
|
|
236
|
+
fixture.categoryId = category.id;
|
|
237
|
+
const product = await scopeCatalog.model.product.insert({
|
|
238
|
+
categoryId: category.id,
|
|
239
|
+
title: `__ProductContentRace-${suffix}__`,
|
|
240
|
+
published: false,
|
|
241
|
+
});
|
|
242
|
+
fixture.productIds.push(product.id);
|
|
243
|
+
return product.id;
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
await Promise.all(
|
|
247
|
+
markdowns.map(descriptionMarkdown =>
|
|
248
|
+
runInMockCtx(async () => {
|
|
249
|
+
await app.scope('commerce-catalog').service.product.update(productId, {
|
|
250
|
+
productContentForm: { descriptionMarkdown },
|
|
251
|
+
} as any);
|
|
252
|
+
}),
|
|
253
|
+
),
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
await runInMockCtx(async () => {
|
|
257
|
+
const stored = await app.scope('commerce-catalog').model.productContent.get({ productId });
|
|
258
|
+
assert.ok(stored);
|
|
259
|
+
assert.ok(markdowns.includes(stored.descriptionMarkdown!));
|
|
260
|
+
assert.equal(
|
|
261
|
+
stored.descriptionHtml,
|
|
262
|
+
app.bean.markdown.renderHtml(stored.descriptionMarkdown),
|
|
263
|
+
);
|
|
264
|
+
});
|
|
265
|
+
} finally {
|
|
266
|
+
await runInMockCtx(async () => {
|
|
267
|
+
await cleanupFixture(fixture);
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
});
|
|
@@ -4,6 +4,8 @@ import { describe, it } from 'node:test';
|
|
|
4
4
|
import { app } from 'vona-mock';
|
|
5
5
|
import {
|
|
6
6
|
DtoProductCreate,
|
|
7
|
+
DtoProductPublic,
|
|
8
|
+
DtoProductPublicDetail,
|
|
7
9
|
DtoProductSelectResItem,
|
|
8
10
|
DtoProductUpdate,
|
|
9
11
|
DtoProductView,
|
|
@@ -15,14 +17,38 @@ function findComponent(apiJson: any, predicate: (properties: any) => boolean) {
|
|
|
15
17
|
}) as any;
|
|
16
18
|
}
|
|
17
19
|
|
|
18
|
-
function
|
|
20
|
+
function getEntryBlocks(component: any) {
|
|
19
21
|
const pageEntry = component?.rest?.blocks?.[0];
|
|
20
22
|
const formBlock = pageEntry?.options?.blocks?.[0];
|
|
21
23
|
const toolbarBlock = pageEntry?.options?.blocks?.[1];
|
|
22
24
|
assert.equal(pageEntry?.render, 'basic-pageentry:blockPageEntry');
|
|
23
25
|
assert.equal(formBlock?.render, 'basic-pageentry:blockForm');
|
|
24
26
|
assert.equal(toolbarBlock?.render, 'basic-pageentry:blockToolbarRow');
|
|
25
|
-
return toolbarBlock;
|
|
27
|
+
return { formBlock, toolbarBlock };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function getEntryForm(component: any) {
|
|
31
|
+
return getEntryBlocks(component).toolbarBlock;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function assertProductFormGroups(component: any, productFields: string[]) {
|
|
35
|
+
const { formBlock } = getEntryBlocks(component);
|
|
36
|
+
const layoutBlock = formBlock?.options?.blocks?.[0];
|
|
37
|
+
assert.equal(layoutBlock?.render, 'basic-form:blockFormLayout');
|
|
38
|
+
const groups = layoutBlock?.options?.formLayout?.children;
|
|
39
|
+
assert.equal(groups?.length, 2);
|
|
40
|
+
assert.deepEqual(
|
|
41
|
+
groups.map((group: any) => group.type),
|
|
42
|
+
['group', 'group'],
|
|
43
|
+
);
|
|
44
|
+
assert.deepEqual(
|
|
45
|
+
groups[0].children[0].children.map((field: any) => field.name),
|
|
46
|
+
productFields,
|
|
47
|
+
);
|
|
48
|
+
assert.deepEqual(
|
|
49
|
+
groups[1].children[0].children.map((field: any) => field.name),
|
|
50
|
+
['productContentForm'],
|
|
51
|
+
);
|
|
26
52
|
}
|
|
27
53
|
|
|
28
54
|
describe('productPresentation.test.ts', () => {
|
|
@@ -35,6 +61,7 @@ describe('productPresentation.test.ts', () => {
|
|
|
35
61
|
assert.deepEqual(Object.keys(createComponent.properties).sort(), [
|
|
36
62
|
'categoryId',
|
|
37
63
|
'description',
|
|
64
|
+
'productContentForm',
|
|
38
65
|
'published',
|
|
39
66
|
'title',
|
|
40
67
|
]);
|
|
@@ -54,6 +81,16 @@ describe('productPresentation.test.ts', () => {
|
|
|
54
81
|
createComponent.properties.published.rest.form.options.items.map((item: any) => item.value),
|
|
55
82
|
[false, true],
|
|
56
83
|
);
|
|
84
|
+
assert.equal(
|
|
85
|
+
createComponent.properties.productContentForm.rest.fieldSource,
|
|
86
|
+
'productContentForm.descriptionMarkdown',
|
|
87
|
+
);
|
|
88
|
+
assert.equal(
|
|
89
|
+
createComponent.properties.productContentForm.rest.form.render,
|
|
90
|
+
'basic-markdown:formFieldMarkdown',
|
|
91
|
+
);
|
|
92
|
+
assert.equal(createComponent.properties.published.default, false);
|
|
93
|
+
assertProductFormGroups(createComponent, ['title', 'categoryId', 'published', 'description']);
|
|
57
94
|
const createToolbar = getEntryForm(createComponent);
|
|
58
95
|
assert.deepEqual(
|
|
59
96
|
createToolbar.options.actions.map((action: any) => action.render),
|
|
@@ -67,6 +104,7 @@ describe('productPresentation.test.ts', () => {
|
|
|
67
104
|
assert.deepEqual(Object.keys(updateComponent.properties).sort(), [
|
|
68
105
|
'categoryId',
|
|
69
106
|
'description',
|
|
107
|
+
'productContentForm',
|
|
70
108
|
'published',
|
|
71
109
|
'title',
|
|
72
110
|
]);
|
|
@@ -78,6 +116,15 @@ describe('productPresentation.test.ts', () => {
|
|
|
78
116
|
updateComponent.properties.published.rest.form.render,
|
|
79
117
|
'basic-select:formFieldSelect',
|
|
80
118
|
);
|
|
119
|
+
assert.equal(
|
|
120
|
+
updateComponent.properties.productContentForm.rest.fieldSource,
|
|
121
|
+
'productContentForm.descriptionMarkdown',
|
|
122
|
+
);
|
|
123
|
+
assert.equal(
|
|
124
|
+
updateComponent.properties.productContentForm.rest.form.render,
|
|
125
|
+
'basic-markdown:formFieldMarkdown',
|
|
126
|
+
);
|
|
127
|
+
assertProductFormGroups(updateComponent, ['title', 'categoryId', 'published', 'description']);
|
|
81
128
|
|
|
82
129
|
const viewJson = await app.bean.openapi.generateJsonOfClass(DtoProductView);
|
|
83
130
|
const viewComponent = findComponent(viewJson, properties => {
|
|
@@ -104,6 +151,23 @@ describe('productPresentation.test.ts', () => {
|
|
|
104
151
|
viewComponent.properties.published.rest.form.render,
|
|
105
152
|
'basic-select:formFieldSelect',
|
|
106
153
|
);
|
|
154
|
+
assert.equal(
|
|
155
|
+
viewComponent.properties.productContentForm.rest.fieldSource,
|
|
156
|
+
'productContentForm.descriptionMarkdown',
|
|
157
|
+
);
|
|
158
|
+
assert.equal(
|
|
159
|
+
viewComponent.properties.productContentForm.rest.form.render,
|
|
160
|
+
'basic-markdown:formFieldMarkdown',
|
|
161
|
+
);
|
|
162
|
+
assertProductFormGroups(viewComponent, [
|
|
163
|
+
'id',
|
|
164
|
+
'title',
|
|
165
|
+
'categoryId',
|
|
166
|
+
'published',
|
|
167
|
+
'description',
|
|
168
|
+
'createdAt',
|
|
169
|
+
'updatedAt',
|
|
170
|
+
]);
|
|
107
171
|
const viewToolbar = getEntryForm(viewComponent);
|
|
108
172
|
assert.deepEqual(
|
|
109
173
|
viewToolbar.options.actions.map((action: any) => action.render),
|
|
@@ -139,6 +203,21 @@ describe('productPresentation.test.ts', () => {
|
|
|
139
203
|
['basic-table:actionUpdate', 'basic-table:actionDelete'],
|
|
140
204
|
);
|
|
141
205
|
|
|
206
|
+
const publicProductJson = await app.bean.openapi.generateJsonOfClass(DtoProductPublic);
|
|
207
|
+
const publicProductComponent = findComponent(
|
|
208
|
+
publicProductJson,
|
|
209
|
+
properties => properties?.skuAvailables && properties?.description,
|
|
210
|
+
);
|
|
211
|
+
assert.equal(publicProductComponent.properties.descriptionHtml, undefined);
|
|
212
|
+
|
|
213
|
+
const publicDetailJson = await app.bean.openapi.generateJsonOfClass(DtoProductPublicDetail);
|
|
214
|
+
const publicDetailComponent = findComponent(
|
|
215
|
+
publicDetailJson,
|
|
216
|
+
properties => properties?.skuAvailables && properties?.descriptionHtml,
|
|
217
|
+
);
|
|
218
|
+
assert.deepEqual(publicDetailComponent.properties.descriptionHtml.type, ['string', 'null']);
|
|
219
|
+
assert.equal(publicDetailComponent.properties.descriptionMarkdown, undefined);
|
|
220
|
+
|
|
142
221
|
const filterBlock = listComponent.rest.blocks[0].options.blocks[0];
|
|
143
222
|
const filterLayout = filterBlock.options.blocks[0].options.formLayout;
|
|
144
223
|
const section = filterLayout.children[0];
|
|
@@ -32,7 +32,9 @@
|
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
34
|
"vona-module-a-pay": "workspace:*",
|
|
35
|
-
"vona-module-
|
|
35
|
+
"vona-module-a-ssr": "workspace:*",
|
|
36
|
+
"vona-module-pay-paypal": "workspace:*",
|
|
37
|
+
"vona-module-pay-stripe": "workspace:*"
|
|
36
38
|
},
|
|
37
39
|
"devDependencies": {
|
|
38
40
|
"clean-package": "^2.2.0",
|
|
@@ -50,6 +52,7 @@
|
|
|
50
52
|
"vonaModule": {
|
|
51
53
|
"dependencies": {
|
|
52
54
|
"a-pay": "5.0.0",
|
|
55
|
+
"a-ssr": "5.0.0",
|
|
53
56
|
"a-vona": "5.0.0"
|
|
54
57
|
},
|
|
55
58
|
"fileVersion": 1
|