From 6579c13111b4bf5edb87380fdd701eb25dfef65d Mon Sep 17 00:00:00 2001 From: Oliver Windall Juhl <59018053+olivermrbl@users.noreply.github.com> Date: Tue, 7 Dec 2021 17:07:23 +0100 Subject: [PATCH] feat: Admin shipping options routes to Typescript (#891) --- .../api/__tests__/admin/shipping-options.js | 91 ++++++++++++++++ .../api/helpers/shipping-option-seeder.js | 24 +++++ integration-tests/api/package.json | 6 +- integration-tests/api/yarn.lock | 70 ++++++------ .../__tests__/create-shipping-option.js | 7 +- ...ng-option.js => create-shipping-option.ts} | 101 +++++++++++++----- ...ng-option.js => delete-shipping-option.ts} | 1 + ...pping-option.js => get-shipping-option.ts} | 1 + .../shipping-options/{index.js => index.ts} | 18 ++++ .../shipping-options/list-shipping-options.js | 33 ------ .../shipping-options/list-shipping-options.ts | 75 +++++++++++++ ...ng-option.js => update-shipping-option.ts} | 76 +++++++++---- .../medusa/src/utils/validators/is-boolean.ts | 14 +++ 13 files changed, 393 insertions(+), 124 deletions(-) rename packages/medusa/src/api/routes/admin/shipping-options/{create-shipping-option.js => create-shipping-option.ts} (64%) rename packages/medusa/src/api/routes/admin/shipping-options/{delete-shipping-option.js => delete-shipping-option.ts} (97%) rename packages/medusa/src/api/routes/admin/shipping-options/{get-shipping-option.js => get-shipping-option.ts} (96%) rename packages/medusa/src/api/routes/admin/shipping-options/{index.js => index.ts} (64%) delete mode 100644 packages/medusa/src/api/routes/admin/shipping-options/list-shipping-options.js create mode 100644 packages/medusa/src/api/routes/admin/shipping-options/list-shipping-options.ts rename packages/medusa/src/api/routes/admin/shipping-options/{update-shipping-option.js => update-shipping-option.ts} (58%) create mode 100644 packages/medusa/src/utils/validators/is-boolean.ts diff --git a/integration-tests/api/__tests__/admin/shipping-options.js b/integration-tests/api/__tests__/admin/shipping-options.js index 4b0f99f8d5..7c77c3cd89 100644 --- a/integration-tests/api/__tests__/admin/shipping-options.js +++ b/integration-tests/api/__tests__/admin/shipping-options.js @@ -383,4 +383,95 @@ describe("/admin/shipping-options", () => { } }) }) + describe("GET /admin/shipping-options", () => { + beforeEach(async () => { + try { + await adminSeeder(dbConnection) + await shippingOptionSeeder(dbConnection) + } catch (err) { + console.error(err) + throw err + } + }) + + afterEach(async () => { + const db = useDb() + await db.teardown() + }) + + it("lists shipping options", async () => { + const api = useApi() + const res = await api.get(`/admin/shipping-options`, { + headers: { + Authorization: "Bearer test_token", + }, + }) + + expect(res.status).toEqual(200) + }) + + it("lists admin only shipping options", async () => { + const api = useApi() + const res = await api.get(`/admin/shipping-options?admin_only=true`, { + headers: { + Authorization: "Bearer test_token", + }, + }) + + expect(res.status).toEqual(200) + expect(res.data.shipping_options).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + id: "test-option-req-admin-only", + admin_only: true, + }), + ]) + ) + }) + + it("lists return shipping options", async () => { + const api = useApi() + const res = await api.get(`/admin/shipping-options?is_return=true`, { + headers: { + Authorization: "Bearer test_token", + }, + }) + + expect(res.status).toEqual(200) + expect(res.data.shipping_options).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + id: "test-option-req-return", + is_return: true, + }), + ]) + ) + }) + + it("lists shipping options without return and admin options", async () => { + const api = useApi() + const res = await api.get( + `/admin/shipping-options?is_return=false&admin_only=true`, + { + headers: { + Authorization: "Bearer test_token", + }, + } + ) + + expect(res.status).toEqual(200) + expect(res.data.shipping_options).not.toEqual( + expect.arrayContaining([ + expect.objectContaining({ + id: "test-option-req-return", + is_return: true, + }), + expect.objectContaining({ + id: "test-option-req-admin-only", + admin_only: true, + }), + ]) + ) + }) + }) }) diff --git a/integration-tests/api/helpers/shipping-option-seeder.js b/integration-tests/api/helpers/shipping-option-seeder.js index 4048169109..33d669ca23 100644 --- a/integration-tests/api/helpers/shipping-option-seeder.js +++ b/integration-tests/api/helpers/shipping-option-seeder.js @@ -43,6 +43,30 @@ module.exports = async (connection, data = {}) => { is_return: false, }) + await manager.insert(ShippingOption, { + id: "test-option-req-admin-only", + name: "With req", + profile_id: defaultProfile.id, + region_id: "region", + admin_only: true, + provider_id: "test-ful", + data: {}, + price_type: "flat_rate", + amount: 2000, + is_return: false, + }) + await manager.insert(ShippingOption, { + id: "test-option-req-return", + name: "With req", + profile_id: defaultProfile.id, + region_id: "region", + is_return: true, + provider_id: "test-ful", + data: {}, + price_type: "flat_rate", + amount: 2000, + }) + await manager.insert(ShippingOptionRequirement, { id: "option-req", shipping_option_id: "test-option-req", diff --git a/integration-tests/api/package.json b/integration-tests/api/package.json index b05a4a963c..dfd998c940 100644 --- a/integration-tests/api/package.json +++ b/integration-tests/api/package.json @@ -8,15 +8,15 @@ "build": "babel src -d dist --extensions \".ts,.js\"" }, "dependencies": { - "@medusajs/medusa": "1.1.57-dev-1638295280349", - "medusa-interfaces": "1.1.31-dev-1638295280349", + "@medusajs/medusa": "1.1.57-dev-1638874121913", + "medusa-interfaces": "1.1.31-dev-1638874121913", "typeorm": "^0.2.31" }, "devDependencies": { "@babel/cli": "^7.12.10", "@babel/core": "^7.12.10", "@babel/node": "^7.12.10", - "babel-preset-medusa-package": "1.1.18-dev-1638295280349", + "babel-preset-medusa-package": "1.1.18-dev-1638874121913", "jest": "^26.6.3" } } diff --git a/integration-tests/api/yarn.lock b/integration-tests/api/yarn.lock index 775f3f6419..9a9f50ebab 100644 --- a/integration-tests/api/yarn.lock +++ b/integration-tests/api/yarn.lock @@ -1256,10 +1256,10 @@ "@types/yargs" "^15.0.0" chalk "^4.0.0" -"@medusajs/medusa-cli@1.1.23-dev-1638295280349": - version "1.1.23-dev-1638295280349" - resolved "http://localhost:4873/@medusajs%2fmedusa-cli/-/medusa-cli-1.1.23-dev-1638295280349.tgz#65fb12a6d63a0132f93547373344f2352d184a13" - integrity sha512-EcnaL44hGs7vlC7vujPeebzx0HK3nj4ADikoAsrXeaulzGOcAaqrHSgMC8g/pz3f8vDFdimm1tqemWL+Zr1ijQ== +"@medusajs/medusa-cli@1.1.23-dev-1638874121913": + version "1.1.23-dev-1638874121913" + resolved "http://localhost:4873/@medusajs%2fmedusa-cli/-/medusa-cli-1.1.23-dev-1638874121913.tgz#6f12e4f54c08f4846ce3c213fb3407833b1febec" + integrity sha512-ZhO3UhIdyjKOoqjfWn09wmV7Vk2A52qOE3JwLvJkyiPVt3tO23/2H7DHF93faYtrKjBCo32xSLBuZYqsRm6p+w== dependencies: "@babel/polyfill" "^7.8.7" "@babel/runtime" "^7.9.6" @@ -1277,8 +1277,8 @@ is-valid-path "^0.1.1" joi-objectid "^3.0.1" meant "^1.0.1" - medusa-core-utils "1.1.30-dev-1638295280349" - medusa-telemetry "0.0.10-dev-1638295280349" + medusa-core-utils "1.1.30-dev-1638874121913" + medusa-telemetry "0.0.10-dev-1638874121913" netrc-parser "^3.1.6" open "^8.0.6" ora "^5.4.1" @@ -1292,13 +1292,13 @@ winston "^3.3.3" yargs "^15.3.1" -"@medusajs/medusa@1.1.57-dev-1638295280349": - version "1.1.57-dev-1638295280349" - resolved "http://localhost:4873/@medusajs%2fmedusa/-/medusa-1.1.57-dev-1638295280349.tgz#ea276c471ca79db87ab44e761c22f6c68068e39a" - integrity sha512-xAY1NO/7i6hinYkUWXtyH7ievW6oY0WJ3ZPN6q3vQBs8wsaysmsev1CHpjq7pZZJ6JUNGL39m4wTwUnCMQ+stw== +"@medusajs/medusa@1.1.57-dev-1638874121913": + version "1.1.57-dev-1638874121913" + resolved "http://localhost:4873/@medusajs%2fmedusa/-/medusa-1.1.57-dev-1638874121913.tgz#c4a40d588a2567a6f80a84b8017dd64382e224bc" + integrity sha512-WSTL4U3TnB7u8YjaqaEnfjQQCmQMpZIqAwRA2+1SuaRrW3A3SYbxoRkL/3bgH13KGv/PCBAvkV1e7l9xcziAnQ== dependencies: "@hapi/joi" "^16.1.8" - "@medusajs/medusa-cli" "1.1.23-dev-1638295280349" + "@medusajs/medusa-cli" "1.1.23-dev-1638874121913" "@types/lodash" "^4.14.168" awilix "^4.2.3" body-parser "^1.19.0" @@ -1322,8 +1322,8 @@ joi "^17.3.0" joi-objectid "^3.0.1" jsonwebtoken "^8.5.1" - medusa-core-utils "1.1.30-dev-1638295280349" - medusa-test-utils "1.1.33-dev-1638295280349" + medusa-core-utils "1.1.30-dev-1638874121913" + medusa-test-utils "1.1.33-dev-1638874121913" morgan "^1.9.1" multer "^1.4.2" passport "^0.4.0" @@ -1947,10 +1947,10 @@ babel-preset-jest@^26.6.2: babel-plugin-jest-hoist "^26.6.2" babel-preset-current-node-syntax "^1.0.0" -babel-preset-medusa-package@1.1.18-dev-1638295280349: - version "1.1.18-dev-1638295280349" - resolved "http://localhost:4873/babel-preset-medusa-package/-/babel-preset-medusa-package-1.1.18-dev-1638295280349.tgz#fa9b9b75642d40b0235f1fbc06f8bea1a166c600" - integrity sha512-hfj+GRAzaG8GY3tBbq7y+Hpcf4CkCp3/GsdbxWjJYdWirBOLgRKxaOip8A9mEtxPk8DEFQfsgMyyJnraRpVSZw== +babel-preset-medusa-package@1.1.18-dev-1638874121913: + version "1.1.18-dev-1638874121913" + resolved "http://localhost:4873/babel-preset-medusa-package/-/babel-preset-medusa-package-1.1.18-dev-1638874121913.tgz#d50b513bbbfe691318c9b9463c7ec0c0c1ca8711" + integrity sha512-FDJyHCp+FyncJDlapHIwxHRUkM/RCxKy7+XIMmlJRGsiF6a7KGjPwQF6KUhVN3vkCl7LMBrO4qPa40uE9N5zVQ== dependencies: "@babel/plugin-proposal-class-properties" "^7.12.1" "@babel/plugin-proposal-decorators" "^7.12.1" @@ -5135,25 +5135,25 @@ media-typer@0.3.0: resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= -medusa-core-utils@1.1.30-dev-1638295280349: - version "1.1.30-dev-1638295280349" - resolved "http://localhost:4873/medusa-core-utils/-/medusa-core-utils-1.1.30-dev-1638295280349.tgz#bead12ee83fa00a03dd208329260b73eaea2a019" - integrity sha512-i4SXOB5VARyYgDMAydfZXK8DHrHZUjad0ABsxOD4m1XYBGMLJfcBQFfOzAiipQDuX38cVAQUoVnPbM3fm1WLKQ== +medusa-core-utils@1.1.30-dev-1638874121913: + version "1.1.30-dev-1638874121913" + resolved "http://localhost:4873/medusa-core-utils/-/medusa-core-utils-1.1.30-dev-1638874121913.tgz#72e5b0694fecbc83a61c58af212d993bb7b2104a" + integrity sha512-ldQ7WXJTdaGzUMVsF8LJqr5uLCJT0ZYUUTYUGazCgj30kULSUV3/VZm4+B7a5D7UoqJMexBqG+qmRDzCZ74Drg== dependencies: joi "^17.3.0" joi-objectid "^3.0.1" -medusa-interfaces@1.1.31-dev-1638295280349: - version "1.1.31-dev-1638295280349" - resolved "http://localhost:4873/medusa-interfaces/-/medusa-interfaces-1.1.31-dev-1638295280349.tgz#978e0215a8b232e375a6ec512dc1e3e65a1d6de7" - integrity sha512-Qs9qYTtYfGTJfDrjRFvQiSTNsQNjpAfdMlNXxeH/HAXZxk3/bYHdf51c8FNhpoMX/zHA150B4yPDksHf0B1Eeg== +medusa-interfaces@1.1.31-dev-1638874121913: + version "1.1.31-dev-1638874121913" + resolved "http://localhost:4873/medusa-interfaces/-/medusa-interfaces-1.1.31-dev-1638874121913.tgz#0ee6c3efe637eff3364b1bd69419001a8db14422" + integrity sha512-NUzBuml4/mxSpx5BBgkQVDNqItdrYlXqqNjwhq5GjHgqkCDVrQRJA5GGcQJ/17NbRSr6KKVEJuKkWlFhOgadkQ== dependencies: - medusa-core-utils "1.1.30-dev-1638295280349" + medusa-core-utils "1.1.30-dev-1638874121913" -medusa-telemetry@0.0.10-dev-1638295280349: - version "0.0.10-dev-1638295280349" - resolved "http://localhost:4873/medusa-telemetry/-/medusa-telemetry-0.0.10-dev-1638295280349.tgz#00c397861ec79453733865a20ca4fc5e37b4152f" - integrity sha512-qRdXd5DcGv0eFZJ70Ns79sJL13xKOXd8trVQEkUjPAWTcaxtmb1tCw8vXQAoWXycq+9kb7KS3zcTdBnI+ML2Bw== +medusa-telemetry@0.0.10-dev-1638874121913: + version "0.0.10-dev-1638874121913" + resolved "http://localhost:4873/medusa-telemetry/-/medusa-telemetry-0.0.10-dev-1638874121913.tgz#b8e58daa306dfeffbf28bdadca12ac852beb7912" + integrity sha512-3V/Um1B2NzmztGttGFNSFzT0ZPyGg6vsxZc1M4XTDQfB1YeioQYImQiGw62mIpnKxTyRLWpQfkaiRIK9ILNL+Q== dependencies: axios "^0.21.1" axios-retry "^3.1.9" @@ -5165,13 +5165,13 @@ medusa-telemetry@0.0.10-dev-1638295280349: remove-trailing-slash "^0.1.1" uuid "^8.3.2" -medusa-test-utils@1.1.33-dev-1638295280349: - version "1.1.33-dev-1638295280349" - resolved "http://localhost:4873/medusa-test-utils/-/medusa-test-utils-1.1.33-dev-1638295280349.tgz#61dc97815f02a3a0756ce5ac4f8cdaace7cd8a92" - integrity sha512-CqKubpbybtCt/SVjdbwM44KA2b9jFH8NMfmjTy/FJnuWqus5GWi6nBYhBMJM609uuSbGXCv8HmhI4B5fg4Cy8A== +medusa-test-utils@1.1.33-dev-1638874121913: + version "1.1.33-dev-1638874121913" + resolved "http://localhost:4873/medusa-test-utils/-/medusa-test-utils-1.1.33-dev-1638874121913.tgz#422aa74ceff6e09abf28c070ea941982544afbad" + integrity sha512-I7Z0GCdPQWMwygffjKw9CnGkFeudA/TCmxNTkWna+4WOlK3FFoilAi/T6Ps3q4ZDXkJ1O+vNG9yBG9zAldGPmw== dependencies: "@babel/plugin-transform-classes" "^7.9.5" - medusa-core-utils "1.1.30-dev-1638295280349" + medusa-core-utils "1.1.30-dev-1638874121913" randomatic "^3.1.1" merge-descriptors@1.0.1: diff --git a/packages/medusa/src/api/routes/admin/shipping-options/__tests__/create-shipping-option.js b/packages/medusa/src/api/routes/admin/shipping-options/__tests__/create-shipping-option.js index 71cbf994b0..18cc5a5e18 100644 --- a/packages/medusa/src/api/routes/admin/shipping-options/__tests__/create-shipping-option.js +++ b/packages/medusa/src/api/routes/admin/shipping-options/__tests__/create-shipping-option.js @@ -43,8 +43,9 @@ describe("POST /admin/shipping-options", () => { name: "Test option", region_id: "testregion", provider_id: "test_provider", + metadata: undefined, data: { id: "test" }, - profile_id: expect.stringMatching(/.*/), + profile_id: expect.any(String), price_type: "flat_rate", amount: 100, requirements: [ @@ -86,7 +87,9 @@ describe("POST /admin/shipping-options", () => { }) it("returns error", () => { - expect(subject.body.message[0].message).toEqual(`"name" is required`) + expect(subject.body.message).toEqual( + expect.stringContaining(`name must be a string`) + ) }) }) }) diff --git a/packages/medusa/src/api/routes/admin/shipping-options/create-shipping-option.js b/packages/medusa/src/api/routes/admin/shipping-options/create-shipping-option.ts similarity index 64% rename from packages/medusa/src/api/routes/admin/shipping-options/create-shipping-option.js rename to packages/medusa/src/api/routes/admin/shipping-options/create-shipping-option.ts index 8e44305392..bff45b1eb9 100644 --- a/packages/medusa/src/api/routes/admin/shipping-options/create-shipping-option.js +++ b/packages/medusa/src/api/routes/admin/shipping-options/create-shipping-option.ts @@ -1,11 +1,21 @@ -import { MedusaError, Validator } from "medusa-core-utils" -import { defaultFields, defaultRelations } from "./" +import { Type } from "class-transformer" +import { + IsArray, + IsNumber, + IsObject, + IsOptional, + IsString, + ValidateNested, +} from "class-validator" +import { defaultFields, defaultRelations } from "." +import { validator } from "../../../../utils/validator" /** * @oas [post] /shipping-options * operationId: "PostShippingOptions" * summary: "Create Shipping Option" * description: "Creates a Shipping Option" + * x-authenticated: true * requestBody: * content: * application/json: @@ -52,6 +62,12 @@ import { defaultFields, defaultRelations } from "./" * is_return: * description: Whether the Shipping Option defines a return shipment. * type: boolean + * admin_only: + * description: If true, the option can be used for draft orders + * type: boolean + * metadata: + * description: An optional set of key-value pairs with additional information. + * type: object * tags: * - Shipping Option * responses: @@ -65,41 +81,18 @@ import { defaultFields, defaultRelations } from "./" * $ref: "#/components/schemas/shipping_option" */ export default async (req, res) => { - const schema = Validator.object().keys({ - name: Validator.string().required(), - region_id: Validator.string().required(), - provider_id: Validator.string().required(), - profile_id: Validator.string(), - data: Validator.object().required(), - price_type: Validator.string().required(), - amount: Validator.number().integer().optional(), - requirements: Validator.array() - .items( - Validator.object({ - type: Validator.string().required(), - amount: Validator.number().integer().required(), - }) - ) - .optional(), - is_return: Validator.boolean().default(false), - admin_only: Validator.boolean().default(false), - }) - - const { value, error } = schema.validate(req.body) - if (error) { - throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details) - } + const validated = await validator(AdminPostShippingOptionsReq, req.body) const optionService = req.scope.resolve("shippingOptionService") const shippingProfileService = req.scope.resolve("shippingProfileService") // Add to default shipping profile - if (!value.profile_id) { + if (!validated.profile_id) { const { id } = await shippingProfileService.retrieveDefault() - value.profile_id = id + validated.profile_id = id } - const result = await optionService.create(value) + const result = await optionService.create(validated) const data = await optionService.retrieve(result.id, { select: defaultFields, relations: defaultRelations, @@ -107,3 +100,53 @@ export default async (req, res) => { res.status(200).json({ shipping_option: data }) } + +class OptionRequirement { + @IsString() + type: string + @IsNumber() + amount: number +} + +export class AdminPostShippingOptionsReq { + @IsString() + name: string + + @IsString() + region_id: string + + @IsString() + provider_id: string + + @IsOptional() + @IsString() + profile_id?: string + + @IsObject() + data: object + + @IsString() + price_type: string + + @IsOptional() + @IsNumber() + amount?: number + + @IsArray() + @IsOptional() + @ValidateNested({ each: true }) + @Type(() => OptionRequirement) + requirements?: OptionRequirement[] + + @IsOptional() + @Type(() => Boolean) + admin_only?: boolean = false + + @IsOptional() + @Type(() => Boolean) + is_return?: boolean = false + + @IsObject() + @IsOptional() + metadata?: object +} diff --git a/packages/medusa/src/api/routes/admin/shipping-options/delete-shipping-option.js b/packages/medusa/src/api/routes/admin/shipping-options/delete-shipping-option.ts similarity index 97% rename from packages/medusa/src/api/routes/admin/shipping-options/delete-shipping-option.js rename to packages/medusa/src/api/routes/admin/shipping-options/delete-shipping-option.ts index f4b32caf43..b382cc5f19 100644 --- a/packages/medusa/src/api/routes/admin/shipping-options/delete-shipping-option.js +++ b/packages/medusa/src/api/routes/admin/shipping-options/delete-shipping-option.ts @@ -3,6 +3,7 @@ * operationId: "DeleteShippingOptionsOption" * summary: "Delete a Shipping Option" * description: "Deletes a Shipping Option." + * x-authenticated: true * parameters: * - (path) id=* {string} The id of the Shipping Option. * tags: diff --git a/packages/medusa/src/api/routes/admin/shipping-options/get-shipping-option.js b/packages/medusa/src/api/routes/admin/shipping-options/get-shipping-option.ts similarity index 96% rename from packages/medusa/src/api/routes/admin/shipping-options/get-shipping-option.js rename to packages/medusa/src/api/routes/admin/shipping-options/get-shipping-option.ts index 20508c9fe2..1100444928 100644 --- a/packages/medusa/src/api/routes/admin/shipping-options/get-shipping-option.js +++ b/packages/medusa/src/api/routes/admin/shipping-options/get-shipping-option.ts @@ -3,6 +3,7 @@ * operationId: "GetShippingOptionsOption" * summary: "Retrieve a Shipping Option" * description: "Retrieves a Shipping Option." + * x-authenticated: true * parameters: * - (path) id=* {string} The id of the Shipping Option. * tags: diff --git a/packages/medusa/src/api/routes/admin/shipping-options/index.js b/packages/medusa/src/api/routes/admin/shipping-options/index.ts similarity index 64% rename from packages/medusa/src/api/routes/admin/shipping-options/index.js rename to packages/medusa/src/api/routes/admin/shipping-options/index.ts index 14f79cb287..30fce989d1 100644 --- a/packages/medusa/src/api/routes/admin/shipping-options/index.js +++ b/packages/medusa/src/api/routes/admin/shipping-options/index.ts @@ -1,4 +1,6 @@ import { Router } from "express" +import { ShippingOption } from "../../../.." +import { DeleteResponse } from "../../../../types/common" import middlewares from "../../../middlewares" const route = Router() @@ -43,3 +45,19 @@ export const defaultFields = [ ] export const defaultRelations = ["region", "profile", "requirements"] + +export type AdminShippingOptionsListRes = { + shipping_options: ShippingOption[] +} + +export type AdminShippingOptionsRes = { + shipping_option: ShippingOption +} + +export type AdminShippingOptionsDeleteRes = DeleteResponse + +export * from "./create-shipping-option" +export * from "./delete-shipping-option" +export * from "./get-shipping-option" +export * from "./list-shipping-options" +export * from "./update-shipping-option" diff --git a/packages/medusa/src/api/routes/admin/shipping-options/list-shipping-options.js b/packages/medusa/src/api/routes/admin/shipping-options/list-shipping-options.js deleted file mode 100644 index 406b447c52..0000000000 --- a/packages/medusa/src/api/routes/admin/shipping-options/list-shipping-options.js +++ /dev/null @@ -1,33 +0,0 @@ -import _ from "lodash" -import { defaultFields, defaultRelations } from "./" - -/** - * @oas [get] /shipping-options - * operationId: "GetShippingOptions" - * summary: "List Shipping Options" - * description: "Retrieves a list of Shipping Options." - * tags: - * - Shipping Option - * responses: - * 200: - * description: OK - * content: - * application/json: - * schema: - * properties: - * shipping_options: - * type: array - * items: - * $ref: "#/components/schemas/shipping_option" - */ -export default async (req, res) => { - const query = _.pick(req.query, ["region_id", "is_return", "admin_only"]) - - const optionService = req.scope.resolve("shippingOptionService") - const data = await optionService.list(query, { - select: defaultFields, - relations: defaultRelations, - }) - - res.status(200).json({ shipping_options: data }) -} diff --git a/packages/medusa/src/api/routes/admin/shipping-options/list-shipping-options.ts b/packages/medusa/src/api/routes/admin/shipping-options/list-shipping-options.ts new file mode 100644 index 0000000000..8e2e0cbda4 --- /dev/null +++ b/packages/medusa/src/api/routes/admin/shipping-options/list-shipping-options.ts @@ -0,0 +1,75 @@ +import { Transform } from "class-transformer" +import { IsBoolean, IsOptional, IsString } from "class-validator" +import { defaultFields, defaultRelations } from "." +import { validator } from "../../../../utils/validator" +import { optionalBooleanMapper } from "../../../../utils/validators/is-boolean" + +/** + * @oas [get] /shipping-options + * operationId: "GetShippingOptions" + * summary: "List Shipping Options" + * description: "Retrieves a list of Shipping Options." + * x-authenticated: true + * parameters: + * - in: path + * name: region_id + * schema: + * type: string + * required: false + * description: Region to fetch options from + * - in: path + * name: is_return + * schema: + * type: boolean + * required: false + * description: Flag for fetching return options + * - in: path + * name: admin_only + * schema: + * type: boolean + * required: false + * description: Flag for fetching admin specific options + * tags: + * - Shipping Option + * responses: + * 200: + * description: OK + * content: + * application/json: + * schema: + * properties: + * shipping_options: + * type: array + * items: + * $ref: "#/components/schemas/shipping_option" + */ +export default async (req, res) => { + const validatedParams = await validator( + AdminGetShippingOptionsParams, + req.query + ) + + const optionService = req.scope.resolve("shippingOptionService") + const data = await optionService.list(validatedParams, { + select: defaultFields, + relations: defaultRelations, + }) + + res.status(200).json({ shipping_options: data }) +} + +export class AdminGetShippingOptionsParams { + @IsOptional() + @IsString() + region_id?: string + + @IsOptional() + @IsBoolean() + @Transform(({ value }) => optionalBooleanMapper.get(value)) + is_return?: string + + @IsOptional() + @IsBoolean() + @Transform(({ value }) => optionalBooleanMapper.get(value)) + admin_only?: string +} diff --git a/packages/medusa/src/api/routes/admin/shipping-options/update-shipping-option.js b/packages/medusa/src/api/routes/admin/shipping-options/update-shipping-option.ts similarity index 58% rename from packages/medusa/src/api/routes/admin/shipping-options/update-shipping-option.js rename to packages/medusa/src/api/routes/admin/shipping-options/update-shipping-option.ts index 185444182d..287de85341 100644 --- a/packages/medusa/src/api/routes/admin/shipping-options/update-shipping-option.js +++ b/packages/medusa/src/api/routes/admin/shipping-options/update-shipping-option.ts @@ -1,11 +1,22 @@ -import { MedusaError, Validator } from "medusa-core-utils" -import { defaultFields, defaultRelations } from "./" +import { Type } from "class-transformer" +import { + IsArray, + IsBoolean, + IsNumber, + IsObject, + IsOptional, + IsString, + ValidateNested, +} from "class-validator" +import { defaultFields, defaultRelations } from "." +import { validator } from "../../../../utils/validator" /** * @oas [post] /shipping-options/{id} * operationId: "PostShippingOptionsOption" * summary: "Update Shipping Option" * description: "Updates a Shipping Option" + * x-authenticated: true * parameters: * - (path) id=* {string} The id of the Shipping Option. * requestBody: @@ -19,6 +30,12 @@ import { defaultFields, defaultRelations } from "./" * amount: * description: "The amount to charge for the Shipping Option." * type: integer + * admin_only: + * description: "If true, the option can be used for draft orders" + * type: boolean + * metadata: + * description: "An optional set of key-value pairs with additional information." + * type: object * requirements: * description: "The requirements that must be satisfied for the Shipping Option to be available." * type: array @@ -47,30 +64,12 @@ import { defaultFields, defaultRelations } from "./" */ export default async (req, res) => { const { option_id } = req.params - const schema = Validator.object().keys({ - name: Validator.string().optional(), - amount: Validator.number().integer().optional(), - requirements: Validator.array() - .items( - Validator.object({ - id: Validator.string().optional(), - type: Validator.string().required(), - amount: Validator.number().integer().required(), - }) - ) - .optional(), - admin_only: Validator.boolean().optional(), - metadata: Validator.object().optional(), - }) - const { value, error } = schema.validate(req.body) - if (error) { - throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details) - } + const validated = await validator(AdminPostShippingOptionsOptionReq, req.body) const optionService = req.scope.resolve("shippingOptionService") - await optionService.update(option_id, value) + await optionService.update(option_id, validated) const data = await optionService.retrieve(option_id, { select: defaultFields, @@ -79,3 +78,36 @@ export default async (req, res) => { res.status(200).json({ shipping_option: data }) } + +class OptionRequirement { + @IsString() + @IsOptional() + id: string + @IsString() + type: string + @IsNumber() + amount: number +} + +export class AdminPostShippingOptionsOptionReq { + @IsString() + @IsOptional() + name: string + + @IsNumber() + @IsOptional() + amount?: number + + @IsArray() + @ValidateNested({ each: true }) + @Type(() => OptionRequirement) + requirements: OptionRequirement[] + + @IsBoolean() + @IsOptional() + admin_only?: boolean + + @IsObject() + @IsOptional() + metadata?: object +} diff --git a/packages/medusa/src/utils/validators/is-boolean.ts b/packages/medusa/src/utils/validators/is-boolean.ts new file mode 100644 index 0000000000..cc767dc1df --- /dev/null +++ b/packages/medusa/src/utils/validators/is-boolean.ts @@ -0,0 +1,14 @@ +// Util function defining the transformation of booleans when part of req.query +// e.g. /admin/shipping-options?is_return=false -> false +// +// We've previously been using @Type(() => Boolean), but this will always return true for strings. +// See https://github.com/typestack/class-transformer/issues/676 +// and https://github.com/typestack/class-transformer/issues/306 +// +// The solution here is stolen from: https://github.com/typestack/class-transformer/issues/676#issuecomment-822699830 +export const optionalBooleanMapper = new Map([ + ["undefined", undefined], + ["null", null], + ["true", true], + ["false", false], +])