feat: Admin shipping options routes to Typescript (#891)
This commit is contained in:
+5
-2
@@ -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`)
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
+72
-29
@@ -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
|
||||
}
|
||||
+1
@@ -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:
|
||||
+1
@@ -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:
|
||||
+18
@@ -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
|
||||
}
|
||||
+54
-22
@@ -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],
|
||||
])
|
||||
Reference in New Issue
Block a user