From d4b921f3dbe0a38f1565a8de759996c70798d58e Mon Sep 17 00:00:00 2001 From: Riqwan Thamir Date: Tue, 12 Mar 2024 18:35:24 +0100 Subject: [PATCH] feat(medusa,pricing,types): added get endpoints for pricing rule types (#6678) what: - adds list endpoint for rule types - adds get endpoint for rule types --- .changeset/chatty-trainers-grow.md | 7 ++ .../pricing/admin/rule-types.spec.ts | 112 ++++++++++++++++++ .../src/api-v2/admin/pricing/middlewares.ts | 35 ++++++ .../src/api-v2/admin/pricing/query-config.ts | 20 ++++ .../admin/pricing/rule-types/[id]/route.ts | 23 ++++ .../api-v2/admin/pricing/rule-types/route.ts | 30 +++++ .../src/api-v2/admin/pricing/validators.ts | 12 ++ packages/medusa/src/api-v2/middlewares.ts | 2 + .../migrations/.snapshot-medusa-pricing.json | 25 +++- .../src/migrations/Migration20230929122253.ts | 3 +- packages/pricing/src/models/money-amount.ts | 9 +- packages/pricing/src/models/rule-type.ts | 15 +++ .../pricing/src/types/services/rule-type.ts | 9 ++ .../types/src/pricing/common/rule-type.ts | 24 ++-- 14 files changed, 312 insertions(+), 14 deletions(-) create mode 100644 .changeset/chatty-trainers-grow.md create mode 100644 integration-tests/modules/__tests__/pricing/admin/rule-types.spec.ts create mode 100644 packages/medusa/src/api-v2/admin/pricing/middlewares.ts create mode 100644 packages/medusa/src/api-v2/admin/pricing/query-config.ts create mode 100644 packages/medusa/src/api-v2/admin/pricing/rule-types/[id]/route.ts create mode 100644 packages/medusa/src/api-v2/admin/pricing/rule-types/route.ts create mode 100644 packages/medusa/src/api-v2/admin/pricing/validators.ts diff --git a/.changeset/chatty-trainers-grow.md b/.changeset/chatty-trainers-grow.md new file mode 100644 index 0000000000..582427f57e --- /dev/null +++ b/.changeset/chatty-trainers-grow.md @@ -0,0 +1,7 @@ +--- +"@medusajs/pricing": patch +"@medusajs/medusa": patch +"@medusajs/types": patch +--- + +feat(medusa,pricing,types): added get endpoints for pricing rule types diff --git a/integration-tests/modules/__tests__/pricing/admin/rule-types.spec.ts b/integration-tests/modules/__tests__/pricing/admin/rule-types.spec.ts new file mode 100644 index 0000000000..b95ce8e625 --- /dev/null +++ b/integration-tests/modules/__tests__/pricing/admin/rule-types.spec.ts @@ -0,0 +1,112 @@ +import { ModuleRegistrationName } from "@medusajs/modules-sdk" +import { IPricingModuleService, RuleTypeDTO } from "@medusajs/types" +import { medusaIntegrationTestRunner } from "medusa-test-utils" +import { createAdminUser } from "../../../../helpers/create-admin-user" + +jest.setTimeout(50000) + +const env = { MEDUSA_FF_MEDUSA_V2: true } +const adminHeaders = { headers: { "x-medusa-access-token": "test_token" } } + +medusaIntegrationTestRunner({ + env, + testSuite: ({ dbConnection, getContainer, api }) => { + describe("Admin: Pricing Rule Types API", () => { + let appContainer + let pricingModule: IPricingModuleService + let ruleTypes: RuleTypeDTO[] + + beforeAll(async () => { + appContainer = getContainer() + pricingModule = appContainer.resolve(ModuleRegistrationName.PRICING) + }) + + beforeEach(async () => { + await createAdminUser(dbConnection, adminHeaders, appContainer) + + ruleTypes = await pricingModule.createRuleTypes([ + { name: "Customer Group ID", rule_attribute: "customer_group_id" }, + { name: "Region ID", rule_attribute: "region_id" }, + ]) + }) + + describe("GET /admin/pricing", () => { + it("should get all rule types and its prices with rules", async () => { + let response = await api.get( + `/admin/pricing/rule-types`, + adminHeaders + ) + + expect(response.status).toEqual(200) + expect(response.data.count).toEqual(2) + expect(response.data.rule_types).toEqual([ + expect.objectContaining({ id: expect.any(String) }), + expect.objectContaining({ id: expect.any(String) }), + ]) + + response = await api.get( + `/admin/pricing/rule-types?fields=id,rule_attribute,created_at`, + adminHeaders + ) + + expect(response.status).toEqual(200) + expect(response.data.count).toEqual(2) + expect(response.data.rule_types).toEqual( + expect.arrayContaining([ + { + id: ruleTypes[0].id, + rule_attribute: ruleTypes[0].rule_attribute, + created_at: expect.any(String), + }, + { + id: ruleTypes[1].id, + rule_attribute: ruleTypes[1].rule_attribute, + created_at: expect.any(String), + }, + ]) + ) + }) + }) + + describe("GET /admin/pricing/:id", () => { + it("should retrieve a rule type and its prices with rules", async () => { + const ruleType = ruleTypes[0] + + let response = await api.get( + `/admin/pricing/rule-types/${ruleType.id}`, + adminHeaders + ) + + expect(response.status).toEqual(200) + expect(response.data.rule_type).toEqual( + expect.objectContaining({ + id: ruleType.id, + }) + ) + + response = await api.get( + `/admin/pricing/rule-types/${ruleType.id}?fields=id,created_at`, + adminHeaders + ) + + expect(response.data.rule_type).toEqual({ + id: ruleType.id, + created_at: expect.any(String), + }) + }) + + it("should throw an error when rule type is not found", async () => { + const error = await api + .get(`/admin/pricing/rule-types/does-not-exist`, adminHeaders) + .catch((e) => e) + + expect(error.response.status).toBe(404) + expect(error.response.data).toEqual({ + type: "not_found", + message: "RuleType with id: does-not-exist was not found", + }) + }) + }) + }) + }, +}) diff --git a/packages/medusa/src/api-v2/admin/pricing/middlewares.ts b/packages/medusa/src/api-v2/admin/pricing/middlewares.ts new file mode 100644 index 0000000000..3630c9bc5b --- /dev/null +++ b/packages/medusa/src/api-v2/admin/pricing/middlewares.ts @@ -0,0 +1,35 @@ +import { transformQuery } from "../../../api/middlewares" +import { MiddlewareRoute } from "../../../loaders/helpers/routing/types" +import { authenticate } from "../../../utils/authenticate-middleware" +import * as QueryConfig from "./query-config" +import { + AdminGetPricingRuleTypesParams, + AdminGetPricingRuleTypesRuleTypeParams, +} from "./validators" + +export const adminPricingRoutesMiddlewares: MiddlewareRoute[] = [ + { + matcher: "/admin/pricing*", + middlewares: [authenticate("admin", ["bearer", "session"])], + }, + { + method: ["GET"], + matcher: "/admin/pricing/rule-types", + middlewares: [ + transformQuery( + AdminGetPricingRuleTypesParams, + QueryConfig.listTransformQueryConfig + ), + ], + }, + { + method: ["GET"], + matcher: "/admin/pricing/rule-types/:id", + middlewares: [ + transformQuery( + AdminGetPricingRuleTypesRuleTypeParams, + QueryConfig.retrieveTransformQueryConfig + ), + ], + }, +] diff --git a/packages/medusa/src/api-v2/admin/pricing/query-config.ts b/packages/medusa/src/api-v2/admin/pricing/query-config.ts new file mode 100644 index 0000000000..b4d54f5130 --- /dev/null +++ b/packages/medusa/src/api-v2/admin/pricing/query-config.ts @@ -0,0 +1,20 @@ +export const defaultAdminPricingRuleTypeRelations = [] +export const allowedAdminPricingRuleTypeRelations = [] +export const defaultAdminPricingRuleTypeFields = [ + "id", + "name", + "rule_attribute", + "default_priority", +] + +export const retrieveTransformQueryConfig = { + defaultFields: defaultAdminPricingRuleTypeFields, + defaultRelations: defaultAdminPricingRuleTypeRelations, + allowedRelations: allowedAdminPricingRuleTypeRelations, + isList: false, +} + +export const listTransformQueryConfig = { + ...retrieveTransformQueryConfig, + isList: true, +} diff --git a/packages/medusa/src/api-v2/admin/pricing/rule-types/[id]/route.ts b/packages/medusa/src/api-v2/admin/pricing/rule-types/[id]/route.ts new file mode 100644 index 0000000000..b2f394237e --- /dev/null +++ b/packages/medusa/src/api-v2/admin/pricing/rule-types/[id]/route.ts @@ -0,0 +1,23 @@ +import { ModuleRegistrationName } from "@medusajs/modules-sdk" +import { IPricingModuleService } from "@medusajs/types" +import { + AuthenticatedMedusaRequest, + MedusaResponse, +} from "../../../../../types/routing" +import { AdminGetPricingRuleTypesRuleTypeParams } from "../../validators" + +export const GET = async ( + req: AuthenticatedMedusaRequest, + res: MedusaResponse +) => { + const pricingModule: IPricingModuleService = req.scope.resolve( + ModuleRegistrationName.PRICING + ) + + const ruleType = await pricingModule.retrieveRuleType(req.params.id, { + select: req.retrieveConfig.select, + relations: req.retrieveConfig.relations, + }) + + res.status(200).json({ rule_type: ruleType }) +} diff --git a/packages/medusa/src/api-v2/admin/pricing/rule-types/route.ts b/packages/medusa/src/api-v2/admin/pricing/rule-types/route.ts new file mode 100644 index 0000000000..60a5dcb690 --- /dev/null +++ b/packages/medusa/src/api-v2/admin/pricing/rule-types/route.ts @@ -0,0 +1,30 @@ +import { ModuleRegistrationName } from "@medusajs/modules-sdk" +import { IPricingModuleService } from "@medusajs/types" +import { + AuthenticatedMedusaRequest, + MedusaResponse, +} from "../../../../types/routing" +import { AdminGetPricingRuleTypesParams } from "../validators" + +export const GET = async ( + req: AuthenticatedMedusaRequest, + res: MedusaResponse +) => { + const pricingModule: IPricingModuleService = req.scope.resolve( + ModuleRegistrationName.PRICING + ) + + const [ruleTypes, count] = await pricingModule.listAndCountRuleTypes( + req.filterableFields, + req.listConfig + ) + + const { limit, offset } = req.validatedQuery + + res.json({ + count, + rule_types: ruleTypes, + offset, + limit, + }) +} diff --git a/packages/medusa/src/api-v2/admin/pricing/validators.ts b/packages/medusa/src/api-v2/admin/pricing/validators.ts new file mode 100644 index 0000000000..c823614d23 --- /dev/null +++ b/packages/medusa/src/api-v2/admin/pricing/validators.ts @@ -0,0 +1,12 @@ +import { IsOptional, IsString } from "class-validator" +import { FindParams, extendedFindParamsMixin } from "../../../types/common" + +export class AdminGetPricingRuleTypesRuleTypeParams extends FindParams {} +export class AdminGetPricingRuleTypesParams extends extendedFindParamsMixin({ + limit: 100, + offset: 0, +}) { + @IsString() + @IsOptional() + rule_attribute?: string[] +} diff --git a/packages/medusa/src/api-v2/middlewares.ts b/packages/medusa/src/api-v2/middlewares.ts index c65eb35198..29a1148815 100644 --- a/packages/medusa/src/api-v2/middlewares.ts +++ b/packages/medusa/src/api-v2/middlewares.ts @@ -8,6 +8,7 @@ import { adminCustomerRoutesMiddlewares } from "./admin/customers/middlewares" import { adminInviteRoutesMiddlewares } from "./admin/invites/middlewares" import { adminPaymentRoutesMiddlewares } from "./admin/payments/middlewares" import { adminPriceListsRoutesMiddlewares } from "./admin/price-lists/middlewares" +import { adminPricingRoutesMiddlewares } from "./admin/pricing/middlewares" import { adminProductRoutesMiddlewares } from "./admin/products/middlewares" import { adminPromotionRoutesMiddlewares } from "./admin/promotions/middlewares" import { adminRegionRoutesMiddlewares } from "./admin/regions/middlewares" @@ -49,5 +50,6 @@ export const config: MiddlewaresConfig = { ...adminPaymentRoutesMiddlewares, ...adminPriceListsRoutesMiddlewares, ...adminCollectionRoutesMiddlewares, + ...adminPricingRoutesMiddlewares, ], } diff --git a/packages/pricing/src/migrations/.snapshot-medusa-pricing.json b/packages/pricing/src/migrations/.snapshot-medusa-pricing.json index f9a0a7e275..6bdc010d3d 100644 --- a/packages/pricing/src/migrations/.snapshot-medusa-pricing.json +++ b/packages/pricing/src/migrations/.snapshot-medusa-pricing.json @@ -116,8 +116,7 @@ } ], "checks": [], - "foreignKeys": { - } + "foreignKeys": {} }, { "columns": { @@ -520,6 +519,28 @@ "nullable": false, "default": "0", "mappedType": "integer" + }, + "created_at": { + "name": "created_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "default": "now()", + "mappedType": "datetime" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "default": "now()", + "mappedType": "datetime" } }, "name": "rule_type", diff --git a/packages/pricing/src/migrations/Migration20230929122253.ts b/packages/pricing/src/migrations/Migration20230929122253.ts index 044bcb8da2..7a2f8665fa 100644 --- a/packages/pricing/src/migrations/Migration20230929122253.ts +++ b/packages/pricing/src/migrations/Migration20230929122253.ts @@ -24,8 +24,9 @@ export class Migration20230929122253 extends Migration { ) this.addSql( - 'create table "rule_type" ("id" text not null, "name" text not null, "rule_attribute" text not null, "default_priority" integer not null default 0, constraint "rule_type_pkey" primary key ("id"));' + 'create table "rule_type" ("id" text not null, "name" text not null, "rule_attribute" text not null, "default_priority" integer not null default 0, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), constraint "rule_type_pkey" primary key ("id"));' ) + this.addSql( 'create index "IDX_rule_type_rule_attribute" on "rule_type" ("rule_attribute");' ) diff --git a/packages/pricing/src/models/money-amount.ts b/packages/pricing/src/models/money-amount.ts index 8141b9c315..b676659f7f 100644 --- a/packages/pricing/src/models/money-amount.ts +++ b/packages/pricing/src/models/money-amount.ts @@ -6,9 +6,8 @@ import { Filter, Index, ManyToMany, - ManyToOne, - OneToOne, OnInit, + OneToOne, OptionalProps, PrimaryKey, Property, @@ -30,7 +29,11 @@ class MoneyAmount { @PrimaryKey({ columnType: "text" }) id!: string - @Property({ columnType: "text", nullable: true }) + @Property({ + columnType: "text", + nullable: true, + index: "IDX_money_amount_currency_code", + }) currency_code: string | null @ManyToMany({ diff --git a/packages/pricing/src/models/rule-type.ts b/packages/pricing/src/models/rule-type.ts index 10c2926f17..9a98395fac 100644 --- a/packages/pricing/src/models/rule-type.ts +++ b/packages/pricing/src/models/rule-type.ts @@ -32,6 +32,21 @@ class RuleType { @ManyToMany(() => PriceSet, (priceSet) => priceSet.rule_types) price_sets = new Collection(this) + @Property({ + onCreate: () => new Date(), + columnType: "timestamptz", + defaultRaw: "now()", + }) + created_at: Date + + @Property({ + onCreate: () => new Date(), + onUpdate: () => new Date(), + columnType: "timestamptz", + defaultRaw: "now()", + }) + updated_at: Date + @BeforeCreate() onCreate() { this.id = generateEntityId(this.id, "rul-typ") diff --git a/packages/pricing/src/types/services/rule-type.ts b/packages/pricing/src/types/services/rule-type.ts index cdd49fec45..b083ef3f2d 100644 --- a/packages/pricing/src/types/services/rule-type.ts +++ b/packages/pricing/src/types/services/rule-type.ts @@ -19,6 +19,15 @@ export interface RuleTypeDTO { name: string rule_attribute: string default_priority: number + /** + * The creation date of the rule type. + */ + created_at?: Date | string + + /** + * The update date of the rule type. + */ + updated_at?: Date | string } export interface FilterableRuleTypeProps diff --git a/packages/types/src/pricing/common/rule-type.ts b/packages/types/src/pricing/common/rule-type.ts index 3951543ecf..4d3e6381e5 100644 --- a/packages/types/src/pricing/common/rule-type.ts +++ b/packages/types/src/pricing/common/rule-type.ts @@ -2,7 +2,7 @@ import { BaseFilterable } from "../../dal" /** * @interface - * + * * A rule type's data. */ export interface RuleTypeDTO { @@ -15,20 +15,28 @@ export interface RuleTypeDTO { */ name: string /** - * The unique name used to later identify the rule_attribute. For example, it can be used in the `context` parameter of + * The unique name used to later identify the rule_attribute. For example, it can be used in the `context` parameter of * the `calculatePrices` method to specify a rule for calculating the price. */ rule_attribute: string /** - * The priority of the rule type. This is useful when calculating the price of a price set, and multiple rules satisfy + * The priority of the rule type. This is useful when calculating the price of a price set, and multiple rules satisfy * the provided context. The higher the value, the higher the priority of the rule type. */ default_priority: number + /** + * The creation date of the rule type. + */ + created_at?: Date | string + /** + * The update date of the rule type. + */ + updated_at?: Date | string } /** * @interface - * + * * The rule type to create. */ export interface CreateRuleTypeDTO { @@ -41,12 +49,12 @@ export interface CreateRuleTypeDTO { */ name: string /** - * The unique name used to later identify the rule_attribute. For example, it can be used in the `context` parameter of the `calculatePrices` + * The unique name used to later identify the rule_attribute. For example, it can be used in the `context` parameter of the `calculatePrices` * method to specify a rule for calculating the price. */ rule_attribute: string /** - * The priority of the rule type. This is useful when calculating the price of a price set, and multiple rules satisfy the provided context. + * The priority of the rule type. This is useful when calculating the price of a price set, and multiple rules satisfy the provided context. * The higher the value, the higher the priority of the rule type. */ default_priority?: number @@ -54,7 +62,7 @@ export interface CreateRuleTypeDTO { /** * @interface - * + * * The data to update in a rule type. The `id` is used to identify which price set to update. */ export interface UpdateRuleTypeDTO { @@ -78,7 +86,7 @@ export interface UpdateRuleTypeDTO { /** * @interface - * + * * Filters to apply on rule types. */ export interface FilterableRuleTypeProps