diff --git a/integration-tests/plugins/__tests__/cart/store/create-cart.ts b/integration-tests/plugins/__tests__/cart/store/create-cart.ts new file mode 100644 index 0000000000..b41e3c8a9c --- /dev/null +++ b/integration-tests/plugins/__tests__/cart/store/create-cart.ts @@ -0,0 +1,59 @@ +import { ModuleRegistrationName } from "@medusajs/modules-sdk" +import { ICartModuleService } from "@medusajs/types" +import path from "path" +import { startBootstrapApp } from "../../../../environment-helpers/bootstrap-app" +import { useApi } from "../../../../environment-helpers/use-api" +import { getContainer } from "../../../../environment-helpers/use-container" +import { initDb, useDb } from "../../../../environment-helpers/use-db" +import adminSeeder from "../../../../helpers/admin-seeder" + +jest.setTimeout(50000) + +const env = { MEDUSA_FF_MEDUSA_V2: true } + +describe("POST /store/carts", () => { + let dbConnection + let appContainer + let shutdownServer + let cartModuleService: ICartModuleService + + beforeAll(async () => { + const cwd = path.resolve(path.join(__dirname, "..", "..", "..")) + dbConnection = await initDb({ cwd, env } as any) + shutdownServer = await startBootstrapApp({ cwd, env }) + appContainer = getContainer() + cartModuleService = appContainer.resolve(ModuleRegistrationName.CART) + }) + + afterAll(async () => { + const db = useDb() + await db.shutdown() + await shutdownServer() + }) + + beforeEach(async () => { + await adminSeeder(dbConnection) + }) + + afterEach(async () => { + const db = useDb() + await db.teardown() + }) + + it("should create a cart", async () => { + const api = useApi() as any + const response = await api.post(`/store/carts`, { + email: "tony@stark.com", + currency_code: "usd", + }) + + expect(response.status).toEqual(200) + expect(response.data.cart).toEqual( + expect.objectContaining({ + id: response.data.cart.id, + currency_code: "usd", + email: "tony@stark.com", + }) + ) + }) +}) diff --git a/integration-tests/plugins/__tests__/customer-group/admin/batch-add-customers.ts b/integration-tests/plugins/__tests__/customer-group/admin/batch-add-customers.ts index ed0efa1cce..d6fa38668d 100644 --- a/integration-tests/plugins/__tests__/customer-group/admin/batch-add-customers.ts +++ b/integration-tests/plugins/__tests__/customer-group/admin/batch-add-customers.ts @@ -7,6 +7,8 @@ import { getContainer } from "../../../../environment-helpers/use-container" import { initDb, useDb } from "../../../../environment-helpers/use-db" import adminSeeder from "../../../../helpers/admin-seeder" +jest.setTimeout(50000) + const env = { MEDUSA_FF_MEDUSA_V2: true } const adminHeaders = { headers: { "x-medusa-access-token": "test_token" }, diff --git a/integration-tests/plugins/__tests__/customer-group/admin/batch-remove-customers.ts b/integration-tests/plugins/__tests__/customer-group/admin/batch-remove-customers.ts index a366dcd05f..1b31e38812 100644 --- a/integration-tests/plugins/__tests__/customer-group/admin/batch-remove-customers.ts +++ b/integration-tests/plugins/__tests__/customer-group/admin/batch-remove-customers.ts @@ -7,6 +7,8 @@ import { getContainer } from "../../../../environment-helpers/use-container" import { initDb, useDb } from "../../../../environment-helpers/use-db" import adminSeeder from "../../../../helpers/admin-seeder" +jest.setTimeout(50000) + const env = { MEDUSA_FF_MEDUSA_V2: true } const adminHeaders = { headers: { "x-medusa-access-token": "test_token" }, diff --git a/integration-tests/plugins/__tests__/customer-group/admin/list-customer-group-customers.ts b/integration-tests/plugins/__tests__/customer-group/admin/list-customer-group-customers.ts index 453170e8e1..ac9dea9a60 100644 --- a/integration-tests/plugins/__tests__/customer-group/admin/list-customer-group-customers.ts +++ b/integration-tests/plugins/__tests__/customer-group/admin/list-customer-group-customers.ts @@ -7,6 +7,8 @@ import { getContainer } from "../../../../environment-helpers/use-container" import { initDb, useDb } from "../../../../environment-helpers/use-db" import adminSeeder from "../../../../helpers/admin-seeder" +jest.setTimeout(50000) + const env = { MEDUSA_FF_MEDUSA_V2: true } const adminHeaders = { headers: { "x-medusa-access-token": "test_token" }, diff --git a/packages/cart/integration-tests/__tests__/services/cart-module/index.spec.ts b/packages/cart/integration-tests/__tests__/services/cart-module/index.spec.ts index 2529a00824..ac66ef2d20 100644 --- a/packages/cart/integration-tests/__tests__/services/cart-module/index.spec.ts +++ b/packages/cart/integration-tests/__tests__/services/cart-module/index.spec.ts @@ -5,7 +5,7 @@ import { initModules } from "medusa-test-utils" import { MikroOrmWrapper } from "../../../utils" import { getInitModuleConfig } from "../../../utils/get-init-module-config" -jest.setTimeout(30000) +jest.setTimeout(50000) describe("Cart Module Service", () => { let service: ICartModuleService diff --git a/packages/core-flows/src/definition/cart/index.ts b/packages/core-flows/src/definition/cart/index.ts index 557e81b94e..c63ebfd8c0 100644 --- a/packages/core-flows/src/definition/cart/index.ts +++ b/packages/core-flows/src/definition/cart/index.ts @@ -1 +1,4 @@ export * from "./create-cart" +export * from "./steps" +export * from "./workflows" + diff --git a/packages/core-flows/src/definition/cart/steps/create-carts.ts b/packages/core-flows/src/definition/cart/steps/create-carts.ts new file mode 100644 index 0000000000..6f06cfe913 --- /dev/null +++ b/packages/core-flows/src/definition/cart/steps/create-carts.ts @@ -0,0 +1,31 @@ +import { ModuleRegistrationName } from "@medusajs/modules-sdk" +import { CreateCartDTO, ICartModuleService } from "@medusajs/types" +import { StepResponse, createStep } from "@medusajs/workflows-sdk" + +export const createCartsStepId = "create-carts" +export const createCartsStep = createStep( + createCartsStepId, + async (data: CreateCartDTO[], { container }) => { + const service = container.resolve( + ModuleRegistrationName.CART + ) + + const createdCarts = await service.create(data) + + return new StepResponse( + createdCarts, + createdCarts.map((cart) => cart.id) + ) + }, + async (createdCartsIds, { container }) => { + if (!createdCartsIds?.length) { + return + } + + const service = container.resolve( + ModuleRegistrationName.CART + ) + + await service.delete(createdCartsIds) + } +) diff --git a/packages/core-flows/src/definition/cart/steps/index.ts b/packages/core-flows/src/definition/cart/steps/index.ts new file mode 100644 index 0000000000..3d037035be --- /dev/null +++ b/packages/core-flows/src/definition/cart/steps/index.ts @@ -0,0 +1,2 @@ +export * from "./create-carts"; + diff --git a/packages/core-flows/src/definition/cart/workflows/create-carts.ts b/packages/core-flows/src/definition/cart/workflows/create-carts.ts new file mode 100644 index 0000000000..ce991eead8 --- /dev/null +++ b/packages/core-flows/src/definition/cart/workflows/create-carts.ts @@ -0,0 +1,13 @@ +import { CartDTO, CreateCartDTO } from "@medusajs/types" +import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk" +import { createCartsStep } from "../steps" + +type WorkflowInput = { cartData: CreateCartDTO[] } + +export const createCartsWorkflowId = "create-carts" +export const createCartsWorkflow = createWorkflow( + createCartsWorkflowId, + (input: WorkflowData): WorkflowData => { + return createCartsStep(input.cartData) + } +) diff --git a/packages/core-flows/src/definition/cart/workflows/index.ts b/packages/core-flows/src/definition/cart/workflows/index.ts new file mode 100644 index 0000000000..3d037035be --- /dev/null +++ b/packages/core-flows/src/definition/cart/workflows/index.ts @@ -0,0 +1,2 @@ +export * from "./create-carts"; + diff --git a/packages/medusa/src/api-v2/store/carts/middlewares.ts b/packages/medusa/src/api-v2/store/carts/middlewares.ts index 2147701191..769d818202 100644 --- a/packages/medusa/src/api-v2/store/carts/middlewares.ts +++ b/packages/medusa/src/api-v2/store/carts/middlewares.ts @@ -1,7 +1,7 @@ -import { transformQuery } from "../../../api/middlewares" +import { transformBody, transformQuery } from "../../../api/middlewares" import { MiddlewareRoute } from "../../../loaders/helpers/routing/types" import * as QueryConfig from "./query-config" -import { StoreGetCartsCartParams } from "./validators" +import { StoreGetCartsCartParams, StorePostCartReq } from "./validators" export const storeCartRoutesMiddlewares: MiddlewareRoute[] = [ { @@ -14,4 +14,9 @@ export const storeCartRoutesMiddlewares: MiddlewareRoute[] = [ ), ], }, + { + method: ["POST"], + matcher: "/store/carts", + middlewares: [transformBody(StorePostCartReq)], + }, ] diff --git a/packages/medusa/src/api-v2/store/carts/route.ts b/packages/medusa/src/api-v2/store/carts/route.ts new file mode 100644 index 0000000000..ff46b02506 --- /dev/null +++ b/packages/medusa/src/api-v2/store/carts/route.ts @@ -0,0 +1,23 @@ +import { createCartsWorkflow } from "@medusajs/core-flows" +import { CreateCartDTO } from "@medusajs/types" +import { MedusaRequest, MedusaResponse } from "../../../types/routing" + +export const POST = async (req: MedusaRequest, res: MedusaResponse) => { + const createCartWorkflow = createCartsWorkflow(req.scope) + const cartData = [ + { + ...(req.validatedBody as CreateCartDTO), + }, + ] + + const { result, errors } = await createCartWorkflow.run({ + input: { cartData }, + throwOnError: false, + }) + + if (Array.isArray(errors) && errors[0]) { + throw errors[0].error + } + + res.status(200).json({ cart: result[0] }) +} diff --git a/packages/medusa/src/api-v2/store/carts/validators.ts b/packages/medusa/src/api-v2/store/carts/validators.ts index 3dff03e7e8..88a841ea76 100644 --- a/packages/medusa/src/api-v2/store/carts/validators.ts +++ b/packages/medusa/src/api-v2/store/carts/validators.ts @@ -1,3 +1,55 @@ +import { Type } from "class-transformer" +import { + IsArray, + IsInt, + IsNotEmpty, + IsObject, + IsOptional, + IsString, + ValidateNested, +} from "class-validator" import { FindParams } from "../../../types/common" export class StoreGetCartsCartParams extends FindParams {} + +export class Item { + @IsNotEmpty() + @IsString() + variant_id: string + + @IsNotEmpty() + @IsInt() + quantity: number +} + +export class StorePostCartReq { + @IsOptional() + @IsString() + region_id?: string + + @IsOptional() + @IsString() + customer_id?: string + + @IsOptional() + @IsString() + email?: string + + @IsOptional() + @IsString() + currency_code?: string + + @IsOptional() + @IsArray() + @ValidateNested({ each: true }) + @Type(() => Item) + items?: Item[] + + @IsString() + @IsOptional() + sales_channel_id?: string + + @IsObject() + @IsOptional() + metadata?: Record +}