| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181 |
- import { CustomFieldConfig, CustomFields, CustomFieldType, Type } from 'shared/shared-types';
- import { assertNever } from 'shared/shared-utils';
- import { Column, ColumnType, Connection, ConnectionOptions, Entity, getConnection } from 'typeorm';
- import { VendureConfig } from '../config/vendure-config';
- import { VendureEntity } from './base/base.entity';
- import { coreEntitiesMap } from './entities';
- @Entity()
- export class CustomAddressFields {}
- @Entity()
- export class CustomFacetFields {}
- @Entity()
- export class CustomFacetFieldsTranslation {}
- @Entity()
- export class CustomFacetValueFields {}
- @Entity()
- export class CustomFacetValueFieldsTranslation {}
- @Entity()
- export class CustomCustomerFields {}
- @Entity()
- export class CustomProductFields {}
- @Entity()
- export class CustomProductFieldsTranslation {}
- @Entity()
- export class CustomProductOptionFields {}
- @Entity()
- export class CustomProductOptionFieldsTranslation {}
- @Entity()
- export class CustomProductOptionGroupFields {}
- @Entity()
- export class CustomProductOptionGroupFieldsTranslation {}
- @Entity()
- export class CustomProductVariantFields {}
- @Entity()
- export class CustomProductVariantFieldsTranslation {}
- @Entity()
- export class CustomUserFields {}
- /**
- * Dynamically add columns to the custom field entity based on the CustomFields config.
- */
- function registerCustomFieldsForEntity(
- config: VendureConfig,
- entityName: keyof CustomFields,
- ctor: { new (): any },
- translation = false,
- ) {
- const customFields = config.customFields && config.customFields[entityName];
- const dbEngine = config.dbConnectionOptions.type;
- if (customFields) {
- for (const customField of customFields) {
- const { name, type } = customField;
- const registerColumn = () =>
- Column({ type: getColumnType(dbEngine, type), name })(new ctor(), name);
- if (translation) {
- if (type === 'localeString') {
- registerColumn();
- }
- } else {
- if (type !== 'localeString') {
- registerColumn();
- }
- }
- }
- }
- }
- function getColumnType(dbEngine: ConnectionOptions['type'], type: CustomFieldType): ColumnType {
- switch (type) {
- case 'string':
- case 'localeString':
- return 'varchar';
- case 'boolean':
- return dbEngine === 'mysql' ? 'tinyint' : 'bool';
- case 'int':
- return 'int';
- case 'float':
- return 'double';
- case 'datetime':
- return dbEngine === 'mysql' ? 'datetime' : 'timestamp';
- default:
- assertNever(type);
- }
- return 'varchar';
- }
- function validateCustomFieldsForEntity(
- connection: Connection,
- entity: Type<VendureEntity>,
- customFields: CustomFieldConfig[],
- ): void {
- const metadata = connection.getMetadata(entity);
- const { relations } = metadata;
- const translationRelation = relations.find(r => r.propertyName === 'translations');
- if (translationRelation) {
- const translationEntity = translationRelation.type;
- const translationPropMap = connection.getMetadata(translationEntity).createPropertiesMap();
- const localeStringFields = customFields.filter(field => field.type === 'localeString');
- assertNoNameConflicts(entity.name, translationPropMap, localeStringFields);
- } else {
- assertNoLocaleStringFields(entity, customFields);
- }
- const nonLocaleStringFields = customFields.filter(field => field.type !== 'localeString');
- const propMap = metadata.createPropertiesMap();
- assertNoNameConflicts(entity.name, propMap, nonLocaleStringFields);
- }
- /**
- * Assert that none of the custom field names conflict with existing properties of the entity, as provided
- * by the TypeORM PropertiesMap object.
- */
- function assertNoNameConflicts(entityName: string, propMap: object, customFields: CustomFieldConfig[]): void {
- for (const customField of customFields) {
- if (propMap.hasOwnProperty(customField.name)) {
- const message = `Custom field name conflict: the "${entityName}" entity already has a built-in property "${
- customField.name
- }".`;
- throw new Error(message);
- }
- }
- }
- /**
- * For entities which are not localized (Address, Customer), we assert that none of the custom fields
- * have a type "localeString".
- */
- function assertNoLocaleStringFields(entity: Type<any>, customFields: CustomFieldConfig[]): void {
- if (!!customFields.find(f => f.type === 'localeString')) {
- const message = `Custom field type error: the "${
- entity.name
- }" entity does not support the "localeString" type.`;
- throw new Error(message);
- }
- }
- /**
- * Dynamically registers any custom fields with TypeORM. This function should be run at the bootstrap
- * stage of the app lifecycle, before the AppModule is initialized.
- */
- export function registerCustomEntityFields(config: VendureConfig) {
- registerCustomFieldsForEntity(config, 'Address', CustomAddressFields);
- registerCustomFieldsForEntity(config, 'Customer', CustomCustomerFields);
- registerCustomFieldsForEntity(config, 'Facet', CustomFacetFields);
- registerCustomFieldsForEntity(config, 'Facet', CustomFacetFieldsTranslation, true);
- registerCustomFieldsForEntity(config, 'FacetValue', CustomFacetValueFields);
- registerCustomFieldsForEntity(config, 'FacetValue', CustomFacetValueFieldsTranslation, true);
- registerCustomFieldsForEntity(config, 'Product', CustomProductFields);
- registerCustomFieldsForEntity(config, 'Product', CustomProductFieldsTranslation, true);
- registerCustomFieldsForEntity(config, 'ProductOption', CustomProductOptionFields);
- registerCustomFieldsForEntity(config, 'ProductOption', CustomProductOptionFieldsTranslation, true);
- registerCustomFieldsForEntity(config, 'ProductOptionGroup', CustomProductOptionGroupFields);
- registerCustomFieldsForEntity(
- config,
- 'ProductOptionGroup',
- CustomProductOptionGroupFieldsTranslation,
- true,
- );
- registerCustomFieldsForEntity(config, 'ProductVariant', CustomProductVariantFields);
- registerCustomFieldsForEntity(config, 'ProductVariant', CustomProductVariantFieldsTranslation, true);
- registerCustomFieldsForEntity(config, 'User', CustomUserFields);
- }
- /**
- * Validates the custom fields config, e.g. by ensuring that there are no naming conflicts with the built-in fields
- * of each entity.
- */
- export function validateCustomFieldsConfig(customFieldConfig: CustomFields) {
- const connection = getConnection();
- for (const key of Object.keys(customFieldConfig)) {
- const entityName = key as keyof CustomFields;
- const customEntityFields = customFieldConfig[entityName] || [];
- const entity = coreEntitiesMap[entityName];
- validateCustomFieldsForEntity(connection, entity, customEntityFields);
- }
- }
|