feat: Admin shipping options routes to Typescript (#891)

This commit is contained in:
Oliver Windall Juhl
2021-12-07 17:07:23 +01:00
committed by GitHub
parent 487356a96f
commit 6579c13111
13 changed files with 393 additions and 124 deletions
@@ -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`)
)
})
})
})
@@ -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
}
@@ -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:
@@ -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:
@@ -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"
@@ -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 })
}
@@ -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
}
@@ -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
}
@@ -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],
])