diff --git a/packages/medusa/src/api/routes/admin/orders/__tests__/delete-metadata.js b/packages/medusa/src/api/routes/admin/orders/__tests__/delete-metadata.js new file mode 100644 index 0000000000..cbd3452225 --- /dev/null +++ b/packages/medusa/src/api/routes/admin/orders/__tests__/delete-metadata.js @@ -0,0 +1,35 @@ +import { IdMap } from "medusa-test-utils" +import { request } from "../../../../../helpers/test-request" +import { OrderServiceMock } from "../../../../../services/__mocks__/order" + +describe("DELETE /admin/orders/:id/metadata/key", () => { + describe("successfully deletes metadata on order", () => { + let subject + + beforeAll(async () => { + subject = await request( + "DELETE", + `/admin/orders/${IdMap.getId("test-order")}/metadata/test-key`, + { + adminSession: { + jwt: { + userId: IdMap.getId("admin_user"), + }, + }, + } + ) + }) + + it("returns 200", () => { + expect(subject.status).toEqual(200) + }) + + it("calls OrderService deleteMetadata", () => { + expect(OrderServiceMock.deleteMetadata).toHaveBeenCalledTimes(1) + expect(OrderServiceMock.deleteMetadata).toHaveBeenCalledWith( + IdMap.getId("test-order"), + "test-key" + ) + }) + }) +}) diff --git a/packages/medusa/src/api/routes/admin/orders/__tests__/set-metadata.js b/packages/medusa/src/api/routes/admin/orders/__tests__/set-metadata.js new file mode 100644 index 0000000000..4d539f4f51 --- /dev/null +++ b/packages/medusa/src/api/routes/admin/orders/__tests__/set-metadata.js @@ -0,0 +1,40 @@ +import { IdMap } from "medusa-test-utils" +import { request } from "../../../../../helpers/test-request" +import { OrderServiceMock } from "../../../../../services/__mocks__/order" + +describe("POST /admin/orders/:id/metadata", () => { + describe("successfully sets metadata on order", () => { + let subject + + beforeAll(async () => { + subject = await request( + "POST", + `/admin/orders/${IdMap.getId("test-order")}/metadata`, + { + payload: { + key: "Test key", + value: "Test value", + }, + adminSession: { + jwt: { + userId: IdMap.getId("admin_user"), + }, + }, + } + ) + }) + + it("returns 200", () => { + expect(subject.status).toEqual(200) + }) + + it("calls OrderService setMetadata", () => { + expect(OrderServiceMock.setMetadata).toHaveBeenCalledTimes(1) + expect(OrderServiceMock.setMetadata).toHaveBeenCalledWith( + IdMap.getId("test-order"), + "Test key", + "Test value" + ) + }) + }) +}) diff --git a/packages/medusa/src/api/routes/admin/orders/delete-metadata.js b/packages/medusa/src/api/routes/admin/orders/delete-metadata.js new file mode 100644 index 0000000000..791e650b72 --- /dev/null +++ b/packages/medusa/src/api/routes/admin/orders/delete-metadata.js @@ -0,0 +1,13 @@ +export default async (req, res) => { + const { id, key } = req.params + + try { + const orderService = req.scope.resolve("orderService") + let order = await orderService.deleteMetadata(id, key) + order = await orderService.decorate(order, [], ["region"]) + + res.status(200).json({ order }) + } catch (err) { + throw err + } +} diff --git a/packages/medusa/src/api/routes/admin/orders/index.js b/packages/medusa/src/api/routes/admin/orders/index.js index 9be363ca9f..4f41c8805c 100644 --- a/packages/medusa/src/api/routes/admin/orders/index.js +++ b/packages/medusa/src/api/routes/admin/orders/index.js @@ -95,5 +95,21 @@ export default app => { middlewares.wrap(require("./archive-order").default) ) + /** + * Set metadata key / value pair. + */ + route.post( + "/:id/metadata", + middlewares.wrap(require("./set-metadata").default) + ) + + /** + * Delete metadata key / value pair. + */ + route.delete( + "/:id/metadata/:key", + middlewares.wrap(require("./delete-metadata").default) + ) + return app } diff --git a/packages/medusa/src/api/routes/admin/orders/set-metadata.js b/packages/medusa/src/api/routes/admin/orders/set-metadata.js new file mode 100644 index 0000000000..51c6c02c18 --- /dev/null +++ b/packages/medusa/src/api/routes/admin/orders/set-metadata.js @@ -0,0 +1,25 @@ +import { MedusaError, Validator } from "medusa-core-utils" + +export default async (req, res) => { + const { id } = req.params + + const schema = Validator.object().keys({ + key: Validator.string().required(), + value: Validator.string().required(), + }) + + const { value, error } = schema.validate(req.body) + if (error) { + throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details) + } + + try { + const orderService = req.scope.resolve("orderService") + let order = await orderService.setMetadata(id, value.key, value.value) + order = await orderService.decorate(order, [], ["region"]) + + res.status(200).json({ order }) + } catch (err) { + throw err + } +} diff --git a/packages/medusa/src/api/routes/admin/regions/delete-metadata.js b/packages/medusa/src/api/routes/admin/regions/delete-metadata.js new file mode 100644 index 0000000000..120df0e9c9 --- /dev/null +++ b/packages/medusa/src/api/routes/admin/regions/delete-metadata.js @@ -0,0 +1,12 @@ +export default async (req, res) => { + const { id, key } = req.params + + try { + const regionService = req.scope.resolve("regionService") + const region = await regionService.deleteMetadata(id, key) + + res.status(200).json({ region }) + } catch (err) { + throw err + } +} diff --git a/packages/medusa/src/api/routes/admin/regions/index.js b/packages/medusa/src/api/routes/admin/regions/index.js index 029b53194f..4618e25b88 100644 --- a/packages/medusa/src/api/routes/admin/regions/index.js +++ b/packages/medusa/src/api/routes/admin/regions/index.js @@ -52,5 +52,21 @@ export default app => { middlewares.wrap(require("./remove-fulfillment-provider").default) ) + /** + * Set metadata key / value pair. + */ + route.post( + "/:id/metadata", + middlewares.wrap(require("./set-metadata").default) + ) + + /** + * Delete metadata key / value pair. + */ + route.delete( + "/:id/metadata/:key", + middlewares.wrap(require("./delete-metadata").default) + ) + return app } diff --git a/packages/medusa/src/api/routes/admin/regions/set-metadata.js b/packages/medusa/src/api/routes/admin/regions/set-metadata.js new file mode 100644 index 0000000000..2ab68d12ba --- /dev/null +++ b/packages/medusa/src/api/routes/admin/regions/set-metadata.js @@ -0,0 +1,24 @@ +import { MedusaError, Validator } from "medusa-core-utils" + +export default async (req, res) => { + const { id } = req.params + + const schema = Validator.object().keys({ + key: Validator.string().required(), + value: Validator.string().required(), + }) + + const { value, error } = schema.validate(req.body) + if (error) { + throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details) + } + + try { + const regionService = req.scope.resolve("regionService") + const region = await regionService.setMetadata(id, value.key, value.value) + + res.status(200).json({ region }) + } catch (err) { + throw err + } +} diff --git a/packages/medusa/src/services/__mocks__/order.js b/packages/medusa/src/services/__mocks__/order.js index 8f5834e186..faeb617895 100644 --- a/packages/medusa/src/services/__mocks__/order.js +++ b/packages/medusa/src/services/__mocks__/order.js @@ -137,6 +137,18 @@ export const OrderServiceMock = { } return Promise.resolve(undefined) }), + setMetadata: jest.fn().mockImplementation((id, key, value) => { + if (id === IdMap.getId("test-order")) { + return Promise.resolve(orders.testOrder) + } + return Promise.resolve(undefined) + }), + deleteMetadata: jest.fn().mockImplementation((id, key, value) => { + if (id === IdMap.getId("test-order")) { + return Promise.resolve(orders.testOrder) + } + return Promise.resolve(undefined) + }), retrieve: jest.fn().mockImplementation(orderId => { if (orderId === IdMap.getId("test-order")) { return Promise.resolve(orders.testOrder) diff --git a/packages/medusa/src/services/region.js b/packages/medusa/src/services/region.js index 5ca6fa6eaa..c8681b9589 100644 --- a/packages/medusa/src/services/region.js +++ b/packages/medusa/src/services/region.js @@ -370,6 +370,57 @@ class RegionService extends BaseService { const final = await this.runDecorators_(decorated) return final } + + /** + * Dedicated method to set metadata for a region. + * To ensure that plugins does not overwrite each + * others metadata fields, setMetadata is provided. + * @param {string} regionId - the region to decorate. + * @param {string} key - key for metadata field + * @param {string} value - value for metadata field. + * @return {Promise} resolves to the updated result. + */ + async setMetadata(regionId, key, value) { + const validatedId = this.validateId_(regionId) + + if (typeof key !== "string") { + throw new MedusaError( + MedusaError.Types.INVALID_ARGUMENT, + "Key type is invalid. Metadata keys must be strings" + ) + } + + const keyPath = `metadata.${key}` + return this.regionModel_ + .updateOne({ _id: validatedId }, { $set: { [keyPath]: value } }) + .catch(err => { + throw new MedusaError(MedusaError.Types.DB_ERROR, err.message) + }) + } + + /** + * Dedicated method to delete metadata for an region. + * @param {string} regionId - the region to delete metadata from. + * @param {string} key - key for metadata field + * @return {Promise} resolves to the updated result. + */ + async deleteMetadata(regionId, key) { + const validatedId = this.validateId_(regionId) + + if (typeof key !== "string") { + throw new MedusaError( + MedusaError.Types.INVALID_ARGUMENT, + "Key type is invalid. Metadata keys must be strings" + ) + } + + const keyPath = `metadata.${key}` + return this.regionModel_ + .updateOne({ _id: validatedId }, { $unset: { [keyPath]: "" } }) + .catch(err => { + throw new MedusaError(MedusaError.Types.DB_ERROR, err.message) + }) + } } export default RegionService