From 483bf98a49f39132bde849b4a30abc570b70f2cf Mon Sep 17 00:00:00 2001 From: Riqwan Thamir Date: Thu, 4 Apr 2024 11:56:17 +0200 Subject: [PATCH] feat(medusa): added endpoints for rule attribute/operator/values options (#6911) what: adds endpoints that returns attribute options, operator options and value options for a particular rule. --- .changeset/fifty-gifts-search.md | 5 + .../promotion/admin/promotion-rules.spec.ts | 315 +++++++++++++++++- packages/customer/src/joiner-config.ts | 19 +- .../api-v2/admin/promotions/middlewares.ts | 12 + .../api-v2/admin/promotions/query-config.ts | 6 + .../[rule_type]/route.ts | 20 ++ .../promotions/rule-operator-options/route.ts | 32 ++ .../[rule_type]/[rule_attribute_id]/route.ts | 94 ++++++ .../api-v2/admin/promotions/utils/index.ts | 3 + .../promotions/utils/rule-attributes-map.ts | 91 +++++ .../utils/validate-rule-attribute.ts | 17 + .../promotions/utils/validate-rule-type.ts | 14 + .../src/api-v2/admin/promotions/validators.ts | 26 +- packages/region/src/joiner-config.ts | 2 +- packages/types/src/region/service.ts | 2 +- 15 files changed, 642 insertions(+), 16 deletions(-) create mode 100644 .changeset/fifty-gifts-search.md create mode 100644 packages/medusa/src/api-v2/admin/promotions/rule-attribute-options/[rule_type]/route.ts create mode 100644 packages/medusa/src/api-v2/admin/promotions/rule-operator-options/route.ts create mode 100644 packages/medusa/src/api-v2/admin/promotions/rule-value-options/[rule_type]/[rule_attribute_id]/route.ts create mode 100644 packages/medusa/src/api-v2/admin/promotions/utils/index.ts create mode 100644 packages/medusa/src/api-v2/admin/promotions/utils/rule-attributes-map.ts create mode 100644 packages/medusa/src/api-v2/admin/promotions/utils/validate-rule-attribute.ts create mode 100644 packages/medusa/src/api-v2/admin/promotions/utils/validate-rule-type.ts diff --git a/.changeset/fifty-gifts-search.md b/.changeset/fifty-gifts-search.md new file mode 100644 index 0000000000..e6f9fdebf0 --- /dev/null +++ b/.changeset/fifty-gifts-search.md @@ -0,0 +1,5 @@ +--- +"@medusajs/medusa": patch +--- + +feat(medusa): added endpoints for rule attribute/operator/values options diff --git a/integration-tests/modules/__tests__/promotion/admin/promotion-rules.spec.ts b/integration-tests/modules/__tests__/promotion/admin/promotion-rules.spec.ts index e359ab3e3d..bde58a75dc 100644 --- a/integration-tests/modules/__tests__/promotion/admin/promotion-rules.spec.ts +++ b/integration-tests/modules/__tests__/promotion/admin/promotion-rules.spec.ts @@ -1,5 +1,11 @@ import { ModuleRegistrationName } from "@medusajs/modules-sdk" -import { IPromotionModuleService } from "@medusajs/types" +import { + ICustomerModuleService, + IProductModuleService, + IPromotionModuleService, + IRegionModuleService, + ISalesChannelModuleService, +} from "@medusajs/types" import { PromotionType } from "@medusajs/utils" import { medusaIntegrationTestRunner } from "medusa-test-utils" import { createAdminUser } from "../../../../helpers/create-admin-user" @@ -16,6 +22,11 @@ medusaIntegrationTestRunner({ let appContainer let standardPromotion let promotionModule: IPromotionModuleService + let regionService: IRegionModuleService + let productService: IProductModuleService + let customerService: ICustomerModuleService + let salesChannelService: ISalesChannelModuleService + const promotionRule = { operator: "eq", attribute: "old_attr", @@ -25,6 +36,12 @@ medusaIntegrationTestRunner({ beforeAll(async () => { appContainer = getContainer() promotionModule = appContainer.resolve(ModuleRegistrationName.PROMOTION) + regionService = appContainer.resolve(ModuleRegistrationName.REGION) + productService = appContainer.resolve(ModuleRegistrationName.PRODUCT) + customerService = appContainer.resolve(ModuleRegistrationName.CUSTOMER) + salesChannelService = appContainer.resolve( + ModuleRegistrationName.SALES_CHANNEL + ) }) beforeEach(async () => { @@ -636,6 +653,302 @@ medusaIntegrationTestRunner({ ) }) }) + + describe("GET /admin/promotions/rule-attribute-options/:ruleType", () => { + it("should throw error when ruleType is invalid", async () => { + const { response } = await api + .get( + `/admin/promotions/rule-attribute-options/does-not-exist`, + adminHeaders + ) + .catch((e) => e) + + expect(response.status).toEqual(400) + expect(response.data).toEqual({ + type: "invalid_data", + message: "Invalid param rule_type (does-not-exist)", + }) + }) + + it("return all rule attributes for a valid ruleType", async () => { + const response = await api.get( + `/admin/promotions/rule-attribute-options/rules`, + adminHeaders + ) + + expect(response.status).toEqual(200) + expect(response.data.attributes).toEqual([ + { + id: "currency", + label: "Currency code", + required: true, + value: "currency_code", + }, + { + id: "customer_group", + label: "Customer Group", + required: false, + value: "customer_group.id", + }, + { + id: "region", + label: "Region", + required: false, + value: "region.id", + }, + { + id: "country", + label: "Country", + required: false, + value: "shipping_address.country_code", + }, + { + id: "sales_channel", + label: "Sales Channel", + required: false, + value: "sales_channel.id", + }, + ]) + }) + }) + + describe("GET /admin/promotions/rule-operator-options", () => { + it("return all rule operators", async () => { + const response = await api.get( + `/admin/promotions/rule-operator-options`, + adminHeaders + ) + + expect(response.status).toEqual(200) + expect(response.data.operators).toEqual([ + { + id: "in", + label: "In", + value: "in", + }, + { + id: "eq", + label: "Equals", + value: "eq", + }, + { + id: "ne", + label: "Not In", + value: "ne", + }, + ]) + }) + }) + + describe("GET /admin/promotions/rule-value-options/:ruleType/:ruleAttributeId", () => { + it("should throw error when ruleType is invalid", async () => { + const { response } = await api + .get( + `/admin/promotions/rule-value-options/does-not-exist/region`, + adminHeaders + ) + .catch((e) => e) + + expect(response.status).toEqual(400) + expect(response.data).toEqual({ + type: "invalid_data", + message: "Invalid param rule_type (does-not-exist)", + }) + }) + + it("should throw error when ruleAttributeId is invalid", async () => { + const { response } = await api + .get( + `/admin/promotions/rule-value-options/rules/does-not-exist`, + adminHeaders + ) + .catch((e) => e) + + expect(response.status).toEqual(400) + expect(response.data).toEqual({ + type: "invalid_data", + message: "Invalid rule attribute - does-not-exist", + }) + }) + + it("should return all values based on rule types", async () => { + const [region1, region2] = await regionService.create([ + { name: "North America", currency_code: "usd" }, + { name: "Europe", currency_code: "eur" }, + ]) + + let response = await api.get( + `/admin/promotions/rule-value-options/rules/region`, + adminHeaders + ) + + expect(response.status).toEqual(200) + expect(response.data.values.length).toEqual(2) + expect(response.data.values).toEqual( + expect.arrayContaining([ + { + label: "North America", + value: region1.id, + }, + { + label: "Europe", + value: region2.id, + }, + ]) + ) + + response = await api.get( + `/admin/promotions/rule-value-options/rules/currency?limit=2`, + adminHeaders + ) + + expect(response.status).toEqual(200) + expect(response.data.values.length).toEqual(2) + expect(response.data.values).toEqual( + expect.arrayContaining([ + { + label: "United Arab Emirates Dirham", + value: "aed", + }, + { + label: "Afghan Afghani", + value: "afn", + }, + ]) + ) + + const group = await customerService.createCustomerGroup({ + name: "VIP", + }) + + response = await api.get( + `/admin/promotions/rule-value-options/rules/customer_group`, + adminHeaders + ) + + expect(response.status).toEqual(200) + expect(response.data.values).toEqual([ + { + label: "VIP", + value: group.id, + }, + ]) + + const salesChannel = await salesChannelService.create({ + name: "Instagram", + }) + + response = await api.get( + `/admin/promotions/rule-value-options/rules/sales_channel`, + adminHeaders + ) + + expect(response.status).toEqual(200) + // TODO: This is returning a default sales channel, but very flakily + // Figure out why this happens and fix + // expect(response.data.values.length).toEqual(1) + expect(response.data.values).toEqual( + expect.arrayContaining([ + { label: "Instagram", value: salesChannel.id }, + ]) + ) + + response = await api.get( + `/admin/promotions/rule-value-options/rules/country?limit=2`, + adminHeaders + ) + + expect(response.status).toEqual(200) + expect(response.data.values.length).toEqual(2) + expect(response.data.values).toEqual( + expect.arrayContaining([ + { label: "Andorra", value: "ad" }, + { label: "United Arab Emirates", value: "ae" }, + ]) + ) + + const [product1, product2] = await productService.create([ + { title: "test product 1" }, + { title: "test product 2" }, + ]) + + response = await api.get( + `/admin/promotions/rule-value-options/target-rules/product`, + adminHeaders + ) + + expect(response.status).toEqual(200) + expect(response.data.values.length).toEqual(2) + expect(response.data.values).toEqual( + expect.arrayContaining([ + { label: "test product 1", value: product1.id }, + { label: "test product 2", value: product2.id }, + ]) + ) + + const category = await productService.createCategory({ + name: "test category 1", + parent_category_id: null, + }) + + response = await api.get( + `/admin/promotions/rule-value-options/target-rules/product_category`, + adminHeaders + ) + + expect(response.status).toEqual(200) + expect(response.data.values).toEqual([ + { label: "test category 1", value: category.id }, + ]) + + const collection = await productService.createCollections({ + title: "test collection 1", + }) + + response = await api.get( + `/admin/promotions/rule-value-options/target-rules/product_collection`, + adminHeaders + ) + + expect(response.status).toEqual(200) + expect(response.data.values).toEqual([ + { label: "test collection 1", value: collection.id }, + ]) + + const type = await productService.createTypes({ + value: "test type", + }) + + response = await api.get( + `/admin/promotions/rule-value-options/target-rules/product_type`, + adminHeaders + ) + + expect(response.status).toEqual(200) + expect(response.data.values).toEqual([ + { label: "test type", value: type.id }, + ]) + + const [tag1, tag2] = await productService.createTags([ + { value: "test tag 1" }, + { value: "test tag 2" }, + ]) + + response = await api.get( + `/admin/promotions/rule-value-options/target-rules/product_tag`, + adminHeaders + ) + + expect(response.status).toEqual(200) + expect(response.data.values.length).toEqual(2) + expect(response.data.values).toEqual( + expect.arrayContaining([ + { label: "test tag 1", value: tag1.id }, + { label: "test tag 2", value: tag2.id }, + ]) + ) + }) + }) }) }, }) diff --git a/packages/customer/src/joiner-config.ts b/packages/customer/src/joiner-config.ts index a4c13525dc..5e6df67bd9 100644 --- a/packages/customer/src/joiner-config.ts +++ b/packages/customer/src/joiner-config.ts @@ -23,10 +23,19 @@ export const joinerConfig: ModuleJoinerConfig = { serviceName: Modules.CUSTOMER, primaryKeys: ["id"], linkableKeys: LinkableKeys, - alias: { - name: ["customer", "customers"], - args: { - entity: Customer.name, + alias: [ + { + name: ["customer", "customers"], + args: { + entity: Customer.name, + }, }, - }, + { + name: ["customer_group", "customer_groups"], + args: { + entity: CustomerGroup.name, + methodSuffix: "CustomerGroups", + }, + }, + ], } diff --git a/packages/medusa/src/api-v2/admin/promotions/middlewares.ts b/packages/medusa/src/api-v2/admin/promotions/middlewares.ts index 31f8add036..cff4bb1510 100644 --- a/packages/medusa/src/api-v2/admin/promotions/middlewares.ts +++ b/packages/medusa/src/api-v2/admin/promotions/middlewares.ts @@ -4,6 +4,7 @@ import { transformBody, transformQuery } from "../../../api/middlewares" import { AdminGetPromotionsParams, AdminGetPromotionsPromotionParams, + AdminGetPromotionsRuleValueParams, AdminPostPromotionsPromotionReq, AdminPostPromotionsPromotionRulesBatchAddReq, AdminPostPromotionsPromotionRulesBatchRemoveReq, @@ -92,4 +93,15 @@ export const adminPromotionRoutesMiddlewares: MiddlewareRoute[] = [ transformBody(AdminPostPromotionsPromotionRulesBatchRemoveReq), ], }, + { + method: ["GET"], + matcher: + "/admin/promotions/rule-value-options/:rule_type/:rule_attribute_id", + middlewares: [ + transformQuery( + AdminGetPromotionsRuleValueParams, + QueryConfig.listRuleValueTransformQueryConfig + ), + ], + }, ] diff --git a/packages/medusa/src/api-v2/admin/promotions/query-config.ts b/packages/medusa/src/api-v2/admin/promotions/query-config.ts index 38d3b65c8a..a6d94bd360 100644 --- a/packages/medusa/src/api-v2/admin/promotions/query-config.ts +++ b/packages/medusa/src/api-v2/admin/promotions/query-config.ts @@ -51,3 +51,9 @@ export const listTransformQueryConfig = { ...retrieveTransformQueryConfig, isList: true, } + +export const listRuleValueTransformQueryConfig = { + defaults: [], + allowed: [], + isList: true, +} diff --git a/packages/medusa/src/api-v2/admin/promotions/rule-attribute-options/[rule_type]/route.ts b/packages/medusa/src/api-v2/admin/promotions/rule-attribute-options/[rule_type]/route.ts new file mode 100644 index 0000000000..802db97d42 --- /dev/null +++ b/packages/medusa/src/api-v2/admin/promotions/rule-attribute-options/[rule_type]/route.ts @@ -0,0 +1,20 @@ +import { + AuthenticatedMedusaRequest, + MedusaResponse, +} from "../../../../../types/routing" +import { ruleAttributesMap, validateRuleType } from "../../utils" + +export const GET = async ( + req: AuthenticatedMedusaRequest, + res: MedusaResponse +) => { + const { rule_type: ruleType } = req.params + + validateRuleType(ruleType) + + const attributes = ruleAttributesMap[ruleType] || [] + + res.json({ + attributes, + }) +} diff --git a/packages/medusa/src/api-v2/admin/promotions/rule-operator-options/route.ts b/packages/medusa/src/api-v2/admin/promotions/rule-operator-options/route.ts new file mode 100644 index 0000000000..c5317f6c9b --- /dev/null +++ b/packages/medusa/src/api-v2/admin/promotions/rule-operator-options/route.ts @@ -0,0 +1,32 @@ +import { RuleOperator } from "@medusajs/utils" +import { + AuthenticatedMedusaRequest, + MedusaResponse, +} from "../../../../types/routing" + +const operators = [ + { + id: RuleOperator.IN, + value: RuleOperator.IN, + label: "In", + }, + { + id: RuleOperator.EQ, + value: RuleOperator.EQ, + label: "Equals", + }, + { + id: RuleOperator.NE, + value: RuleOperator.NE, + label: "Not In", + }, +] + +export const GET = async ( + req: AuthenticatedMedusaRequest, + res: MedusaResponse +) => { + res.json({ + operators, + }) +} diff --git a/packages/medusa/src/api-v2/admin/promotions/rule-value-options/[rule_type]/[rule_attribute_id]/route.ts b/packages/medusa/src/api-v2/admin/promotions/rule-value-options/[rule_type]/[rule_attribute_id]/route.ts new file mode 100644 index 0000000000..8f2d009cd7 --- /dev/null +++ b/packages/medusa/src/api-v2/admin/promotions/rule-value-options/[rule_type]/[rule_attribute_id]/route.ts @@ -0,0 +1,94 @@ +import { + ContainerRegistrationKeys, + remoteQueryObjectFromString, +} from "@medusajs/utils" +import { + AuthenticatedMedusaRequest, + MedusaResponse, +} from "../../../../../../types/routing" +import { validateRuleAttribute, validateRuleType } from "../../../utils" + +const queryConfigurations = { + region: { + entryPoint: "region", + labelAttr: "name", + valueAttr: "id", + }, + currency: { + entryPoint: "currency", + labelAttr: "name", + valueAttr: "code", + }, + customer_group: { + entryPoint: "customer_group", + labelAttr: "name", + valueAttr: "id", + }, + sales_channel: { + entryPoint: "sales_channel", + labelAttr: "name", + valueAttr: "id", + }, + country: { + entryPoint: "country", + labelAttr: "display_name", + valueAttr: "iso_2", + }, + product: { + entryPoint: "product", + labelAttr: "title", + valueAttr: "id", + }, + product_category: { + entryPoint: "product_category", + labelAttr: "name", + valueAttr: "id", + }, + product_collection: { + entryPoint: "product_collection", + labelAttr: "title", + valueAttr: "id", + }, + product_type: { + entryPoint: "product_type", + labelAttr: "value", + valueAttr: "id", + }, + product_tag: { + entryPoint: "product_tag", + labelAttr: "value", + valueAttr: "id", + }, +} + +export const GET = async ( + req: AuthenticatedMedusaRequest, + res: MedusaResponse +) => { + const { rule_type: ruleType, rule_attribute_id: ruleAttributeId } = req.params + const queryConfig = queryConfigurations[ruleAttributeId] + const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY) + + validateRuleType(ruleType) + validateRuleAttribute(ruleType, ruleAttributeId) + + const { rows } = await remoteQuery( + remoteQueryObjectFromString({ + entryPoint: queryConfig.entryPoint, + variables: { + filters: req.filterableFields, + ...req.remoteQueryConfig.pagination, + }, + fields: [queryConfig.labelAttr, queryConfig.valueAttr], + }) + ) + + const values = rows.map((r) => ({ + label: r[queryConfig.labelAttr], + value: r[queryConfig.valueAttr], + })) + + res.json({ + values, + }) +} diff --git a/packages/medusa/src/api-v2/admin/promotions/utils/index.ts b/packages/medusa/src/api-v2/admin/promotions/utils/index.ts new file mode 100644 index 0000000000..193881239c --- /dev/null +++ b/packages/medusa/src/api-v2/admin/promotions/utils/index.ts @@ -0,0 +1,3 @@ +export * from "./rule-attributes-map" +export * from "./validate-rule-attribute" +export * from "./validate-rule-type" diff --git a/packages/medusa/src/api-v2/admin/promotions/utils/rule-attributes-map.ts b/packages/medusa/src/api-v2/admin/promotions/utils/rule-attributes-map.ts new file mode 100644 index 0000000000..9e41712c0d --- /dev/null +++ b/packages/medusa/src/api-v2/admin/promotions/utils/rule-attributes-map.ts @@ -0,0 +1,91 @@ +const ruleAttributes = [ + { + id: "currency", + value: "currency_code", + label: "Currency code", + required: true, + }, + { + id: "customer_group", + value: "customer_group.id", + label: "Customer Group", + required: false, + }, + { + id: "region", + value: "region.id", + label: "Region", + required: false, + }, + { + id: "country", + value: "shipping_address.country_code", + label: "Country", + required: false, + }, + { + id: "sales_channel", + value: "sales_channel.id", + label: "Sales Channel", + required: false, + }, +] + +const commonAttributes = [ + { + id: "product", + value: "items.product.id", + label: "Product", + required: false, + }, + { + id: "product_category", + value: "items.product.categories.id", + label: "Product Category", + required: false, + }, + { + id: "product_collection", + value: "items.product.collection_id", + label: "Product Collection", + required: false, + }, + { + id: "product_type", + value: "items.product.type_id", + label: "Product Type", + required: false, + }, + { + id: "product_tag", + value: "items.product.tags.id", + label: "Product Tag", + required: false, + }, +] + +const buyRuleAttributes = [ + { + id: "buy_rules_min_quantity", + value: "buy_rules_min_quantity", + label: "Minimum quantity of items", + required: true, + }, + ...commonAttributes, +] + +const targetRuleAttributes = [ + { + id: "apply_to_quantity", + value: "apply_to_quantity", + label: "Quantity of items promotion will apply to", + required: true, + }, + ...commonAttributes, +] + +export const ruleAttributesMap = { + rules: ruleAttributes, + "target-rules": targetRuleAttributes, + "buy-rules": buyRuleAttributes, +} diff --git a/packages/medusa/src/api-v2/admin/promotions/utils/validate-rule-attribute.ts b/packages/medusa/src/api-v2/admin/promotions/utils/validate-rule-attribute.ts new file mode 100644 index 0000000000..f6e4211cc9 --- /dev/null +++ b/packages/medusa/src/api-v2/admin/promotions/utils/validate-rule-attribute.ts @@ -0,0 +1,17 @@ +import { MedusaError } from "@medusajs/utils" +import { ruleAttributesMap } from "./rule-attributes-map" + +export function validateRuleAttribute( + ruleType: string, + ruleAttributeId: string +) { + const ruleAttributes = ruleAttributesMap[ruleType] || [] + const ruleAttribute = ruleAttributes.find((obj) => obj.id === ruleAttributeId) + + if (!ruleAttribute) { + throw new MedusaError( + MedusaError.Types.INVALID_DATA, + `Invalid rule attribute - ${ruleAttributeId}` + ) + } +} diff --git a/packages/medusa/src/api-v2/admin/promotions/utils/validate-rule-type.ts b/packages/medusa/src/api-v2/admin/promotions/utils/validate-rule-type.ts new file mode 100644 index 0000000000..e2ec7f6d78 --- /dev/null +++ b/packages/medusa/src/api-v2/admin/promotions/utils/validate-rule-type.ts @@ -0,0 +1,14 @@ +import { MedusaError, RuleType } from "@medusajs/utils" + +const validRuleTypes: string[] = Object.values(RuleType) + +export function validateRuleType(ruleType: string) { + const underscorizedRuleType = ruleType.split("-").join("_") + + if (!validRuleTypes.includes(underscorizedRuleType)) { + throw new MedusaError( + MedusaError.Types.INVALID_DATA, + `Invalid param rule_type (${ruleType})` + ) + } +} diff --git a/packages/medusa/src/api-v2/admin/promotions/validators.ts b/packages/medusa/src/api-v2/admin/promotions/validators.ts index f41d40b3a5..5c4c71d936 100644 --- a/packages/medusa/src/api-v2/admin/promotions/validators.ts +++ b/packages/medusa/src/api-v2/admin/promotions/validators.ts @@ -30,6 +30,18 @@ import { AdminPostCampaignsReq } from "../campaigns/validators" export class AdminGetPromotionsPromotionParams extends FindParams {} +export class AdminGetPromotionsRuleValueParams extends extendedFindParamsMixin({ + limit: 100, + offset: 0, +}) { + /** + * Search terms to search fields. + */ + @IsString() + @IsOptional() + q?: string +} + export class AdminGetPromotionsParams extends extendedFindParamsMixin({ limit: 100, offset: 0, @@ -71,7 +83,6 @@ export class AdminPostPromotionsReq { @IsOptional() is_automatic?: boolean - @IsOptional() @IsEnum(PromotionType) type?: PromotionTypeValues @@ -86,8 +97,8 @@ export class AdminPostPromotionsReq { @IsNotEmpty() @ValidateNested() - @Type(() => ApplicationMethodsPostReq) - application_method: ApplicationMethodsPostReq + @Type(() => AdminPostApplicationMethodsReq) + application_method: AdminPostApplicationMethodsReq @IsOptional() @IsArray() @@ -113,7 +124,7 @@ export class PromotionRule { values: string[] } -export class ApplicationMethodsPostReq { +export class AdminPostApplicationMethodsReq { @IsOptional() @IsString() description?: string @@ -130,7 +141,6 @@ export class ApplicationMethodsPostReq { @IsEnum(ApplicationMethodType) type?: ApplicationMethodType - @IsOptional() @IsEnum(ApplicationMethodTargetType) target_type?: ApplicationMethodTargetType @@ -161,7 +171,7 @@ export class ApplicationMethodsPostReq { buy_rules_min_quantity?: number } -export class ApplicationMethodsMethodPostReq { +export class AdminPostApplicationMethodsMethodReq { @IsOptional() @IsString() description?: string @@ -233,8 +243,8 @@ export class AdminPostPromotionsPromotionReq { @IsOptional() @ValidateNested() - @Type(() => ApplicationMethodsMethodPostReq) - application_method?: ApplicationMethodsMethodPostReq + @Type(() => AdminPostApplicationMethodsMethodReq) + application_method?: AdminPostApplicationMethodsMethodReq @IsOptional() @IsArray() diff --git a/packages/region/src/joiner-config.ts b/packages/region/src/joiner-config.ts index 85f7a7bc59..28ec186528 100644 --- a/packages/region/src/joiner-config.ts +++ b/packages/region/src/joiner-config.ts @@ -30,7 +30,7 @@ export const joinerConfig: ModuleJoinerConfig = { }, { name: ["country", "countries"], - args: { entity: Country.name }, + args: { entity: Country.name, methodSuffix: "Countries" }, }, ], } as ModuleJoinerConfig diff --git a/packages/types/src/region/service.ts b/packages/types/src/region/service.ts index 214912bfb7..75ee0af4bd 100644 --- a/packages/types/src/region/service.ts +++ b/packages/types/src/region/service.ts @@ -8,7 +8,7 @@ import { RegionCountryDTO, RegionDTO, } from "./common" -import { CreateRegionDTO, UpsertRegionDTO, UpdateRegionDTO } from "./mutations" +import { CreateRegionDTO, UpdateRegionDTO, UpsertRegionDTO } from "./mutations" /** * The main service interface for the region module.