From 7186943ed4006fc2c084dfde832e3b592756562a Mon Sep 17 00:00:00 2001 From: Stevche Radevski Date: Fri, 26 Apr 2024 10:37:22 +0200 Subject: [PATCH] feat: Add collection endpoints to store --- .../store/__snapshots__/collections.js.snap | 52 ----------- .../api/__tests__/store/collections.js | 90 ------------------ .../api/__tests__/store/collections.ts | 92 +++++++++++++++++++ packages/medusa/src/api-v2/middlewares.ts | 2 + .../api-v2/store/collections/[id]/route.ts | 18 ++++ .../src/api-v2/store/collections/helpers.ts | 23 +++++ .../api-v2/store/collections/middlewares.ts | 30 ++++++ .../api-v2/store/collections/query-config.ts | 18 ++++ .../src/api-v2/store/collections/route.ts | 34 +++++++ .../api-v2/store/collections/validators.ts | 28 ++++++ 10 files changed, 245 insertions(+), 142 deletions(-) delete mode 100644 integration-tests/api/__tests__/store/__snapshots__/collections.js.snap delete mode 100644 integration-tests/api/__tests__/store/collections.js create mode 100644 integration-tests/api/__tests__/store/collections.ts create mode 100644 packages/medusa/src/api-v2/store/collections/[id]/route.ts create mode 100644 packages/medusa/src/api-v2/store/collections/helpers.ts create mode 100644 packages/medusa/src/api-v2/store/collections/middlewares.ts create mode 100644 packages/medusa/src/api-v2/store/collections/query-config.ts create mode 100644 packages/medusa/src/api-v2/store/collections/route.ts create mode 100644 packages/medusa/src/api-v2/store/collections/validators.ts diff --git a/integration-tests/api/__tests__/store/__snapshots__/collections.js.snap b/integration-tests/api/__tests__/store/__snapshots__/collections.js.snap deleted file mode 100644 index 95526ab0051f..000000000000 --- a/integration-tests/api/__tests__/store/__snapshots__/collections.js.snap +++ /dev/null @@ -1,52 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`/store/collections /store/collections lists collections 1`] = ` -Object { - "collections": Array [ - Object { - "created_at": Any, - "deleted_at": null, - "handle": "test-collection2", - "id": "test-collection2", - "metadata": null, - "title": "Test collection 2", - "updated_at": Any, - }, - Object { - "created_at": Any, - "deleted_at": null, - "handle": "test-collection1", - "id": "test-collection1", - "metadata": null, - "title": "Test collection 1", - "updated_at": Any, - }, - Object { - "created_at": Any, - "deleted_at": null, - "handle": "test-collection", - "id": "test-collection", - "metadata": null, - "title": "Test collection", - "updated_at": Any, - }, - ], - "count": 3, - "limit": 10, - "offset": 0, -} -`; - -exports[`/store/collections /store/collections/:id gets collection 1`] = ` -Object { - "collection": Object { - "created_at": Any, - "deleted_at": null, - "handle": "test-collection", - "id": "test-collection", - "metadata": null, - "title": "Test collection", - "updated_at": Any, - }, -} -`; diff --git a/integration-tests/api/__tests__/store/collections.js b/integration-tests/api/__tests__/store/collections.js deleted file mode 100644 index 19432db87004..000000000000 --- a/integration-tests/api/__tests__/store/collections.js +++ /dev/null @@ -1,90 +0,0 @@ -const { ProductCollection } = require("@medusajs/medusa") -const path = require("path") -const setupServer = require("../../../environment-helpers/setup-server") -const { useApi } = require("../../../environment-helpers/use-api") -const { initDb, useDb } = require("../../../environment-helpers/use-db") - -const productSeeder = require("../../../helpers/product-seeder") - -jest.setTimeout(30000) -describe("/store/collections", () => { - let medusaProcess - let dbConnection - - beforeAll(async () => { - const cwd = path.resolve(path.join(__dirname, "..", "..")) - dbConnection = await initDb({ cwd }) - medusaProcess = await setupServer({ cwd }) - }) - - afterAll(async () => { - const db = useDb() - await db.shutdown() - medusaProcess.kill() - }) - - describe("/store/collections/:id", () => { - beforeEach(async () => { - await productSeeder(dbConnection) - }) - - afterEach(async () => { - const db = useDb() - await db.teardown() - }) - - it("gets collection", async () => { - const api = useApi() - - const response = await api.get("/store/collections/test-collection") - - expect(response.data).toMatchSnapshot({ - collection: { - id: "test-collection", - created_at: expect.any(String), - updated_at: expect.any(String), - }, - }) - }) - }) - - describe("/store/collections", () => { - beforeEach(async () => { - await productSeeder(dbConnection) - }) - - afterEach(async () => { - const db = useDb() - await db.teardown() - }) - - it("lists collections", async () => { - const api = useApi() - - const response = await api.get("/store/collections") - - expect(response.data).toMatchSnapshot({ - collections: [ - { - id: "test-collection2", - created_at: expect.any(String), - updated_at: expect.any(String), - }, - { - id: "test-collection1", - created_at: expect.any(String), - updated_at: expect.any(String), - }, - { - id: "test-collection", - created_at: expect.any(String), - updated_at: expect.any(String), - }, - ], - count: 3, - limit: 10, - offset: 0, - }) - }) - }) -}) diff --git a/integration-tests/api/__tests__/store/collections.ts b/integration-tests/api/__tests__/store/collections.ts new file mode 100644 index 000000000000..0dd40404cf39 --- /dev/null +++ b/integration-tests/api/__tests__/store/collections.ts @@ -0,0 +1,92 @@ +import { medusaIntegrationTestRunner } from "medusa-test-utils" +import { + createAdminUser, + adminHeaders, +} from "../../../helpers/create-admin-user" + +jest.setTimeout(30000) + +medusaIntegrationTestRunner({ + env: { MEDUSA_FF_PRODUCT_CATEGORIES: true }, + testSuite: ({ dbConnection, getContainer, api }) => { + let baseCollection + let baseCollection1 + let baseCollection2 + + beforeEach(async () => { + const container = getContainer() + await createAdminUser(dbConnection, adminHeaders, container) + + baseCollection = ( + await api.post( + "/admin/collections", + { title: "test-collection" }, + adminHeaders + ) + ).data.collection + + baseCollection1 = ( + await api.post( + "/admin/collections", + { title: "test-collection1" }, + adminHeaders + ) + ).data.collection + + baseCollection2 = ( + await api.post( + "/admin/collections", + { title: "test-collection2" }, + adminHeaders + ) + ).data.collection + }) + + describe("/store/collections", () => { + describe("/store/collections/:id", () => { + it("gets collection", async () => { + const response = await api.get( + `/store/collections/${baseCollection.id}` + ) + + expect(response.data.collection).toEqual( + expect.objectContaining({ + id: baseCollection.id, + created_at: expect.any(String), + updated_at: expect.any(String), + }) + ) + }) + }) + + describe("/store/collections", () => { + it("lists collections", async () => { + const response = await api.get("/store/collections") + + expect(response.data).toEqual({ + collections: [ + expect.objectContaining({ + id: baseCollection2.id, + created_at: expect.any(String), + updated_at: expect.any(String), + }), + expect.objectContaining({ + id: baseCollection1.id, + created_at: expect.any(String), + updated_at: expect.any(String), + }), + expect.objectContaining({ + id: baseCollection.id, + created_at: expect.any(String), + updated_at: expect.any(String), + }), + ], + count: 3, + limit: 10, + offset: 0, + }) + }) + }) + }) + }, +}) diff --git a/packages/medusa/src/api-v2/middlewares.ts b/packages/medusa/src/api-v2/middlewares.ts index d277b8c850b2..874e34106f90 100644 --- a/packages/medusa/src/api-v2/middlewares.ts +++ b/packages/medusa/src/api-v2/middlewares.ts @@ -34,6 +34,7 @@ import { adminWorkflowsExecutionsMiddlewares } from "./admin/workflows-execution import { authRoutesMiddlewares } from "./auth/middlewares" import { hooksRoutesMiddlewares } from "./hooks/middlewares" import { storeCartRoutesMiddlewares } from "./store/carts/middlewares" +import { storeCollectionRoutesMiddlewares } from "./store/collections/middlewares" import { storeCurrencyRoutesMiddlewares } from "./store/currencies/middlewares" import { storeCustomerRoutesMiddlewares } from "./store/customers/middlewares" import { storeProductRoutesMiddlewares } from "./store/products/middlewares" @@ -48,6 +49,7 @@ export const config: MiddlewaresConfig = { ...storeCartRoutesMiddlewares, ...storeCustomerRoutesMiddlewares, ...storeCartRoutesMiddlewares, + ...storeCollectionRoutesMiddlewares, ...authRoutesMiddlewares, ...adminWorkflowsExecutionsMiddlewares, ...storeRegionRoutesMiddlewares, diff --git a/packages/medusa/src/api-v2/store/collections/[id]/route.ts b/packages/medusa/src/api-v2/store/collections/[id]/route.ts new file mode 100644 index 000000000000..468364593b7e --- /dev/null +++ b/packages/medusa/src/api-v2/store/collections/[id]/route.ts @@ -0,0 +1,18 @@ +import { + AuthenticatedMedusaRequest, + MedusaResponse, +} from "../../../../types/routing" +import { refetchCollection } from "../helpers" + +export const GET = async ( + req: AuthenticatedMedusaRequest, + res: MedusaResponse +) => { + const collection = await refetchCollection( + req.params.id, + req.scope, + req.remoteQueryConfig.fields + ) + + res.status(200).json({ collection }) +} diff --git a/packages/medusa/src/api-v2/store/collections/helpers.ts b/packages/medusa/src/api-v2/store/collections/helpers.ts new file mode 100644 index 000000000000..e142f7f6a538 --- /dev/null +++ b/packages/medusa/src/api-v2/store/collections/helpers.ts @@ -0,0 +1,23 @@ +import { MedusaContainer } from "@medusajs/types" +import { + ContainerRegistrationKeys, + remoteQueryObjectFromString, +} from "@medusajs/utils" + +export const refetchCollection = async ( + collectionId: string, + scope: MedusaContainer, + fields: string[] +) => { + const remoteQuery = scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY) + const queryObject = remoteQueryObjectFromString({ + entryPoint: "product_collection", + variables: { + filters: { id: collectionId }, + }, + fields: fields, + }) + + const collections = await remoteQuery(queryObject) + return collections[0] +} diff --git a/packages/medusa/src/api-v2/store/collections/middlewares.ts b/packages/medusa/src/api-v2/store/collections/middlewares.ts new file mode 100644 index 000000000000..b6c54c750b43 --- /dev/null +++ b/packages/medusa/src/api-v2/store/collections/middlewares.ts @@ -0,0 +1,30 @@ +import * as QueryConfig from "./query-config" +import { MiddlewareRoute } from "../../../loaders/helpers/routing/types" +import { validateAndTransformQuery } from "../../utils/validate-query" +import { + StoreGetCollectionParams, + StoreGetCollectionsParams, +} from "./validators" + +export const storeCollectionRoutesMiddlewares: MiddlewareRoute[] = [ + { + method: ["GET"], + matcher: "/store/collections", + middlewares: [ + validateAndTransformQuery( + StoreGetCollectionsParams, + QueryConfig.listTransformQueryConfig + ), + ], + }, + { + method: ["GET"], + matcher: "/store/collections/:id", + middlewares: [ + validateAndTransformQuery( + StoreGetCollectionParams, + QueryConfig.retrieveTransformQueryConfig + ), + ], + }, +] diff --git a/packages/medusa/src/api-v2/store/collections/query-config.ts b/packages/medusa/src/api-v2/store/collections/query-config.ts new file mode 100644 index 000000000000..44d4f045ef99 --- /dev/null +++ b/packages/medusa/src/api-v2/store/collections/query-config.ts @@ -0,0 +1,18 @@ +export const defaultStoreCollectionFields = [ + "id", + "title", + "handle", + "created_at", + "updated_at", +] + +export const retrieveTransformQueryConfig = { + defaults: defaultStoreCollectionFields, + isList: false, +} + +export const listTransformQueryConfig = { + ...retrieveTransformQueryConfig, + defaultLimit: 10, + isList: true, +} diff --git a/packages/medusa/src/api-v2/store/collections/route.ts b/packages/medusa/src/api-v2/store/collections/route.ts new file mode 100644 index 000000000000..083322408472 --- /dev/null +++ b/packages/medusa/src/api-v2/store/collections/route.ts @@ -0,0 +1,34 @@ +import { + AuthenticatedMedusaRequest, + MedusaResponse, +} from "../../../types/routing" + +import { + ContainerRegistrationKeys, + remoteQueryObjectFromString, +} from "@medusajs/utils" + +export const GET = async ( + req: AuthenticatedMedusaRequest, + res: MedusaResponse +) => { + const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY) + + const query = remoteQueryObjectFromString({ + entryPoint: "product_collection", + variables: { + filters: req.filterableFields, + ...req.remoteQueryConfig.pagination, + }, + fields: req.remoteQueryConfig.fields, + }) + + const { rows: collections, metadata } = await remoteQuery(query) + + res.json({ + collections, + count: metadata.count, + offset: metadata.skip, + limit: metadata.take, + }) +} diff --git a/packages/medusa/src/api-v2/store/collections/validators.ts b/packages/medusa/src/api-v2/store/collections/validators.ts new file mode 100644 index 000000000000..efead6613e90 --- /dev/null +++ b/packages/medusa/src/api-v2/store/collections/validators.ts @@ -0,0 +1,28 @@ +import { + createFindParams, + createOperatorMap, + createSelectParams, +} from "../../utils/validators" +import { z } from "zod" + +export const StoreGetCollectionParams = createSelectParams() + +export type StoreGetCollectionsParamsType = z.infer< + typeof StoreGetCollectionsParams +> +export const StoreGetCollectionsParams = createFindParams({ + offset: 0, + limit: 10, + order: "-created_at", +}).merge( + z.object({ + q: z.string().optional(), + title: z.union([z.string(), z.array(z.string())]).optional(), + handle: z.union([z.string(), z.array(z.string())]).optional(), + created_at: createOperatorMap().optional(), + updated_at: createOperatorMap().optional(), + deleted_at: createOperatorMap().optional(), + $and: z.lazy(() => StoreGetCollectionsParams.array()).optional(), + $or: z.lazy(() => StoreGetCollectionsParams.array()).optional(), + }) +)