From 58127564d7110c674fb14fdd97fbc080afba156d Mon Sep 17 00:00:00 2001 From: Sebastian Rindom Date: Wed, 3 Nov 2021 11:24:45 +0100 Subject: [PATCH] feat(medusa): adds collection endpoints to storefront (#711) Adds: - `/store/collections/:id` - `/store/collections` --- .../store/__snapshots__/collections.js.snap | 50 ++++++++++ .../api/__tests__/store/collections.js | 98 +++++++++++++++++++ .../collections/__tests__/list-collections.js | 2 +- .../admin/collections/list-collections.js | 38 +++---- .../collections/__tests__/get-collection.js | 27 +++++ .../collections/__tests__/list-collections.js | 21 ++++ .../store/collections/get-collection.js | 33 +++++++ .../src/api/routes/store/collections/index.js | 16 +++ .../store/collections/list-collections.js | 37 +++++++ packages/medusa/src/api/routes/store/index.js | 2 + .../services/__mocks__/product-collection.js | 11 ++- .../medusa/src/services/product-collection.js | 17 +++- 12 files changed, 324 insertions(+), 28 deletions(-) create mode 100644 integration-tests/api/__tests__/store/__snapshots__/collections.js.snap create mode 100644 integration-tests/api/__tests__/store/collections.js create mode 100644 packages/medusa/src/api/routes/store/collections/__tests__/get-collection.js create mode 100644 packages/medusa/src/api/routes/store/collections/__tests__/list-collections.js create mode 100644 packages/medusa/src/api/routes/store/collections/get-collection.js create mode 100644 packages/medusa/src/api/routes/store/collections/index.js create mode 100644 packages/medusa/src/api/routes/store/collections/list-collections.js diff --git a/integration-tests/api/__tests__/store/__snapshots__/collections.js.snap b/integration-tests/api/__tests__/store/__snapshots__/collections.js.snap new file mode 100644 index 0000000000..12e685f244 --- /dev/null +++ b/integration-tests/api/__tests__/store/__snapshots__/collections.js.snap @@ -0,0 +1,50 @@ +// 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-collection", + "id": "test-collection", + "metadata": null, + "title": "Test collection", + "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-collection2", + "id": "test-collection2", + "metadata": null, + "title": "Test collection 2", + "updated_at": Any, + }, + ], + "count": 3, +} +`; + +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 new file mode 100644 index 0000000000..bebd844c58 --- /dev/null +++ b/integration-tests/api/__tests__/store/collections.js @@ -0,0 +1,98 @@ +const { ProductCollection } = require("@medusajs/medusa") +const path = require("path") +const setupServer = require("../../../helpers/setup-server") +const { useApi } = require("../../../helpers/use-api") +const { initDb, useDb } = require("../../../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, verbose: true }) + }) + + afterAll(async () => { + const db = useDb() + await db.shutdown() + medusaProcess.kill() + }) + + describe("/store/collections/:id", () => { + beforeEach(async () => { + try { + await productSeeder(dbConnection) + } catch (err) { + console.log(err) + throw err + } + }) + + 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 () => { + try { + await productSeeder(dbConnection) + } catch (err) { + console.log(err) + throw err + } + }) + + 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-collection", + 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-collection2", + created_at: expect.any(String), + updated_at: expect.any(String), + }, + ], + count: 3, + }) + }) + }) +}) diff --git a/packages/medusa/src/api/routes/admin/collections/__tests__/list-collections.js b/packages/medusa/src/api/routes/admin/collections/__tests__/list-collections.js index 1d7b82dda0..d74cc289eb 100644 --- a/packages/medusa/src/api/routes/admin/collections/__tests__/list-collections.js +++ b/packages/medusa/src/api/routes/admin/collections/__tests__/list-collections.js @@ -22,7 +22,7 @@ describe("GET /admin/collections", () => { }) it("calls product collection service list", () => { - expect(ProductCollectionServiceMock.list).toHaveBeenCalledTimes(1) + expect(ProductCollectionServiceMock.listAndCount).toHaveBeenCalledTimes(1) }) }) }) diff --git a/packages/medusa/src/api/routes/admin/collections/list-collections.js b/packages/medusa/src/api/routes/admin/collections/list-collections.js index b932d79159..5bdabf668b 100644 --- a/packages/medusa/src/api/routes/admin/collections/list-collections.js +++ b/packages/medusa/src/api/routes/admin/collections/list-collections.js @@ -18,30 +18,24 @@ import { defaultFields, defaultRelations } from "." * $ref: "#/components/schemas/product_collection" */ export default async (req, res) => { - try { - const selector = {} + const selector = {} - const limit = parseInt(req.query.limit) || 10 - const offset = parseInt(req.query.offset) || 0 + const limit = parseInt(req.query.limit) || 10 + const offset = parseInt(req.query.offset) || 0 - const productCollectionService = req.scope.resolve( - "productCollectionService" - ) + const productCollectionService = req.scope.resolve("productCollectionService") - const listConfig = { - select: defaultFields, - relations: defaultRelations, - skip: offset, - take: limit, - } - - const collections = await productCollectionService.list( - selector, - listConfig - ) - - res.status(200).json({ collections }) - } catch (err) { - throw err + const listConfig = { + select: defaultFields, + relations: defaultRelations, + skip: offset, + take: limit, } + + const [collections, count] = await productCollectionService.listAndCount( + selector, + listConfig + ) + + res.status(200).json({ collections, count }) } diff --git a/packages/medusa/src/api/routes/store/collections/__tests__/get-collection.js b/packages/medusa/src/api/routes/store/collections/__tests__/get-collection.js new file mode 100644 index 0000000000..73d56f421f --- /dev/null +++ b/packages/medusa/src/api/routes/store/collections/__tests__/get-collection.js @@ -0,0 +1,27 @@ +import { IdMap } from "medusa-test-utils" +import { request } from "../../../../../helpers/test-request" +import { ProductCollectionServiceMock } from "../../../../../services/__mocks__/product-collection" + +describe("GET /store/categories/:id", () => { + describe("get collection by id successfully", () => { + let subject + beforeAll(async () => { + subject = await request("GET", `/store/collections/${IdMap.getId("col")}`) + }) + + afterAll(() => { + jest.clearAllMocks() + }) + + it("calls retrieve from product collection service", () => { + expect(ProductCollectionServiceMock.retrieve).toHaveBeenCalledTimes(1) + expect(ProductCollectionServiceMock.retrieve).toHaveBeenCalledWith( + IdMap.getId("col") + ) + }) + + it("returns variant decorated", () => { + expect(subject.body.collection.id).toEqual(IdMap.getId("col")) + }) + }) +}) diff --git a/packages/medusa/src/api/routes/store/collections/__tests__/list-collections.js b/packages/medusa/src/api/routes/store/collections/__tests__/list-collections.js new file mode 100644 index 0000000000..84aa85ecaf --- /dev/null +++ b/packages/medusa/src/api/routes/store/collections/__tests__/list-collections.js @@ -0,0 +1,21 @@ +import { request } from "../../../../../helpers/test-request" +import { ProductCollectionServiceMock } from "../../../../../services/__mocks__/product-collection" + +describe("GET /store/collections", () => { + describe("successful retrieval", () => { + let subject + + beforeAll(async () => { + jest.clearAllMocks() + subject = await request("GET", `/store/collections`) + }) + + it("returns 200", () => { + expect(subject.status).toEqual(200) + }) + + it("calls product collection service list", () => { + expect(ProductCollectionServiceMock.listAndCount).toHaveBeenCalledTimes(1) + }) + }) +}) diff --git a/packages/medusa/src/api/routes/store/collections/get-collection.js b/packages/medusa/src/api/routes/store/collections/get-collection.js new file mode 100644 index 0000000000..e883fc4dc2 --- /dev/null +++ b/packages/medusa/src/api/routes/store/collections/get-collection.js @@ -0,0 +1,33 @@ +/** + * @oas [get] /collections/{id} + * operationId: "GetCollectionsCollection" + * summary: "Retrieve a Product Collection" + * description: "Retrieves a Product Collection." + * parameters: + * - (path) id=* {string} The id of the Product Collection + * tags: + * - Collection + * responses: + * "200": + * description: OK + * content: + * application/json: + * schema: + * properties: + * collection: + * $ref: "#/components/schemas/product_collection" + */ + +export default async (req, res) => { + const { id } = req.params + try { + const productCollectionService = req.scope.resolve( + "productCollectionService" + ) + + const collection = await productCollectionService.retrieve(id) + res.status(200).json({ collection }) + } catch (err) { + throw err + } +} diff --git a/packages/medusa/src/api/routes/store/collections/index.js b/packages/medusa/src/api/routes/store/collections/index.js new file mode 100644 index 0000000000..be9dd72c3f --- /dev/null +++ b/packages/medusa/src/api/routes/store/collections/index.js @@ -0,0 +1,16 @@ +import { Router } from "express" +import middlewares from "../../../middlewares" + +const route = Router() + +export default (app) => { + app.use("/collections", route) + + route.get("/", middlewares.wrap(require("./list-collections").default)) + route.get("/:id", middlewares.wrap(require("./get-collection").default)) + + return app +} + +export const defaultFields = ["id", "title", "handle"] +export const defaultRelations = ["products"] diff --git a/packages/medusa/src/api/routes/store/collections/list-collections.js b/packages/medusa/src/api/routes/store/collections/list-collections.js new file mode 100644 index 0000000000..e501b51b4d --- /dev/null +++ b/packages/medusa/src/api/routes/store/collections/list-collections.js @@ -0,0 +1,37 @@ +/** + * @oas [get] /collections + * operationId: "GetCollections" + * summary: "List Product Collections" + * description: "Retrieve a list of Product Collection." + * tags: + * - Collection + * responses: + * "200": + * description: OK + * content: + * application/json: + * schema: + * properties: + * collection: + * $ref: "#/components/schemas/product_collection" + */ +export default async (req, res) => { + const selector = {} + + const limit = parseInt(req.query.limit) || 10 + const offset = parseInt(req.query.offset) || 0 + + const productCollectionService = req.scope.resolve("productCollectionService") + + const listConfig = { + skip: offset, + take: limit, + } + + const [collections, count] = await productCollectionService.listAndCount( + selector, + listConfig + ) + + res.status(200).json({ collections, count }) +} diff --git a/packages/medusa/src/api/routes/store/index.js b/packages/medusa/src/api/routes/store/index.js index 413ea3237b..de95546071 100644 --- a/packages/medusa/src/api/routes/store/index.js +++ b/packages/medusa/src/api/routes/store/index.js @@ -14,6 +14,7 @@ import returnRoutes from "./returns" import returnReasonRoutes from "./return-reasons" import swapRoutes from "./swaps" import variantRoutes from "./variants" +import collectionRoutes from "./collections" import giftCardRoutes from "./gift-cards" const route = Router() @@ -32,6 +33,7 @@ export default (app, container, config) => { route.use(middlewares.authenticateCustomer()) authRoutes(route) + collectionRoutes(route) customerRoutes(route, container) productRoutes(route) orderRoutes(route) diff --git a/packages/medusa/src/services/__mocks__/product-collection.js b/packages/medusa/src/services/__mocks__/product-collection.js index e41bae350b..caf198e96e 100644 --- a/packages/medusa/src/services/__mocks__/product-collection.js +++ b/packages/medusa/src/services/__mocks__/product-collection.js @@ -1,13 +1,13 @@ import { IdMap } from "medusa-test-utils" export const ProductCollectionServiceMock = { - withTransaction: function() { + withTransaction: function () { return this }, - create: jest.fn().mockImplementation(data => { + create: jest.fn().mockImplementation((data) => { return Promise.resolve({ id: IdMap.getId("col"), ...data }) }), - retrieve: jest.fn().mockImplementation(id => { + retrieve: jest.fn().mockImplementation((id) => { if (id === IdMap.getId("col")) { return Promise.resolve({ id: IdMap.getId("col"), title: "Suits" }) } @@ -16,9 +16,12 @@ export const ProductCollectionServiceMock = { update: jest.fn().mockImplementation((id, value) => { return Promise.resolve({ id, title: value }) }), - list: jest.fn().mockImplementation(data => { + list: jest.fn().mockImplementation((data) => { return Promise.resolve([{ id: IdMap.getId("col"), title: "Suits" }]) }), + listAndCount: jest.fn().mockImplementation((data) => { + return Promise.resolve([[{ id: IdMap.getId("col"), title: "Suits" }], 1]) + }), } const mock = jest.fn().mockImplementation(() => { diff --git a/packages/medusa/src/services/product-collection.js b/packages/medusa/src/services/product-collection.js index 6cb5522ac1..77ebd5072f 100644 --- a/packages/medusa/src/services/product-collection.js +++ b/packages/medusa/src/services/product-collection.js @@ -149,7 +149,22 @@ class ProductCollectionService extends BaseService { ) const query = this.buildQuery_(selector, config) - return productCollectionRepo.find(query) + return await productCollectionRepo.find(query) + } + + /** + * Lists product collections and add count. + * @param {Object} selector - the query object for find + * @param {Object} config - the config to be used for find + * @return {Promise} the result of the find operation + */ + async listAndCount(selector = {}, config = { skip: 0, take: 20 }) { + const productCollectionRepo = this.manager_.getCustomRepository( + this.productCollectionRepository_ + ) + + const query = this.buildQuery_(selector, config) + return await productCollectionRepo.findAndCount(query) } }