diff --git a/.changeset/ninety-rockets-remain.md b/.changeset/ninety-rockets-remain.md new file mode 100644 index 0000000000..9b08e3ed05 --- /dev/null +++ b/.changeset/ninety-rockets-remain.md @@ -0,0 +1,6 @@ +--- +"@medusajs/core-flows": patch +"@medusajs/medusa": patch +--- + +feat(core-flows,medusa): added api + workflows for rule types CRUD diff --git a/integration-tests/modules/__tests__/pricing/admin/rule-types.spec.ts b/integration-tests/modules/__tests__/pricing/admin/rule-types.spec.ts index b95ce8e625..190840cf0c 100644 --- a/integration-tests/modules/__tests__/pricing/admin/rule-types.spec.ts +++ b/integration-tests/modules/__tests__/pricing/admin/rule-types.spec.ts @@ -107,6 +107,122 @@ medusaIntegrationTestRunner({ }) }) }) + + describe("POST /admin/pricing/rule-types", () => { + it("should throw an error if required params are not passed", async () => { + const { response } = await api + .post( + `/admin/pricing/rule-types`, + { + rule_attribute: "rule_attr_test1", + default_priority: 7, + }, + adminHeaders + ) + .catch((e) => e) + + expect(response.status).toEqual(400) + expect(response.data.message).toEqual( + "name must be a string, name should not be empty" + ) + }) + + it("should create a rule type successfully", async () => { + const response = await api.post( + `/admin/pricing/rule-types`, + { + name: "test", + rule_attribute: "rule_attr_test", + default_priority: 6, + }, + adminHeaders + ) + + expect(response.status).toEqual(200) + expect(response.data.rule_type).toEqual( + expect.objectContaining({ + id: expect.any(String), + name: "test", + rule_attribute: "rule_attr_test", + default_priority: 6, + }) + ) + }) + }) + + describe("POST /admin/pricing/rule-types/:id", () => { + it("should throw an error if id does not exist", async () => { + const { response } = await api + .post(`/admin/pricing/rule-types/does-not-exist`, {}, adminHeaders) + .catch((e) => e) + + expect(response.status).toEqual(404) + expect(response.data.message).toEqual( + `RuleType with id "does-not-exist" not found` + ) + }) + + it("should update a rule type successfully", async () => { + const [ruleType] = ruleTypes + + const response = await api.post( + `/admin/pricing/rule-types/${ruleType.id}`, + { + name: "test update", + rule_attribute: "test_update", + default_priority: 7, + }, + adminHeaders + ) + + expect(response.status).toEqual(200) + expect(response.data.rule_type).toEqual( + expect.objectContaining({ + id: expect.any(String), + name: "test update", + rule_attribute: "test_update", + default_priority: 7, + }) + ) + }) + }) + + describe("DELETE /admin/pricing/rule-types/:id", () => { + it("should delete rule type successfully", async () => { + const [ruleType] = ruleTypes + const response = await api.delete( + `/admin/pricing/rule-types/${ruleType.id}`, + adminHeaders + ) + console.log("response.data -- ", response.data) + expect(response.status).toEqual(200) + expect(response.data).toEqual({ + id: ruleType.id, + object: "rule_type", + deleted: true, + }) + + const deletedRuleTypes = await pricingModule.listRuleTypes({ + id: [ruleType.id], + }) + + expect(deletedRuleTypes.length).toEqual(0) + }) + + it("should return 200 when id does not exist", async () => { + const response = await api.delete( + `/admin/pricing/rule-types/does-not-exist`, + adminHeaders + ) + + expect(response.status).toEqual(200) + expect(response.data).toEqual({ + id: "does-not-exist", + object: "rule_type", + deleted: true, + }) + }) + }) }) }, }) diff --git a/packages/core-flows/src/index.ts b/packages/core-flows/src/index.ts index 575556cc43..b93ec6f997 100644 --- a/packages/core-flows/src/index.ts +++ b/packages/core-flows/src/index.ts @@ -6,6 +6,7 @@ export * from "./definitions" export * as Handlers from "./handlers" export * from "./invite" export * from "./payment" +export * from "./pricing" export * from "./product" export * from "./promotion" export * from "./region" diff --git a/packages/core-flows/src/pricing/index.ts b/packages/core-flows/src/pricing/index.ts new file mode 100644 index 0000000000..68de82c9f9 --- /dev/null +++ b/packages/core-flows/src/pricing/index.ts @@ -0,0 +1,2 @@ +export * from "./steps" +export * from "./workflows" diff --git a/packages/core-flows/src/pricing/steps/create-pricing-rule-types.ts b/packages/core-flows/src/pricing/steps/create-pricing-rule-types.ts new file mode 100644 index 0000000000..f3b48c80c2 --- /dev/null +++ b/packages/core-flows/src/pricing/steps/create-pricing-rule-types.ts @@ -0,0 +1,31 @@ +import { ModuleRegistrationName } from "@medusajs/modules-sdk" +import { CreateRuleTypeDTO, IPricingModuleService } from "@medusajs/types" +import { StepResponse, createStep } from "@medusajs/workflows-sdk" + +export const createPricingRuleTypesStepId = "create-pricing-rule-types" +export const createPricingRuleTypesStep = createStep( + createPricingRuleTypesStepId, + async (data: CreateRuleTypeDTO[], { container }) => { + const pricingModule = container.resolve( + ModuleRegistrationName.PRICING + ) + + const ruleTypes = await pricingModule.createRuleTypes(data) + + return new StepResponse( + ruleTypes, + ruleTypes.map((ruleType) => ruleType.id) + ) + }, + async (ruleTypeIds, { container }) => { + if (!ruleTypeIds?.length) { + return + } + + const pricingModule = container.resolve( + ModuleRegistrationName.PRICING + ) + + await pricingModule.delete(ruleTypeIds) + } +) diff --git a/packages/core-flows/src/pricing/steps/delete-pricing-rule-types.ts b/packages/core-flows/src/pricing/steps/delete-pricing-rule-types.ts new file mode 100644 index 0000000000..6ecec07a9c --- /dev/null +++ b/packages/core-flows/src/pricing/steps/delete-pricing-rule-types.ts @@ -0,0 +1,31 @@ +import { ModuleRegistrationName } from "@medusajs/modules-sdk" +import { IPricingModuleService } from "@medusajs/types" +import { StepResponse, createStep } from "@medusajs/workflows-sdk" + +export const deletePricingRuleTypesStepId = "delete-pricing-rule-types" +export const deletePricingRuleTypesStep = createStep( + deletePricingRuleTypesStepId, + async (ids: string[], { container }) => { + const pricingModule = container.resolve( + ModuleRegistrationName.PRICING + ) + + // TODO: implement soft deleting rule types + // await pricingModule.softDeleteRuleTypes(ids) + await pricingModule.deleteRuleTypes(ids) + + return new StepResponse(void 0, ids) + }, + async (ids, { container }) => { + if (!ids?.length) { + return + } + + const pricingModule = container.resolve( + ModuleRegistrationName.PRICING + ) + + // TODO: implement restoring soft deleted rule types + // await pricingModule.restoreRuleTypes(ids) + } +) diff --git a/packages/core-flows/src/pricing/steps/index.ts b/packages/core-flows/src/pricing/steps/index.ts new file mode 100644 index 0000000000..551d2fee21 --- /dev/null +++ b/packages/core-flows/src/pricing/steps/index.ts @@ -0,0 +1,3 @@ +export * from "./create-pricing-rule-types" +export * from "./delete-pricing-rule-types" +export * from "./update-pricing-rule-types" diff --git a/packages/core-flows/src/pricing/steps/update-pricing-rule-types.ts b/packages/core-flows/src/pricing/steps/update-pricing-rule-types.ts new file mode 100644 index 0000000000..44c3ea5655 --- /dev/null +++ b/packages/core-flows/src/pricing/steps/update-pricing-rule-types.ts @@ -0,0 +1,48 @@ +import { ModuleRegistrationName } from "@medusajs/modules-sdk" +import { IPricingModuleService, UpdateRuleTypeDTO } from "@medusajs/types" +import { + convertItemResponseToUpdateRequest, + getSelectsAndRelationsFromObjectArray, +} from "@medusajs/utils" +import { StepResponse, createStep } from "@medusajs/workflows-sdk" + +export const updatePricingRuleTypesStepId = "update-pricing-rule-types" +export const updatePricingRuleTypesStep = createStep( + updatePricingRuleTypesStepId, + async (data: UpdateRuleTypeDTO[], { container }) => { + const pricingModule = container.resolve( + ModuleRegistrationName.PRICING + ) + + const { selects, relations } = getSelectsAndRelationsFromObjectArray(data) + const dataBeforeUpdate = await pricingModule.listRuleTypes( + { id: data.map((d) => d.id) }, + { relations, select: selects } + ) + + const updatedRuleTypes = await pricingModule.updateRuleTypes(data) + + return new StepResponse(updatedRuleTypes, { + dataBeforeUpdate, + selects, + relations, + }) + }, + async (revertInput, { container }) => { + if (!revertInput) { + return + } + + const { dataBeforeUpdate = [], selects, relations } = revertInput + + const pricingModule = container.resolve( + ModuleRegistrationName.PRICING + ) + + await pricingModule.update( + dataBeforeUpdate.map((data) => + convertItemResponseToUpdateRequest(data, selects, relations) + ) + ) + } +) diff --git a/packages/core-flows/src/pricing/workflows/create-pricing-rule-types.ts b/packages/core-flows/src/pricing/workflows/create-pricing-rule-types.ts new file mode 100644 index 0000000000..8a605b04a1 --- /dev/null +++ b/packages/core-flows/src/pricing/workflows/create-pricing-rule-types.ts @@ -0,0 +1,13 @@ +import { CreateRuleTypeDTO, RuleTypeDTO } from "@medusajs/types" +import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk" +import { createPricingRuleTypesStep } from "../steps" + +type WorkflowInput = { data: CreateRuleTypeDTO[] } + +export const createPricingRuleTypesWorkflowId = "create-pricing-rule-types" +export const createPricingRuleTypesWorkflow = createWorkflow( + createPricingRuleTypesWorkflowId, + (input: WorkflowData): WorkflowData => { + return createPricingRuleTypesStep(input.data) + } +) diff --git a/packages/core-flows/src/pricing/workflows/delete-pricing-rule-types.ts b/packages/core-flows/src/pricing/workflows/delete-pricing-rule-types.ts new file mode 100644 index 0000000000..e4e7e03318 --- /dev/null +++ b/packages/core-flows/src/pricing/workflows/delete-pricing-rule-types.ts @@ -0,0 +1,12 @@ +import { createWorkflow, WorkflowData } from "@medusajs/workflows-sdk" +import { deletePricingRuleTypesStep } from "../steps" + +type WorkflowInput = { ids: string[] } + +export const deletePricingRuleTypesWorkflowId = "delete-pricing-rule-types" +export const deletePricingRuleTypesWorkflow = createWorkflow( + deletePricingRuleTypesWorkflowId, + (input: WorkflowData): WorkflowData => { + deletePricingRuleTypesStep(input.ids) + } +) diff --git a/packages/core-flows/src/pricing/workflows/index.ts b/packages/core-flows/src/pricing/workflows/index.ts new file mode 100644 index 0000000000..551d2fee21 --- /dev/null +++ b/packages/core-flows/src/pricing/workflows/index.ts @@ -0,0 +1,3 @@ +export * from "./create-pricing-rule-types" +export * from "./delete-pricing-rule-types" +export * from "./update-pricing-rule-types" diff --git a/packages/core-flows/src/pricing/workflows/update-pricing-rule-types.ts b/packages/core-flows/src/pricing/workflows/update-pricing-rule-types.ts new file mode 100644 index 0000000000..d2f4e358ff --- /dev/null +++ b/packages/core-flows/src/pricing/workflows/update-pricing-rule-types.ts @@ -0,0 +1,13 @@ +import { RuleTypeDTO, UpdateRuleTypeDTO } from "@medusajs/types" +import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk" +import { updatePricingRuleTypesStep } from "../steps" + +type WorkflowInput = { data: UpdateRuleTypeDTO[] } + +export const updatePricingRuleTypesWorkflowId = "update-pricing-rule-types" +export const updatePricingRuleTypesWorkflow = createWorkflow( + updatePricingRuleTypesWorkflowId, + (input: WorkflowData): WorkflowData => { + return updatePricingRuleTypesStep(input.data) + } +) diff --git a/packages/medusa/src/api-v2/admin/pricing/middlewares.ts b/packages/medusa/src/api-v2/admin/pricing/middlewares.ts index 3630c9bc5b..6745dace7d 100644 --- a/packages/medusa/src/api-v2/admin/pricing/middlewares.ts +++ b/packages/medusa/src/api-v2/admin/pricing/middlewares.ts @@ -1,10 +1,13 @@ -import { transformQuery } from "../../../api/middlewares" +import { transformBody, transformQuery } from "../../../api/middlewares" import { MiddlewareRoute } from "../../../loaders/helpers/routing/types" import { authenticate } from "../../../utils/authenticate-middleware" import * as QueryConfig from "./query-config" import { + AdminDeletePricingRuleTypesRuleTypeReq, AdminGetPricingRuleTypesParams, AdminGetPricingRuleTypesRuleTypeParams, + AdminPostPricingRuleTypesReq, + AdminPostPricingRuleTypesRuleTypeReq, } from "./validators" export const adminPricingRoutesMiddlewares: MiddlewareRoute[] = [ @@ -22,6 +25,11 @@ export const adminPricingRoutesMiddlewares: MiddlewareRoute[] = [ ), ], }, + { + method: ["POST"], + matcher: "/admin/pricing/rule-types", + middlewares: [transformBody(AdminPostPricingRuleTypesReq)], + }, { method: ["GET"], matcher: "/admin/pricing/rule-types/:id", @@ -32,4 +40,14 @@ export const adminPricingRoutesMiddlewares: MiddlewareRoute[] = [ ), ], }, + { + method: ["POST"], + matcher: "/admin/pricing/rule-types/:id", + middlewares: [transformBody(AdminPostPricingRuleTypesRuleTypeReq)], + }, + { + method: ["DELETE"], + matcher: "/admin/pricing/rule-types/:id", + middlewares: [transformBody(AdminDeletePricingRuleTypesRuleTypeReq)], + }, ] 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 index b2f394237e..42c93829ef 100644 --- 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 @@ -1,10 +1,20 @@ +import { + deletePricingRuleTypesWorkflow, + updatePricingRuleTypesWorkflow, +} from "@medusajs/core-flows" import { ModuleRegistrationName } from "@medusajs/modules-sdk" import { IPricingModuleService } from "@medusajs/types" import { AuthenticatedMedusaRequest, MedusaResponse, } from "../../../../../types/routing" -import { AdminGetPricingRuleTypesRuleTypeParams } from "../../validators" +import { cleanResponseData } from "../../../../../utils/clean-response-data" +import { defaultAdminPricingRuleTypeFields } from "../../query-config" +import { + AdminDeletePricingRuleTypesRuleTypeReq, + AdminGetPricingRuleTypesRuleTypeParams, + AdminPostPricingRuleTypesRuleTypeReq, +} from "../../validators" export const GET = async ( req: AuthenticatedMedusaRequest, @@ -21,3 +31,47 @@ export const GET = async ( res.status(200).json({ rule_type: ruleType }) } + +export const POST = async ( + req: AuthenticatedMedusaRequest, + res: MedusaResponse +) => { + const workflow = updatePricingRuleTypesWorkflow(req.scope) + const { result, errors } = await workflow.run({ + input: { + data: [{ ...req.validatedBody, id: req.params.id }], + }, + throwOnError: false, + }) + + if (Array.isArray(errors) && errors[0]) { + throw errors[0].error + } + + res.status(200).json({ + rule_type: cleanResponseData(result[0], defaultAdminPricingRuleTypeFields), + }) +} + +export const DELETE = async ( + req: AuthenticatedMedusaRequest, + res: MedusaResponse +) => { + const id = req.params.id + const workflow = deletePricingRuleTypesWorkflow(req.scope) + + const { errors } = await workflow.run({ + input: { ids: [id] }, + throwOnError: false, + }) + + if (Array.isArray(errors) && errors[0]) { + throw errors[0].error + } + + res.status(200).json({ + id, + object: "rule_type", + deleted: true, + }) +} 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 index 60a5dcb690..aa6e949b05 100644 --- a/packages/medusa/src/api-v2/admin/pricing/rule-types/route.ts +++ b/packages/medusa/src/api-v2/admin/pricing/rule-types/route.ts @@ -1,10 +1,16 @@ +import { createPricingRuleTypesWorkflow } from "@medusajs/core-flows" import { ModuleRegistrationName } from "@medusajs/modules-sdk" import { IPricingModuleService } from "@medusajs/types" import { AuthenticatedMedusaRequest, MedusaResponse, } from "../../../../types/routing" -import { AdminGetPricingRuleTypesParams } from "../validators" +import { cleanResponseData } from "../../../../utils/clean-response-data" +import { defaultAdminPricingRuleTypeFields } from "../query-config" +import { + AdminGetPricingRuleTypesParams, + AdminPostPricingRuleTypesReq, +} from "../validators" export const GET = async ( req: AuthenticatedMedusaRequest, @@ -28,3 +34,24 @@ export const GET = async ( limit, }) } + +export const POST = async ( + req: AuthenticatedMedusaRequest, + res: MedusaResponse +) => { + const workflow = createPricingRuleTypesWorkflow(req.scope) + const ruleTypesData = [req.validatedBody] + + const { result, errors } = await workflow.run({ + input: { data: ruleTypesData }, + throwOnError: false, + }) + + if (Array.isArray(errors) && errors[0]) { + throw errors[0].error + } + + res.status(200).json({ + rule_type: cleanResponseData(result[0], defaultAdminPricingRuleTypeFields), + }) +} diff --git a/packages/medusa/src/api-v2/admin/pricing/validators.ts b/packages/medusa/src/api-v2/admin/pricing/validators.ts index c823614d23..d5c88ae2e4 100644 --- a/packages/medusa/src/api-v2/admin/pricing/validators.ts +++ b/packages/medusa/src/api-v2/admin/pricing/validators.ts @@ -1,4 +1,4 @@ -import { IsOptional, IsString } from "class-validator" +import { IsNotEmpty, IsNumber, IsOptional, IsString } from "class-validator" import { FindParams, extendedFindParamsMixin } from "../../../types/common" export class AdminGetPricingRuleTypesRuleTypeParams extends FindParams {} @@ -10,3 +10,33 @@ export class AdminGetPricingRuleTypesParams extends extendedFindParamsMixin({ @IsOptional() rule_attribute?: string[] } + +export class AdminPostPricingRuleTypesReq { + @IsNotEmpty() + @IsString() + name: string + + @IsNotEmpty() + @IsString() + rule_attribute: string + + @IsNotEmpty() + @IsNumber() + default_priority: number +} + +export class AdminPostPricingRuleTypesRuleTypeReq { + @IsOptional() + @IsString() + name?: string + + @IsOptional() + @IsString() + rule_attribute?: string + + @IsOptional() + @IsNumber() + default_priority?: number +} + +export class AdminDeletePricingRuleTypesRuleTypeReq {}