chore: Move several more endpoints to use zod for validation, unify APIs (#7086)

This commit is contained in:
Stevche Radevski authored and GitHub committed 2024-04-18 06:51:43 +00:00
1 parent 2a835cae13
commit 44829f296a
35 files changed
+701 -1028

No files matched your search

@@ -334,14 +334,14 @@ medusaIntegrationTestRunner({
it("should delete an inventory location level and create a new one", async () => { it("should delete an inventory location level and create a new one", async () => {
const result = await api.post( const result = await api.post(
`/admin/inventory-items/${inventoryItem.id}/location-levels/batch/combi`, `/admin/inventory-items/${inventoryItem.id}/location-levels/op/batch`,
{ {
creates: [ create: [
{ {
location_id: "location_2", location_id: "location_2",
}, },
], ],
deletes: [locationId], delete: [locationId],
}, },
adminHeaders adminHeaders
) )
@@ -386,6 +386,7 @@ medusaIntegrationTestRunner({
id: expect.any(String), id: expect.any(String),
object: "inventory-level", object: "inventory-level",
deleted: true, deleted: true,
parent: expect.any(Object),
}) })
}) })
+1 -1
View File
@@ -32,7 +32,7 @@ export const joinerConfig: ModuleJoinerConfig = {
schema: moduleSchema, schema: moduleSchema,
alias: [ alias: [
{ {
name: ["inventory_items", "inventory"], name: ["inventory_items", "inventory_item", "inventory"],
args: { args: {
entity: "InventoryItem", entity: "InventoryItem",
}, },
@@ -1,8 +1,4 @@
import { import { AdminFulfillmentSetsDeleteResponse } from "@medusajs/types"
AdminFulfillmentSetsDeleteResponse,
IFulfillmentModuleService,
} from "@medusajs/types"
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { deleteFulfillmentSetsWorkflow } from "@medusajs/core-flows" import { deleteFulfillmentSetsWorkflow } from "@medusajs/core-flows"
import { import {
@@ -16,13 +12,6 @@ export const DELETE = async (
) => { ) => {
const { id } = req.params const { id } = req.params
const fulfillmentModuleService = req.scope.resolve<IFulfillmentModuleService>(
ModuleRegistrationName.FULFILLMENT
)
// Test if exists
await fulfillmentModuleService.retrieve(id)
const { errors } = await deleteFulfillmentSetsWorkflow(req.scope).run({ const { errors } = await deleteFulfillmentSetsWorkflow(req.scope).run({
input: { ids: [id] }, input: { ids: [id] },
throwOnError: false, throwOnError: false,
@@ -1,17 +1,12 @@
import { createServiceZonesWorkflow } from "@medusajs/core-flows" import { createServiceZonesWorkflow } from "@medusajs/core-flows"
import {
ContainerRegistrationKeys,
remoteQueryObjectFromString,
} from "@medusajs/utils"
import { MedusaRequest, MedusaResponse } from "../../../../../types/routing" import { MedusaRequest, MedusaResponse } from "../../../../../types/routing"
import { AdminCreateFulfillmentSetServiceZonesType } from "../../validators" import { AdminCreateFulfillmentSetServiceZonesType } from "../../validators"
import { refetchFulfillmentSet } from "../../helpers"
export const POST = async ( export const POST = async (
req: MedusaRequest<AdminCreateFulfillmentSetServiceZonesType>, req: MedusaRequest<AdminCreateFulfillmentSetServiceZonesType>,
res: MedusaResponse res: MedusaResponse
) => { ) => {
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
const workflowInput = { const workflowInput = {
data: [ data: [
{ {
@@ -31,15 +26,11 @@ export const POST = async (
throw errors[0].error throw errors[0].error
} }
const [fulfillment_set] = await remoteQuery( const fulfillmentSet = await refetchFulfillmentSet(
remoteQueryObjectFromString({ req.params.id,
entryPoint: "fulfillment_sets", req.scope,
variables: { req.remoteQueryConfig.fields
id: req.params.id,
},
fields: req.remoteQueryConfig.fields,
})
) )
res.status(200).json({ fulfillment_set }) res.status(200).json({ fulfillment_set: fulfillmentSet })
} }
@@ -0,0 +1,23 @@
import { MedusaContainer } from "@medusajs/types"
import {
ContainerRegistrationKeys,
remoteQueryObjectFromString,
} from "@medusajs/utils"
export const refetchFulfillmentSet = async (
fulfillmentSetId: string,
scope: MedusaContainer,
fields: string[]
) => {
const remoteQuery = scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
const queryObject = remoteQueryObjectFromString({
entryPoint: "fulfillment_set",
variables: {
filters: { id: fulfillmentSetId },
},
fields: fields,
})
const fulfillmentSets = await remoteQuery(queryObject)
return fulfillmentSets[0]
}
@@ -30,7 +30,12 @@ export const adminFulfillmentSetsRoutesMiddlewares: MiddlewareRoute[] = [
{ {
method: ["DELETE"], method: ["DELETE"],
matcher: "/admin/fulfillment-sets/:id/service-zones/:zone_id", matcher: "/admin/fulfillment-sets/:id/service-zones/:zone_id",
middlewares: [], middlewares: [
validateAndTransformQuery(
AdminFulfillmentSetParams,
QueryConfig.retrieveTransformQueryConfig
),
],
}, },
{ {
method: ["DELETE"], method: ["DELETE"],
@@ -5,15 +5,19 @@ import {
} from "@medusajs/utils" } from "@medusajs/utils"
import { MedusaRequest, MedusaResponse } from "../../../../../../types/routing" import { MedusaRequest, MedusaResponse } from "../../../../../../types/routing"
import { AdminPostInventoryItemsItemLocationLevelsLevelReq } from "../../../validators" import {
import { deleteInventoryLevelsWorkflow } from "@medusajs/core-flows" deleteInventoryLevelsWorkflow,
import { updateInventoryLevelsWorkflow } from "@medusajs/core-flows" updateInventoryLevelsWorkflow,
} from "@medusajs/core-flows"
import { refetchInventoryItem } from "../../../helpers"
import { AdminUpdateInventoryLocationLevelType } from "../../../validators"
export const DELETE = async (req: MedusaRequest, res: MedusaResponse) => { export const DELETE = async (req: MedusaRequest, res: MedusaResponse) => {
const { id, location_id } = req.params const { id, location_id } = req.params
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY) const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
// TODO: We probably want to move this logic to the workflow
const [{ id: levelId, reserved_quantity: reservedQuantity }] = const [{ id: levelId, reserved_quantity: reservedQuantity }] =
await remoteQuery( await remoteQuery(
remoteQueryObjectFromString({ remoteQueryObjectFromString({
@@ -41,23 +45,28 @@ export const DELETE = async (req: MedusaRequest, res: MedusaResponse) => {
}, },
}) })
const inventoryItem = await refetchInventoryItem(
id,
req.scope,
req.remoteQueryConfig.fields
)
res.status(200).json({ res.status(200).json({
id: levelId, id: levelId,
object: "inventory-level", object: "inventory-level",
deleted: true, deleted: true,
parent: inventoryItem,
}) })
} }
export const POST = async ( export const POST = async (
req: MedusaRequest<AdminPostInventoryItemsItemLocationLevelsLevelReq>, req: MedusaRequest<AdminUpdateInventoryLocationLevelType>,
res: MedusaResponse res: MedusaResponse
) => { ) => {
const { id: inventory_item_id, location_id } = req.params const { id, location_id } = req.params
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
const { errors } = await updateInventoryLevelsWorkflow(req.scope).run({ const { errors } = await updateInventoryLevelsWorkflow(req.scope).run({
input: { input: {
updates: [{ inventory_item_id, location_id, ...req.validatedBody }], updates: [{ ...req.validatedBody, inventory_item_id: id, location_id }],
}, },
throwOnError: false, throwOnError: false,
}) })
@@ -66,17 +75,13 @@ export const POST = async (
throw errors[0].error throw errors[0].error
} }
const [inventory_item] = await remoteQuery( const inventoryItem = await refetchInventoryItem(
remoteQueryObjectFromString({ id,
entryPoint: "inventory", req.scope,
variables: { req.remoteQueryConfig.fields
id: inventory_item_id,
},
fields: req.remoteQueryConfig.fields,
})
) )
res.status(200).json({ res.status(200).json({
inventory_item, inventory_item: inventoryItem,
}) })
} }
@@ -1,55 +0,0 @@
import {
AdminPostInventoryItemsItemLocationLevelsBatchReq,
AdminPostInventoryItemsItemLocationLevelsReq,
} from "../../../../validators"
import {
ContainerRegistrationKeys,
remoteQueryObjectFromString,
} from "@medusajs/utils"
import {
MedusaRequest,
MedusaResponse,
} from "../../../../../../../types/routing"
import { bulkCreateDeleteLevelsWorkflow } from "@medusajs/core-flows"
import { defaultAdminInventoryItemFields } from "../../../../query-config"
export const POST = async (
req: MedusaRequest<AdminPostInventoryItemsItemLocationLevelsBatchReq>,
res: MedusaResponse
) => {
const { id } = req.params
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
const workflow = bulkCreateDeleteLevelsWorkflow(req.scope)
const { errors } = await workflow.run({
input: {
deletes: req.validatedBody.deletes.map((location_id) => ({
location_id,
inventory_item_id: id,
})),
creates: req.validatedBody.creates.map((c) => ({
...c,
inventory_item_id: id,
})),
},
throwOnError: false,
})
if (Array.isArray(errors) && errors[0]) {
throw errors[0].error
}
const itemQuery = remoteQueryObjectFromString({
entryPoint: "inventory_items",
variables: {
id,
},
fields: defaultAdminInventoryItemFields,
})
const [inventory_item] = await remoteQuery(itemQuery)
res.status(200).json({ inventory_item })
}
@@ -0,0 +1,47 @@
import {
AdminCreateInventoryLocationLevelType,
AdminUpdateInventoryLocationLevelType,
} from "../../../../validators"
import {
MedusaRequest,
MedusaResponse,
} from "../../../../../../../types/routing"
import { bulkCreateDeleteLevelsWorkflow } from "@medusajs/core-flows"
import { BatchMethodRequest } from "@medusajs/types"
export const POST = async (
req: MedusaRequest<
BatchMethodRequest<
AdminCreateInventoryLocationLevelType,
AdminUpdateInventoryLocationLevelType
>
>,
res: MedusaResponse
) => {
const { id } = req.params
// TODO: Normalize workflow and response, and add support for updates
const workflow = bulkCreateDeleteLevelsWorkflow(req.scope)
const { errors } = await workflow.run({
input: {
deletes:
req.validatedBody.delete?.map((location_id) => ({
location_id,
inventory_item_id: id,
})) ?? [],
creates:
req.validatedBody.create?.map((c) => ({
...c,
inventory_item_id: id,
})) ?? [],
},
throwOnError: false,
})
if (Array.isArray(errors) && errors[0]) {
throw errors[0].error
}
res.status(200).json({ inventory_item: {} })
}
@@ -4,26 +4,26 @@ import {
} from "@medusajs/utils" } from "@medusajs/utils"
import { MedusaRequest, MedusaResponse } from "../../../../../types/routing" import { MedusaRequest, MedusaResponse } from "../../../../../types/routing"
import { AdminPostInventoryItemsItemLocationLevelsReq } from "../../validators"
import { MedusaError } from "@medusajs/utils"
import { createInventoryLevelsWorkflow } from "@medusajs/core-flows" import { createInventoryLevelsWorkflow } from "@medusajs/core-flows"
import { defaultAdminInventoryItemFields } from "../../query-config" import {
AdminCreateInventoryLocationLevelType,
AdminGetInventoryLocationLevelsParamsType,
} from "../../validators"
import { refetchInventoryItem } from "../../helpers"
export const POST = async ( export const POST = async (
req: MedusaRequest<AdminPostInventoryItemsItemLocationLevelsReq>, req: MedusaRequest<AdminCreateInventoryLocationLevelType>,
res: MedusaResponse res: MedusaResponse
) => { ) => {
const { id } = req.params const { id } = req.params
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
const workflow = createInventoryLevelsWorkflow(req.scope) const workflow = createInventoryLevelsWorkflow(req.scope)
const { errors } = await workflow.run({ const { errors } = await workflow.run({
input: { input: {
inventory_levels: [ inventory_levels: [
{ {
inventory_item_id: id,
...req.validatedBody, ...req.validatedBody,
inventory_item_id: id,
}, },
], ],
}, },
@@ -34,20 +34,18 @@ export const POST = async (
throw errors[0].error throw errors[0].error
} }
const itemQuery = remoteQueryObjectFromString({ const inventoryItem = await refetchInventoryItem(
entryPoint: "inventory_items",
variables: {
id, id,
}, req.scope,
fields: defaultAdminInventoryItemFields, req.remoteQueryConfig.fields
}) )
res.status(200).json({ inventory_item: inventoryItem })
const [inventory_item] = await remoteQuery(itemQuery)
res.status(200).json({ inventory_item })
} }
export const GET = async (req: MedusaRequest, res: MedusaResponse) => { export const GET = async (
req: MedusaRequest<AdminGetInventoryLocationLevelsParamsType>,
res: MedusaResponse
) => {
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY) const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
const query = remoteQueryObjectFromString({ const query = remoteQueryObjectFromString({
@@ -1,36 +1,26 @@
import { import { MedusaError } from "@medusajs/utils"
ContainerRegistrationKeys,
MedusaError,
remoteQueryObjectFromString,
} from "@medusajs/utils"
import { MedusaRequest, MedusaResponse } from "../../../../types/routing" import { MedusaRequest, MedusaResponse } from "../../../../types/routing"
import { import {
deleteInventoryItemWorkflow, deleteInventoryItemWorkflow,
updateInventoryItemsWorkflow, updateInventoryItemsWorkflow,
} from "@medusajs/core-flows" } from "@medusajs/core-flows"
import {
AdminGetInventoryItemParamsType,
AdminUpdateInventoryItemType,
} from "../validators"
import { refetchInventoryItem } from "../helpers"
import { AdminPostInventoryItemsInventoryItemReq } from "../validators" export const GET = async (
req: MedusaRequest<AdminGetInventoryItemParamsType>,
export const GET = async (req: MedusaRequest, res: MedusaResponse) => { res: MedusaResponse
) => {
const { id } = req.params const { id } = req.params
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY) const inventoryItem = await refetchInventoryItem(
id,
const query = remoteQueryObjectFromString({ req.scope,
entryPoint: "inventory", req.remoteQueryConfig.fields
variables: { )
filters: { id }, if (!inventoryItem) {
skip: 0,
take: 1,
},
fields: req.retrieveConfig.select as string[],
})
const { rows } = await remoteQuery(query)
const [inventory_item] = rows
if (!inventory_item) {
throw new MedusaError( throw new MedusaError(
MedusaError.Types.NOT_FOUND, MedusaError.Types.NOT_FOUND,
`Inventory item with id: ${id} was not found` `Inventory item with id: ${id} was not found`
@@ -38,13 +28,13 @@ export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
} }
res.status(200).json({ res.status(200).json({
inventory_item, inventory_item: inventoryItem,
}) })
} }
// Update inventory item // Update inventory item
export const POST = async ( export const POST = async (
req: MedusaRequest<AdminPostInventoryItemsInventoryItemReq>, req: MedusaRequest<AdminUpdateInventoryItemType>,
res: MedusaResponse res: MedusaResponse
) => { ) => {
const { id } = req.params const { id } = req.params
@@ -55,20 +45,14 @@ export const POST = async (
}, },
}) })
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY) const inventoryItem = await refetchInventoryItem(
const [inventory_item] = await remoteQuery(
remoteQueryObjectFromString({
entryPoint: "inventory",
variables: {
id, id,
}, req.scope,
fields: req.retrieveConfig.select as string[], req.remoteQueryConfig.fields
})
) )
res.status(200).json({ res.status(200).json({
inventory_item, inventory_item: inventoryItem,
}) })
} }
@@ -0,0 +1,28 @@
import { MedusaContainer } from "@medusajs/types"
import {
ContainerRegistrationKeys,
remoteQueryObjectFromString,
} from "@medusajs/utils"
export const refetchInventoryItem = async (
inventoryItemId: string,
scope: MedusaContainer,
fields: string[]
) => {
const remoteQuery = scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
const queryObject = remoteQueryObjectFromString({
entryPoint: "inventory_item",
variables: {
filters: { id: inventoryItemId },
skip: 0,
take: 1,
},
fields: fields,
})
// TODO: Why does the response type change if you pass skip and take, vs not passing it?
// Also, why does the data change (in this case, not doing skip and take will not return the lazy fields of stockedQuantity and reserved_quantity)
const { rows } = await remoteQuery(queryObject)
return rows[0]
}
@@ -1,21 +1,19 @@
import * as QueryConfig from "./query-config" import * as QueryConfig from "./query-config"
import {
AdminGetInventoryItemsItemLocationLevelsParams,
AdminGetInventoryItemsItemParams,
AdminGetInventoryItemsParams,
AdminPostInventoryItemsInventoryItemParams,
AdminPostInventoryItemsInventoryItemReq,
AdminPostInventoryItemsItemLocationLevelsBatchReq,
AdminPostInventoryItemsItemLocationLevelsLevelParams,
AdminPostInventoryItemsItemLocationLevelsLevelReq,
AdminPostInventoryItemsItemLocationLevelsReq,
AdminPostInventoryItemsReq,
} from "./validators"
import { transformBody, transformQuery } from "../../../api/middlewares"
import { MiddlewareRoute } from "../../../types/middlewares" import { MiddlewareRoute } from "../../../types/middlewares"
import { authenticate } from "../../../utils/authenticate-middleware" import { authenticate } from "../../../utils/authenticate-middleware"
import { validateAndTransformQuery } from "../../utils/validate-query"
import {
AdminCreateInventoryItem,
AdminCreateInventoryLocationLevel,
AdminGetInventoryItemParams,
AdminGetInventoryItemsParams,
AdminGetInventoryLocationLevelParams,
AdminGetInventoryLocationLevelsParams,
AdminUpdateInventoryItem,
AdminUpdateInventoryLocationLevel,
} from "./validators"
import { validateAndTransformBody } from "../../utils/validate-body"
import { createBatchBody } from "../../utils/validators"
export const adminInventoryRoutesMiddlewares: MiddlewareRoute[] = [ export const adminInventoryRoutesMiddlewares: MiddlewareRoute[] = [
{ {
@@ -27,7 +25,7 @@ export const adminInventoryRoutesMiddlewares: MiddlewareRoute[] = [
method: ["GET"], method: ["GET"],
matcher: "/admin/inventory-items", matcher: "/admin/inventory-items",
middlewares: [ middlewares: [
transformQuery( validateAndTransformQuery(
AdminGetInventoryItemsParams, AdminGetInventoryItemsParams,
QueryConfig.listTransformQueryConfig QueryConfig.listTransformQueryConfig
), ),
@@ -37,8 +35,8 @@ export const adminInventoryRoutesMiddlewares: MiddlewareRoute[] = [
method: ["GET"], method: ["GET"],
matcher: "/admin/inventory-items/:id", matcher: "/admin/inventory-items/:id",
middlewares: [ middlewares: [
transformQuery( validateAndTransformQuery(
AdminGetInventoryItemsItemParams, AdminGetInventoryItemParams,
QueryConfig.retrieveTransformQueryConfig QueryConfig.retrieveTransformQueryConfig
), ),
], ],
@@ -47,47 +45,9 @@ export const adminInventoryRoutesMiddlewares: MiddlewareRoute[] = [
method: ["POST"], method: ["POST"],
matcher: "/admin/inventory-items", matcher: "/admin/inventory-items",
middlewares: [ middlewares: [
transformBody(AdminPostInventoryItemsReq), validateAndTransformBody(AdminCreateInventoryItem),
transformQuery( validateAndTransformQuery(
AdminGetInventoryItemsItemParams, AdminGetInventoryItemParams,
QueryConfig.retrieveTransformQueryConfig
),
],
},
{
method: ["POST"],
matcher: "/admin/inventory-items/:id/location-levels/batch/combi",
middlewares: [
transformBody(AdminPostInventoryItemsItemLocationLevelsBatchReq),
],
},
{
method: ["GET"],
matcher: "/admin/inventory-items/:id/location-levels",
middlewares: [
transformQuery(
AdminGetInventoryItemsItemLocationLevelsParams,
QueryConfig.listLocationLevelsTransformQueryConfig
),
],
},
{
method: ["POST"],
matcher: "/admin/inventory-items/:id/location-levels",
middlewares: [transformBody(AdminPostInventoryItemsItemLocationLevelsReq)],
},
{
method: ["POST"],
matcher: "/admin/inventory-items/:id/location-levels",
middlewares: [transformBody(AdminPostInventoryItemsItemLocationLevelsReq)],
},
{
method: ["POST"],
matcher: "/admin/inventory-items/:id/location-levels/:location_id",
middlewares: [
transformBody(AdminPostInventoryItemsItemLocationLevelsLevelReq),
transformQuery(
AdminPostInventoryItemsItemLocationLevelsLevelParams,
QueryConfig.retrieveTransformQueryConfig QueryConfig.retrieveTransformQueryConfig
), ),
], ],
@@ -96,11 +56,69 @@ export const adminInventoryRoutesMiddlewares: MiddlewareRoute[] = [
method: ["POST"], method: ["POST"],
matcher: "/admin/inventory-items/:id", matcher: "/admin/inventory-items/:id",
middlewares: [ middlewares: [
transformBody(AdminPostInventoryItemsInventoryItemReq), validateAndTransformBody(AdminUpdateInventoryItem),
transformQuery( validateAndTransformQuery(
AdminPostInventoryItemsInventoryItemParams, AdminGetInventoryItemParams,
QueryConfig.retrieveTransformQueryConfig QueryConfig.retrieveTransformQueryConfig
), ),
], ],
}, },
{
method: ["GET"],
matcher: "/admin/inventory-items/:id/location-levels",
middlewares: [
validateAndTransformQuery(
AdminGetInventoryLocationLevelsParams,
QueryConfig.listLocationLevelsTransformQueryConfig
),
],
},
{
method: ["POST"],
matcher: "/admin/inventory-items/:id/location-levels",
middlewares: [
validateAndTransformBody(AdminCreateInventoryLocationLevel),
validateAndTransformQuery(
AdminGetInventoryItemParams,
QueryConfig.retrieveTransformQueryConfig
),
],
},
{
method: ["DELETE"],
matcher: "/admin/inventory-items/:id/location-levels/:location_id",
middlewares: [
validateAndTransformQuery(
AdminGetInventoryItemParams,
QueryConfig.retrieveTransformQueryConfig
),
],
},
{
method: ["POST"],
matcher: "/admin/inventory-items/:id/location-levels/:location_id",
middlewares: [
validateAndTransformBody(AdminUpdateInventoryLocationLevel),
validateAndTransformQuery(
AdminGetInventoryItemParams,
QueryConfig.retrieveTransformQueryConfig
),
],
},
{
method: ["POST"],
matcher: "/admin/inventory-items/:id/location-levels/op/batch",
middlewares: [
validateAndTransformBody(
createBatchBody(
AdminCreateInventoryLocationLevel,
AdminUpdateInventoryLocationLevel
)
),
validateAndTransformQuery(
AdminGetInventoryLocationLevelParams,
QueryConfig.retrieveLocationLevelsTransformQueryConfig
),
],
},
] ]
@@ -1,6 +1,3 @@
import { InventoryNext } from "@medusajs/types"
import { defaultAdminProductsVariantFields } from "../products/query-config"
// eslint-disable-next-line max-len // eslint-disable-next-line max-len
export const defaultAdminLocationLevelFields = [ export const defaultAdminLocationLevelFields = [
"id", "id",
@@ -35,12 +32,7 @@ export const defaultAdminInventoryItemFields = [
"stocked_quantity", "stocked_quantity",
"created_at", "created_at",
"updated_at", "updated_at",
...defaultAdminLocationLevelFields.map( "*location_levels",
(field) => `location_levels.${field.toString()}`
),
...defaultAdminProductsVariantFields
.filter((field) => !field.startsWith("*"))
.map((field) => `variant.${field}`),
] ]
export const retrieveTransformQueryConfig = { export const retrieveTransformQueryConfig = {
@@ -7,36 +7,32 @@ import {
remoteQueryObjectFromString, remoteQueryObjectFromString,
} from "@medusajs/utils" } from "@medusajs/utils"
import { AdminPostInventoryItemsReq } from "./validators"
import { createInventoryItemsWorkflow } from "@medusajs/core-flows" import { createInventoryItemsWorkflow } from "@medusajs/core-flows"
import {
AdminCreateInventoryItemType,
AdminGetInventoryItemsParamsType,
} from "./validators"
import { refetchInventoryItem } from "./helpers"
// Create inventory-item
export const POST = async ( export const POST = async (
req: AuthenticatedMedusaRequest<AdminPostInventoryItemsReq>, req: AuthenticatedMedusaRequest<AdminCreateInventoryItemType>,
res: MedusaResponse res: MedusaResponse
) => { ) => {
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
const { result } = await createInventoryItemsWorkflow(req.scope).run({ const { result } = await createInventoryItemsWorkflow(req.scope).run({
input: { items: [req.validatedBody] }, input: { items: [req.validatedBody] },
}) })
const [inventory_item] = await remoteQuery( const inventoryItem = await refetchInventoryItem(
remoteQueryObjectFromString({ result[0].id,
entryPoint: "inventory_items", req.scope,
variables: { req.remoteQueryConfig.fields
id: result[0].id,
},
fields: req.remoteQueryConfig.fields,
})
) )
res.status(200).json({ inventory_item }) res.status(200).json({ inventory_item: inventoryItem })
} }
// List inventory-items
export const GET = async ( export const GET = async (
req: AuthenticatedMedusaRequest, req: AuthenticatedMedusaRequest<AdminGetInventoryItemsParamsType>,
res: MedusaResponse res: MedusaResponse
) => { ) => {
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY) const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
@@ -1,408 +1,129 @@
import { z } from "zod"
import { import {
DateComparisonOperator, createFindParams,
FindParams, createOperatorMap,
NumericalComparisonOperator, createSelectParams,
StringComparisonOperator, } from "../../utils/validators"
extendedFindParamsMixin, import { optionalBooleanMapper } from "../../../utils/validators/is-boolean"
} from "../../../types/common"
import {
IsBoolean,
IsEmail,
IsNotEmpty,
IsNumber,
IsObject,
IsOptional,
IsString,
Min,
ValidateNested,
} from "class-validator"
import { Transform, Type } from "class-transformer"
import { IsType } from "../../../utils" export type AdminGetInventoryItemParamsType = z.infer<
typeof AdminGetInventoryItemParams
>
export const AdminGetInventoryItemParams = createSelectParams()
export class AdminGetInventoryItemsItemParams extends FindParams {} export type AdminGetInventoryItemsParamsType = z.infer<
typeof AdminGetInventoryItemsParams
/** >
* Parameters used to filter and configure the pagination of the retrieved inventory items. export const AdminGetInventoryItemsParams = createFindParams({
*/
export class AdminGetInventoryItemsParams extends extendedFindParamsMixin({
limit: 20, limit: 20,
offset: 0, offset: 0,
}) { }).merge(
/** z.object({
* IDs to filter inventory items by. q: z.string().optional(),
*/ id: z.union([z.string(), z.array(z.string())]).optional(),
@IsOptional() location_id: z.union([z.string(), z.array(z.string())]).optional(),
@IsType([String, [String]]) sku: z.union([z.string(), z.array(z.string())]).optional(),
id?: string | string[] origin_country: z.union([z.string(), z.array(z.string())]).optional(),
mid_code: z.union([z.string(), z.array(z.string())]).optional(),
hs_code: z.union([z.string(), z.array(z.string())]).optional(),
material: z.union([z.string(), z.array(z.string())]).optional(),
requires_shipping: z
.preprocess(
(val: any) => optionalBooleanMapper.get(val?.toLowerCase()),
z.boolean().optional()
)
.optional(),
weight: createOperatorMap(z.number(), parseFloat).optional(),
length: createOperatorMap(z.number(), parseFloat).optional(),
height: createOperatorMap(z.number(), parseFloat).optional(),
width: createOperatorMap(z.number(), parseFloat).optional(),
$and: z.lazy(() => AdminGetInventoryItemsParams.array()).optional(),
$or: z.lazy(() => AdminGetInventoryItemsParams.array()).optional(),
})
)
/** export type AdminGetInventoryLocationLevelParamsType = z.infer<
* Search terms to search inventory items' sku, title, and description. typeof AdminGetInventoryLocationLevelParams
*/ >
@IsOptional() export const AdminGetInventoryLocationLevelParams = createSelectParams()
@IsString()
q?: string
/** export type AdminGetInventoryLocationLevelsParamsType = z.infer<
* Location IDs to filter inventory items by. typeof AdminGetInventoryLocationLevelsParams
*/ >
@IsOptional() export const AdminGetInventoryLocationLevelsParams = createFindParams({
@IsType([String, [String]])
location_id?: string | string[]
/**
* SKUs to filter inventory items by.
*/
@IsOptional()
@IsType([String, [String]])
sku?: string | string[]
/**
* Origin countries to filter inventory items by.
*/
@IsOptional()
@IsType([String, [String]])
origin_country?: string | string[]
/**
* MID codes to filter inventory items by.
*/
@IsOptional()
@IsType([String, [String]])
mid_code?: string | string[]
/**
* Materials to filter inventory items by.
*/
@IsOptional()
@IsType([String, [String]])
material?: string | string[]
/**
* String filters to apply to inventory items' `hs_code` field.
*/
@IsOptional()
@IsType([String, [String], StringComparisonOperator])
hs_code?: string | string[] | StringComparisonOperator
/**
* Number filters to apply to inventory items' `weight` field.
*/
@IsOptional()
@IsType([Number, NumericalComparisonOperator])
weight?: number | NumericalComparisonOperator
/**
* Number filters to apply to inventory items' `length` field.
*/
@IsOptional()
@IsType([Number, NumericalComparisonOperator])
length?: number | NumericalComparisonOperator
/**
* Number filters to apply to inventory items' `height` field.
*/
@IsOptional()
@IsType([Number, NumericalComparisonOperator])
height?: number | NumericalComparisonOperator
/**
* Number filters to apply to inventory items' `width` field.
*/
@IsOptional()
@IsType([Number, NumericalComparisonOperator])
width?: number | NumericalComparisonOperator
/**
* Filter inventory items by whether they require shipping.
*/
@IsBoolean()
@IsOptional()
@Transform(({ value }) => value === "true")
requires_shipping?: boolean
}
export class AdminPostInventoryItemsItemLocationLevelsReq {
@IsString()
location_id: string
@IsNumber()
@IsOptional()
stocked_quantity?: number
@IsOptional()
@IsNumber()
incoming_quantity?: number
}
export class AdminPostInventoryItemsItemLocationLevelsBatchReq {
@ValidateNested({ each: true })
@Type(() => AdminPostInventoryItemsItemLocationLevelsReq)
creates: AdminPostInventoryItemsItemLocationLevelsReq[]
@IsString({ each: true })
deletes: string[]
}
// eslint-disable-next-line
export class AdminPostInventoryItemsItemLocationLevelsParams extends FindParams {}
/**
* @schema AdminPostInventoryItemsReq
* type: object
* description: "The details of the inventory item to create."
* properties:
* sku:
* description: The unique SKU of the associated Product Variant.
* type: string
* ean:
* description: The EAN number of the item.
* type: string
* upc:
* description: The UPC number of the item.
* type: string
* barcode:
* description: A generic GTIN field for the Product Variant.
* type: string
* hs_code:
* description: The Harmonized System code of the Inventory Item. May be used by Fulfillment Providers to pass customs information to shipping carriers.
* type: string
* inventory_quantity:
* description: The amount of stock kept of the associated Product Variant.
* type: integer
* default: 0
* allow_backorder:
* description: Whether the associated Product Variant can be purchased when out of stock.
* type: boolean
* manage_inventory:
* description: Whether Medusa should keep track of the inventory for the associated Product Variant.
* type: boolean
* default: true
* weight:
* description: The weight of the Inventory Item. May be used in shipping rate calculations.
* type: number
* length:
* description: The length of the Inventory Item. May be used in shipping rate calculations.
* type: number
* height:
* description: The height of the Inventory Item. May be used in shipping rate calculations.
* type: number
* width:
* description: The width of the Inventory Item. May be used in shipping rate calculations.
* type: number
* origin_country:
* description: The country in which the Inventory Item was produced. May be used by Fulfillment Providers to pass customs information to shipping carriers.
* type: string
* mid_code:
* description: The Manufacturers Identification code that identifies the manufacturer of the Inventory Item. May be used by Fulfillment Providers to pass customs information to shipping carriers.
* type: string
* material:
* description: The material and composition that the Inventory Item is made of, May be used by Fulfillment Providers to pass customs information to shipping carriers.
* type: string
* title:
* description: The inventory item's title.
* type: string
* description:
* description: The inventory item's description.
* type: string
* thumbnail:
* description: The inventory item's thumbnail.
* type: string
* metadata:
* description: An optional set of key-value pairs with additional information.
* type: object
* externalDocs:
* description: "Learn about the metadata attribute, and how to delete and update it."
* url: "https://docs.medusajs.com/development/entities/overview#metadata-attribute"
*/
export class AdminPostInventoryItemsReq {
@IsString()
@IsOptional()
sku?: string
@IsString()
@IsOptional()
hs_code?: string
@IsNumber()
@IsOptional()
weight?: number
@IsNumber()
@IsOptional()
length?: number
@IsNumber()
@IsOptional()
height?: number
@IsNumber()
@IsOptional()
width?: number
@IsString()
@IsOptional()
origin_country?: string
@IsString()
@IsOptional()
mid_code?: string
@IsString()
@IsOptional()
material?: string
@IsString()
@IsOptional()
title?: string
@IsString()
@IsOptional()
description?: string
@IsString()
@IsOptional()
thumbnail?: string
@IsObject()
@IsOptional()
metadata?: Record<string, unknown>
}
// eslint-disable-next-line max-len
export class AdminGetInventoryItemsItemLocationLevelsParams extends extendedFindParamsMixin(
{
limit: 50, limit: 50,
offset: 0, offset: 0,
} }).merge(
) { z.object({
/** location_id: z.union([z.string(), z.array(z.string())]).optional(),
* Location IDs to filter location levels. $and: z
*/ .lazy(() => AdminGetInventoryLocationLevelsParams.array())
@IsOptional() .optional(),
@IsString({ each: true }) $or: z.lazy(() => AdminGetInventoryLocationLevelsParams.array()).optional(),
location_id?: string[] })
} )
/**
* @schema AdminPostInventoryItemsItemLocationLevelsLevelReq
* type: object
* properties:
* stocked_quantity:
* description: the total stock quantity of an inventory item at the given location ID
* type: number
* incoming_quantity:
* description: the incoming stock quantity of an inventory item at the given location ID
* type: number
*/
export class AdminPostInventoryItemsItemLocationLevelsLevelReq {
@IsOptional()
@IsNumber()
@Min(0)
incoming_quantity?: number
@IsOptional() export type AdminCreateInventoryLocationLevelType = z.infer<
@IsNumber() typeof AdminCreateInventoryLocationLevel
@Min(0) >
stocked_quantity?: number export const AdminCreateInventoryLocationLevel = z
} .object({
location_id: z.string(),
stocked_quantity: z.number().min(0).optional(),
incoming_quantity: z.number().min(0).optional(),
})
.strict()
// eslint-disable-next-line export type AdminUpdateInventoryLocationLevelType = z.infer<
export class AdminPostInventoryItemsItemLocationLevelsLevelParams extends FindParams {} typeof AdminUpdateInventoryLocationLevel
/** >
* @schema AdminPostInventoryItemsInventoryItemReq export const AdminUpdateInventoryLocationLevel = z
* type: object .object({
* description: "The attributes to update in an inventory item." stocked_quantity: z.number().min(0).optional(),
* properties: incoming_quantity: z.number().min(0).optional(),
* hs_code: })
* description: The Harmonized System code of the Inventory Item. May be used by Fulfillment Providers to pass customs information to shipping carriers. .strict()
* type: string
* origin_country:
* description: The country in which the Inventory Item was produced. May be used by Fulfillment Providers to pass customs information to shipping carriers.
* type: string
* mid_code:
* description: The Manufacturers Identification code that identifies the manufacturer of the Inventory Item. May be used by Fulfillment Providers to pass customs information to shipping carriers.
* type: string
* material:
* description: The material and composition that the Inventory Item is made of, May be used by Fulfillment Providers to pass customs information to shipping carriers.
* type: string
* weight:
* description: The weight of the Inventory Item. May be used in shipping rate calculations.
* type: number
* height:
* description: The height of the Inventory Item. May be used in shipping rate calculations.
* type: number
* width:
* description: The width of the Inventory Item. May be used in shipping rate calculations.
* type: number
* length:
* description: The length of the Inventory Item. May be used in shipping rate calculations.
* type: number
* title:
* description: The inventory item's title.
* type: string
* description:
* description: The inventory item's description.
* type: string
* thumbnail:
* description: The inventory item's thumbnail.
* type: string
* requires_shipping:
* description: Whether the item requires shipping.
* type: boolean
*/
export class AdminPostInventoryItemsInventoryItemReq { export type AdminCreateInventoryItemType = z.infer<
@IsString() typeof AdminCreateInventoryItem
@IsOptional() >
sku?: string export const AdminCreateInventoryItem = z
.object({
sku: z.string().optional(),
hs_code: z.string().optional(),
weight: z.number().optional(),
length: z.number().optional(),
height: z.number().optional(),
width: z.number().optional(),
origin_country: z.string().optional(),
mid_code: z.string().optional(),
material: z.string().optional(),
title: z.string().optional(),
description: z.string().optional(),
requires_shipping: z.boolean().optional(),
thumbnail: z.string().optional(),
metadata: z.record(z.string(), z.unknown()).optional(),
})
.strict()
@IsOptional() export type AdminUpdateInventoryItemType = z.infer<
@IsString() typeof AdminUpdateInventoryItem
origin_country?: string >
export const AdminUpdateInventoryItem = z
@IsOptional() .object({
@IsString() sku: z.string().optional(),
hs_code?: string hs_code: z.string().optional(),
weight: z.number().optional(),
@IsOptional() length: z.number().optional(),
@IsString() height: z.number().optional(),
mid_code?: string width: z.number().optional(),
origin_country: z.string().optional(),
@IsOptional() mid_code: z.string().optional(),
@IsString() material: z.string().optional(),
material?: string title: z.string().optional(),
description: z.string().optional(),
@IsOptional() requires_shipping: z.boolean().optional(),
@IsNumber() thumbnail: z.string().optional(),
weight?: number metadata: z.record(z.string(), z.unknown()).optional(),
})
@IsOptional() .strict()
@IsNumber()
height?: number
@IsOptional()
@IsNumber()
length?: number
@IsOptional()
@IsNumber()
width?: number
@IsString()
@IsOptional()
title?: string
@IsString()
@IsOptional()
description?: string
@IsString()
@IsOptional()
thumbnail?: string
@IsBoolean()
@IsOptional()
requires_shipping?: boolean
}
export class AdminPostInventoryItemsInventoryItemParams extends FindParams {}
@@ -1,6 +1,7 @@
import { MedusaRequest, MedusaResponse } from "../../../../../types/routing" import { MedusaRequest, MedusaResponse } from "../../../../../types/routing"
import { refreshInviteTokensWorkflow } from "@medusajs/core-flows" import { refreshInviteTokensWorkflow } from "@medusajs/core-flows"
import { refetchInvite } from "../../helpers"
export const POST = async (req: MedusaRequest, res: MedusaResponse) => { export const POST = async (req: MedusaRequest, res: MedusaResponse) => {
const workflow = refreshInviteTokensWorkflow(req.scope) const workflow = refreshInviteTokensWorkflow(req.scope)
@@ -9,7 +10,12 @@ export const POST = async (req: MedusaRequest, res: MedusaResponse) => {
invite_ids: [req.params.id], invite_ids: [req.params.id],
} }
const { result: invites } = await workflow.run({ input }) const { result } = await workflow.run({ input })
const invite = await refetchInvite(
result[0].id,
req.scope,
req.remoteQueryConfig.fields
)
res.status(200).json({ invite: invites[0] }) res.status(200).json({ invite })
} }
@@ -2,31 +2,21 @@ import {
AuthenticatedMedusaRequest, AuthenticatedMedusaRequest,
MedusaResponse, MedusaResponse,
} from "../../../../types/routing" } from "../../../../types/routing"
import { import { MedusaError } from "@medusajs/utils"
ContainerRegistrationKeys,
MedusaError,
remoteQueryObjectFromString,
} from "@medusajs/utils"
import { deleteInvitesWorkflow } from "@medusajs/core-flows" import { deleteInvitesWorkflow } from "@medusajs/core-flows"
import { refetchInvite } from "../helpers"
// Get invite
export const GET = async ( export const GET = async (
req: AuthenticatedMedusaRequest, req: AuthenticatedMedusaRequest,
res: MedusaResponse res: MedusaResponse
) => { ) => {
const { id } = req.params const { id } = req.params
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY) const invite = await refetchInvite(
const query = remoteQueryObjectFromString({
entryPoint: "invite",
variables: {
id, id,
}, req.scope,
fields: req.retrieveConfig.select as string[], req.remoteQueryConfig.fields
}) )
const [invite] = await remoteQuery(query)
if (!invite) { if (!invite) {
throw new MedusaError( throw new MedusaError(
@@ -38,7 +28,6 @@ export const GET = async (
res.status(200).json({ invite }) res.status(200).json({ invite })
} }
// delete invite
export const DELETE = async ( export const DELETE = async (
req: AuthenticatedMedusaRequest, req: AuthenticatedMedusaRequest,
res: MedusaResponse res: MedusaResponse
@@ -5,11 +5,10 @@ import {
AuthenticatedMedusaRequest, AuthenticatedMedusaRequest,
MedusaResponse, MedusaResponse,
} from "../../../../types/routing" } from "../../../../types/routing"
import { AdminInviteAcceptType } from "../validators"
import { AdminPostInvitesInviteAcceptReq } from "../validators"
export const POST = async ( export const POST = async (
req: AuthenticatedMedusaRequest<AdminPostInvitesInviteAcceptReq>, req: AuthenticatedMedusaRequest<AdminInviteAcceptType>,
res: MedusaResponse res: MedusaResponse
) => { ) => {
if (req.auth.actor_id) { if (req.auth.actor_id) {
@@ -0,0 +1,23 @@
import { MedusaContainer } from "@medusajs/types"
import {
ContainerRegistrationKeys,
remoteQueryObjectFromString,
} from "@medusajs/utils"
export const refetchInvite = async (
inviteId: string,
scope: MedusaContainer,
fields: string[]
) => {
const remoteQuery = scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
const queryObject = remoteQueryObjectFromString({
entryPoint: "invite",
variables: {
filters: { id: inviteId },
},
fields: fields,
})
const invites = await remoteQuery(queryObject)
return invites[0]
}
@@ -1,42 +1,25 @@
import * as QueryConfig from "./query-config" import * as QueryConfig from "./query-config"
import { import {
AdminCreateInviteRequest, AdminCreateInvite,
AdminGetInvitesInviteParams, AdminGetInviteAcceptParams,
AdminGetInviteParams,
AdminGetInvitesParams, AdminGetInvitesParams,
AdminPostInvitesInviteAcceptParams, AdminInviteAccept,
AdminPostInvitesInviteAcceptReq,
} from "./validators" } from "./validators"
import { transformBody, transformQuery } from "../../../api/middlewares"
import { MiddlewareRoute } from "../../../types/middlewares" import { MiddlewareRoute } from "../../../types/middlewares"
import { authenticate } from "../../../utils/authenticate-middleware" import { authenticate } from "../../../utils/authenticate-middleware"
import { validateAndTransformQuery } from "../../utils/validate-query"
import { validateAndTransformBody } from "../../utils/validate-body"
export const adminInviteRoutesMiddlewares: MiddlewareRoute[] = [ export const adminInviteRoutesMiddlewares: MiddlewareRoute[] = [
{
method: "ALL",
matcher: "/admin/invites",
middlewares: [authenticate("admin", ["session", "bearer"])],
},
{
method: "POST",
matcher: "/admin/invites/accept",
middlewares: [
authenticate("admin", ["session", "bearer"], {
allowUnregistered: true,
}),
],
},
{
method: ["GET", "DELETE"],
matcher: "/admin/invites/:id",
middlewares: [authenticate("admin", ["session", "bearer"])],
},
{ {
method: ["GET"], method: ["GET"],
matcher: "/admin/invites", matcher: "/admin/invites",
middlewares: [ middlewares: [
transformQuery( authenticate("admin", ["session", "bearer", "api-key"]),
validateAndTransformQuery(
AdminGetInvitesParams, AdminGetInvitesParams,
QueryConfig.listTransformQueryConfig QueryConfig.listTransformQueryConfig
), ),
@@ -45,22 +28,52 @@ export const adminInviteRoutesMiddlewares: MiddlewareRoute[] = [
{ {
method: ["POST"], method: ["POST"],
matcher: "/admin/invites", matcher: "/admin/invites",
middlewares: [transformBody(AdminCreateInviteRequest)], middlewares: [
authenticate("admin", ["session", "bearer", "api-key"]),
validateAndTransformBody(AdminCreateInvite),
validateAndTransformQuery(
AdminGetInviteParams,
QueryConfig.retrieveTransformQueryConfig
),
],
}, },
{ {
method: "POST", method: "POST",
matcher: "/admin/invites/accept", matcher: "/admin/invites/accept",
middlewares: [ middlewares: [
transformBody(AdminPostInvitesInviteAcceptReq), authenticate("admin", ["session", "bearer"], {
transformQuery(AdminPostInvitesInviteAcceptParams), allowUnregistered: true,
}),
validateAndTransformBody(AdminInviteAccept),
validateAndTransformQuery(
AdminGetInviteAcceptParams,
QueryConfig.retrieveTransformQueryConfig
),
], ],
}, },
{ {
method: ["GET"], method: ["GET"],
matcher: "/admin/invites/:id", matcher: "/admin/invites/:id",
middlewares: [ middlewares: [
transformQuery( authenticate("admin", ["session", "bearer", "api-key"]),
AdminGetInvitesInviteParams, validateAndTransformQuery(
AdminGetInviteParams,
QueryConfig.retrieveTransformQueryConfig
),
],
},
{
method: ["DELETE"],
matcher: "/admin/invites/:id",
middlewares: [authenticate("admin", ["session", "bearer", "api-key"])],
},
{
method: "POST",
matcher: "/admin/invites/:id/resend",
middlewares: [
authenticate("admin", ["session", "bearer", "api-key"]),
validateAndTransformQuery(
AdminGetInviteParams,
QueryConfig.retrieveTransformQueryConfig QueryConfig.retrieveTransformQueryConfig
), ),
], ],
@@ -1,5 +1,3 @@
export const defaultAdminInviteRelations = []
export const allowedAdminInviteRelations = []
export const defaultAdminInviteFields = [ export const defaultAdminInviteFields = [
"id", "id",
"email", "email",
@@ -13,9 +11,7 @@ export const defaultAdminInviteFields = [
] ]
export const retrieveTransformQueryConfig = { export const retrieveTransformQueryConfig = {
defaultFields: defaultAdminInviteFields, defaults: defaultAdminInviteFields,
defaultRelations: defaultAdminInviteRelations,
allowedRelations: allowedAdminInviteRelations,
isList: false, isList: false,
} }
@@ -7,32 +7,27 @@ import {
remoteQueryObjectFromString, remoteQueryObjectFromString,
} from "@medusajs/utils" } from "@medusajs/utils"
import { CreateInviteDTO } from "@medusajs/types"
import { createInvitesWorkflow } from "@medusajs/core-flows" import { createInvitesWorkflow } from "@medusajs/core-flows"
import { AdminCreateInviteType, AdminGetInvitesParamsType } from "./validators"
import { refetchInvite } from "./helpers"
// List invites
export const GET = async ( export const GET = async (
req: AuthenticatedMedusaRequest, req: AuthenticatedMedusaRequest<AdminGetInvitesParamsType>,
res: MedusaResponse res: MedusaResponse
) => { ) => {
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY) const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
const queryObject = remoteQueryObjectFromString({
const query = remoteQueryObjectFromString({
entryPoint: "invite", entryPoint: "invite",
variables: { variables: {
filters: req.filterableFields, filters: req.filterableFields,
order: req.listConfig.order, ...req.remoteQueryConfig.pagination,
skip: req.listConfig.skip,
take: req.listConfig.take,
}, },
fields: req.listConfig.select as string[], fields: req.remoteQueryConfig.fields,
}) })
const { rows: invites, metadata } = await remoteQuery({ const { rows: invites, metadata } = await remoteQuery(queryObject)
...query,
})
res.status(200).json({ res.json({
invites, invites,
count: metadata.count, count: metadata.count,
offset: metadata.skip, offset: metadata.skip,
@@ -40,9 +35,8 @@ export const GET = async (
}) })
} }
// Create invite
export const POST = async ( export const POST = async (
req: AuthenticatedMedusaRequest<CreateInviteDTO>, req: AuthenticatedMedusaRequest<AdminCreateInviteType>,
res: MedusaResponse res: MedusaResponse
) => { ) => {
const workflow = createInvitesWorkflow(req.scope) const workflow = createInvitesWorkflow(req.scope)
@@ -54,7 +48,11 @@ export const POST = async (
} }
const { result } = await workflow.run(input) const { result } = await workflow.run(input)
const invite = await refetchInvite(
result[0].id,
req.scope,
req.remoteQueryConfig.fields
)
const [invite] = result
res.status(200).json({ invite }) res.status(200).json({ invite })
} }
@@ -1,146 +1,51 @@
import { Type } from "class-transformer" import { z } from "zod"
import { import {
IsEmail, createFindParams,
IsNotEmpty, createOperatorMap,
IsOptional, createSelectParams,
IsString, } from "../../utils/validators"
ValidateNested,
} from "class-validator"
import {
DateComparisonOperator,
FindParams,
extendedFindParamsMixin,
} from "../../../types/common"
import { IsType } from "../../../utils"
export class AdminGetInvitesInviteParams extends FindParams {} export type AdminGetInviteParamsType = z.infer<typeof AdminGetInviteParams>
export const AdminGetInviteParams = createSelectParams()
export class AdminGetInvitesParams extends extendedFindParamsMixin({ export type AdminGetInvitesParamsType = z.infer<typeof AdminGetInvitesParams>
export const AdminGetInvitesParams = createFindParams({
limit: 50, limit: 50,
offset: 0, offset: 0,
}) { }).merge(
/** z.object({
* IDs to filter invites by. q: z.string().optional(),
*/ id: z.union([z.string(), z.array(z.string())]).optional(),
@IsOptional() email: z.union([z.string(), z.array(z.string())]).optional(),
@IsType([String, [String]]) created_at: createOperatorMap().optional(),
id?: string | string[] updated_at: createOperatorMap().optional(),
deleted_at: createOperatorMap().optional(),
$and: z.lazy(() => AdminGetInvitesParams.array()).optional(),
$or: z.lazy(() => AdminGetInvitesParams.array()).optional(),
})
)
/** export type AdminGetInviteAcceptParamsType = z.infer<
* The field to sort the data by. By default, the sort order is ascending. To change the order to descending, prefix the field name with `-`. typeof AdminGetInviteAcceptParams
*/ >
@IsString() export const AdminGetInviteAcceptParams = createSelectParams().merge(
@IsOptional() z.object({
order?: string token: z.string(),
})
)
/** export type AdminCreateInviteType = z.infer<typeof AdminCreateInvite>
* Date filters to apply on the invites' `update_at` date. export const AdminCreateInvite = z
*/ .object({
@IsOptional() email: z.string(),
@ValidateNested() })
@Type(() => DateComparisonOperator) .strict()
updated_at?: DateComparisonOperator
/** export type AdminInviteAcceptType = z.infer<typeof AdminInviteAccept>
* Date filters to apply on the customer invites' `created_at` date. export const AdminInviteAccept = z
*/ .object({
@IsOptional() email: z.string().optional(),
@ValidateNested() first_name: z.string().optional(),
@Type(() => DateComparisonOperator) last_name: z.string().optional(),
created_at?: DateComparisonOperator })
.strict()
/**
* Date filters to apply on the invites' `deleted_at` date.
*/
@IsOptional()
@ValidateNested()
@Type(() => DateComparisonOperator)
deleted_at?: DateComparisonOperator
/**
* Filter to apply on the invites' `email` field.
*/
@IsOptional()
@IsString()
email?: string
/**
* Comma-separated fields that should be included in the returned invites.
*/
@IsOptional()
@IsString()
fields?: string
/**
* The term to search invites emails.
*/
@IsString()
@IsOptional()
q?: string
}
export class AdminCreateInviteRequest {
@IsEmail()
email: string
}
/**
* @schema AdminPostInvitesInviteAcceptReq
* type: object
* description: "The details of the invite to be accepted."
* required:
* - token
* - user
* properties:
* token:
* description: "The token of the invite to accept. This is a unique token generated when the invite was created or resent."
* type: string
* user:
* description: "The details of the user to create."
* type: object
* required:
* - first_name
* - last_name
* - password
* properties:
* first_name:
* type: string
* description: the first name of the User
* last_name:
* type: string
* description: the last name of the User
* password:
* description: The password for the User
* type: string
* format: password
*/
export class AdminPostInvitesInviteAcceptReq {
/**
* If email is not passed, we default to using the email of the invite.
*/
@IsString()
@IsOptional()
email: string
/**
* The invite's first name.
*/
@IsString()
@IsOptional()
first_name: string
/**
* The invite's last name.
*/
@IsString()
@IsOptional()
last_name: string
}
export class AdminPostInvitesInviteAcceptParams {
@IsString()
@IsNotEmpty()
token: string
@IsOptional()
expand = undefined
}
@@ -1,21 +1,15 @@
import { capturePaymentWorkflow } from "@medusajs/core-flows" import { capturePaymentWorkflow } from "@medusajs/core-flows"
import {
ContainerRegistrationKeys,
remoteQueryObjectFromString,
} from "@medusajs/utils"
import { import {
AuthenticatedMedusaRequest, AuthenticatedMedusaRequest,
MedusaResponse, MedusaResponse,
} from "../../../../../types/routing" } from "../../../../../types/routing"
import { defaultAdminPaymentFields } from "../../query-config" import { refetchPayment } from "../../helpers"
import { AdminPostPaymentsCapturesReq } from "../../validators" import { AdminCreatePaymentCaptureType } from "../../validators"
export const POST = async ( export const POST = async (
req: AuthenticatedMedusaRequest<AdminPostPaymentsCapturesReq>, req: AuthenticatedMedusaRequest<AdminCreatePaymentCaptureType>,
res: MedusaResponse res: MedusaResponse
) => { ) => {
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
const { id } = req.params const { id } = req.params
const { errors } = await capturePaymentWorkflow(req.scope).run({ const { errors } = await capturePaymentWorkflow(req.scope).run({
@@ -31,13 +25,11 @@ export const POST = async (
throw errors[0].error throw errors[0].error
} }
const query = remoteQueryObjectFromString({ const payment = await refetchPayment(
entryPoint: "payments", id,
variables: { id }, req.scope,
fields: defaultAdminPaymentFields, req.remoteQueryConfig.fields
}) )
const [payment] = await remoteQuery(query)
res.status(200).json({ payment }) res.status(200).json({ payment })
} }
@@ -1,23 +1,16 @@
import { refundPaymentWorkflow } from "@medusajs/core-flows" import { refundPaymentWorkflow } from "@medusajs/core-flows"
import {
ContainerRegistrationKeys,
remoteQueryObjectFromString,
} from "@medusajs/utils"
import { import {
AuthenticatedMedusaRequest, AuthenticatedMedusaRequest,
MedusaResponse, MedusaResponse,
} from "../../../../../types/routing" } from "../../../../../types/routing"
import { defaultAdminPaymentFields } from "../../query-config" import { refetchPayment } from "../../helpers"
import { AdminPostPaymentsRefundsReq } from "../../validators" import { AdminCreatePaymentRefundType } from "../../validators"
export const POST = async ( export const POST = async (
req: AuthenticatedMedusaRequest<AdminPostPaymentsRefundsReq>, req: AuthenticatedMedusaRequest<AdminCreatePaymentRefundType>,
res: MedusaResponse res: MedusaResponse
) => { ) => {
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
const { id } = req.params const { id } = req.params
const { errors } = await refundPaymentWorkflow(req.scope).run({ const { errors } = await refundPaymentWorkflow(req.scope).run({
input: { input: {
payment_id: id, payment_id: id,
@@ -31,13 +24,11 @@ export const POST = async (
throw errors[0].error throw errors[0].error
} }
const query = remoteQueryObjectFromString({ const payment = await refetchPayment(
entryPoint: "payments", id,
variables: { id }, req.scope,
fields: defaultAdminPaymentFields, req.remoteQueryConfig.fields
}) )
const [payment] = await remoteQuery(query)
res.status(200).json({ payment }) res.status(200).json({ payment })
} }
@@ -1,28 +1,19 @@
import { Modules } from "@medusajs/modules-sdk"
import {
ContainerRegistrationKeys,
remoteQueryObjectFromString,
} from "@medusajs/utils"
import { import {
AuthenticatedMedusaRequest, AuthenticatedMedusaRequest,
MedusaResponse, MedusaResponse,
} from "../../../../types/routing" } from "../../../../types/routing"
import { AdminGetPaymentParamsType } from "../validators"
import { refetchPayment } from "../helpers"
export const GET = async ( export const GET = async (
req: AuthenticatedMedusaRequest, req: AuthenticatedMedusaRequest<AdminGetPaymentParamsType>,
res: MedusaResponse res: MedusaResponse
) => { ) => {
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY) const payment = await refetchPayment(
req.params.id,
const { id } = req.params req.scope,
req.remoteQueryConfig.fields
const query = remoteQueryObjectFromString({ )
entryPoint: Modules.PAYMENT,
variables: { id },
fields: req.retrieveConfig.select as string[],
})
const [payment] = await remoteQuery(query)
res.status(200).json({ payment }) res.status(200).json({ payment })
} }
@@ -0,0 +1,23 @@
import { MedusaContainer } from "@medusajs/types"
import {
ContainerRegistrationKeys,
remoteQueryObjectFromString,
} from "@medusajs/utils"
export const refetchPayment = async (
paymentId: string,
scope: MedusaContainer,
fields: string[]
) => {
const remoteQuery = scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
const queryObject = remoteQueryObjectFromString({
entryPoint: "payment",
variables: {
filters: { id: paymentId },
},
fields: fields,
})
const payments = await remoteQuery(queryObject)
return payments[0]
}
@@ -1,25 +1,28 @@
import { transformBody, transformQuery } from "../../../api/middlewares"
import { MiddlewareRoute } from "../../../types/middlewares" import { MiddlewareRoute } from "../../../types/middlewares"
import { authenticate } from "../../../utils/authenticate-middleware" import { authenticate } from "../../../utils/authenticate-middleware"
import { unlessPath } from "../../utils/unless-path"
import { validateAndTransformBody } from "../../utils/validate-body"
import { validateAndTransformQuery } from "../../utils/validate-query"
import * as queryConfig from "./query-config" import * as queryConfig from "./query-config"
import { import {
AdminCreatePaymentCapture,
AdminCreatePaymentRefund,
AdminGetPaymentParams,
AdminGetPaymentProvidersParams,
AdminGetPaymentsParams, AdminGetPaymentsParams,
AdminGetPaymentsPaymentProvidersParams,
AdminPostPaymentsCapturesReq,
AdminPostPaymentsRefundsReq,
} from "./validators" } from "./validators"
export const adminPaymentRoutesMiddlewares: MiddlewareRoute[] = [ export const adminPaymentRoutesMiddlewares: MiddlewareRoute[] = [
{ {
method: "ALL", method: "ALL",
matcher: "/admin/payments", matcher: "/admin/payments",
middlewares: [authenticate("admin", ["session", "bearer"])], middlewares: [authenticate("admin", ["session", "bearer", "api-key"])],
}, },
{ {
method: ["GET"], method: ["GET"],
matcher: "/admin/payments", matcher: "/admin/payments",
middlewares: [ middlewares: [
transformQuery( validateAndTransformQuery(
AdminGetPaymentsParams, AdminGetPaymentsParams,
queryConfig.listTransformQueryConfig queryConfig.listTransformQueryConfig
), ),
@@ -29,8 +32,8 @@ export const adminPaymentRoutesMiddlewares: MiddlewareRoute[] = [
method: ["GET"], method: ["GET"],
matcher: "/admin/payments/payment-providers", matcher: "/admin/payments/payment-providers",
middlewares: [ middlewares: [
transformQuery( validateAndTransformQuery(
AdminGetPaymentsPaymentProvidersParams, AdminGetPaymentProvidersParams,
queryConfig.listTransformPaymentProvidersQueryConfig queryConfig.listTransformPaymentProvidersQueryConfig
), ),
], ],
@@ -39,20 +42,35 @@ export const adminPaymentRoutesMiddlewares: MiddlewareRoute[] = [
method: ["GET"], method: ["GET"],
matcher: "/admin/payments/:id", matcher: "/admin/payments/:id",
middlewares: [ middlewares: [
transformQuery( unlessPath(
AdminGetPaymentsParams, new RegExp("/admin/payments/payment-providers"),
validateAndTransformQuery(
AdminGetPaymentParams,
queryConfig.retrieveTransformQueryConfig queryConfig.retrieveTransformQueryConfig
)
), ),
], ],
}, },
{ {
method: ["POST"], method: ["POST"],
matcher: "/admin/payments/:id/capture", matcher: "/admin/payments/:id/capture",
middlewares: [transformBody(AdminPostPaymentsCapturesReq)], middlewares: [
validateAndTransformBody(AdminCreatePaymentCapture),
validateAndTransformQuery(
AdminGetPaymentParams,
queryConfig.retrieveTransformQueryConfig
),
],
}, },
{ {
method: ["POST"], method: ["POST"],
matcher: "/admin/payments/:id/refund", matcher: "/admin/payments/:id/refund",
middlewares: [transformBody(AdminPostPaymentsRefundsReq)], middlewares: [
validateAndTransformBody(AdminCreatePaymentRefund),
validateAndTransformQuery(
AdminGetPaymentParams,
queryConfig.retrieveTransformQueryConfig
),
],
}, },
] ]
@@ -1,30 +1,33 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { IPaymentModuleService } from "@medusajs/types"
import { import {
AuthenticatedMedusaRequest, AuthenticatedMedusaRequest,
MedusaResponse, MedusaResponse,
} from "../../../../types/routing" } from "../../../../types/routing"
import { AdminGetPaymentsPaymentProvidersParams } from "../validators" import { AdminGetPaymentProvidersParamsType } from "../validators"
import {
ContainerRegistrationKeys,
remoteQueryObjectFromString,
} from "@medusajs/utils"
export const GET = async ( export const GET = async (
req: AuthenticatedMedusaRequest<AdminGetPaymentsPaymentProvidersParams>, req: AuthenticatedMedusaRequest<AdminGetPaymentProvidersParamsType>,
res: MedusaResponse res: MedusaResponse
) => { ) => {
const paymentModule = req.scope.resolve<IPaymentModuleService>( const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
ModuleRegistrationName.PAYMENT const queryObject = remoteQueryObjectFromString({
) entryPoint: "payment_provider",
variables: {
const [payment_providers, count] = filters: req.filterableFields,
await paymentModule.listAndCountPaymentProviders(req.filterableFields, { ...req.remoteQueryConfig.pagination,
skip: req.listConfig.skip, },
take: req.listConfig.take, fields: req.remoteQueryConfig.fields,
}) })
res.status(200).json({ const { rows: payment_providers, metadata } = await remoteQuery(queryObject)
count,
res.json({
payment_providers, payment_providers,
offset: req.listConfig.skip, count: metadata.count,
limit: req.listConfig.take, offset: metadata.skip,
limit: metadata.take,
}) })
} }
@@ -11,29 +11,19 @@ export const defaultAdminPaymentFields = [
"refunds.amount", "refunds.amount",
] ]
export const defaultAdminPaymentRelations = ["captures", "refunds"]
export const allowedAdminPaymentRelations = ["captures", "refunds"]
export const listTransformQueryConfig = { export const listTransformQueryConfig = {
defaultFields: defaultAdminPaymentFields, defaults: defaultAdminPaymentFields,
defaultRelations: defaultAdminPaymentRelations,
allowedRelations: allowedAdminPaymentRelations,
isList: true, isList: true,
} }
export const retrieveTransformQueryConfig = { export const retrieveTransformQueryConfig = {
defaultFields: defaultAdminPaymentFields, defaults: defaultAdminPaymentFields,
defaultRelations: defaultAdminPaymentRelations,
allowedRelations: allowedAdminPaymentRelations,
isList: false, isList: false,
} }
export const defaultAdminPaymentPaymentProviderFields = ["id", "is_enabled"] export const defaultAdminPaymentPaymentProviderFields = ["id", "is_enabled"]
export const listTransformPaymentProvidersQueryConfig = { export const listTransformPaymentProvidersQueryConfig = {
defaultFields: defaultAdminPaymentPaymentProviderFields, defaults: defaultAdminPaymentPaymentProviderFields,
defaultRelations: [],
allowedRelations: [],
isList: true, isList: true,
} }
@@ -1,4 +1,3 @@
import { Modules } from "@medusajs/modules-sdk"
import { import {
ContainerRegistrationKeys, ContainerRegistrationKeys,
remoteQueryObjectFromString, remoteQueryObjectFromString,
@@ -7,27 +6,25 @@ import {
AuthenticatedMedusaRequest, AuthenticatedMedusaRequest,
MedusaResponse, MedusaResponse,
} from "../../../types/routing" } from "../../../types/routing"
import { AdminGetPaymentsParamsType } from "./validators"
export const GET = async ( export const GET = async (
req: AuthenticatedMedusaRequest, req: AuthenticatedMedusaRequest<AdminGetPaymentsParamsType>,
res: MedusaResponse res: MedusaResponse
) => { ) => {
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY) const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
const queryObject = remoteQueryObjectFromString({
const query = remoteQueryObjectFromString({ entryPoint: "payment",
entryPoint: Modules.PAYMENT,
variables: { variables: {
filters: req.filterableFields, filters: req.filterableFields,
order: req.listConfig.order, ...req.remoteQueryConfig.pagination,
skip: req.listConfig.skip,
take: req.listConfig.take,
}, },
fields: req.listConfig.select as string[], fields: req.remoteQueryConfig.fields,
}) })
const { rows: payments, metadata } = await remoteQuery(query) const { rows: payments, metadata } = await remoteQuery(queryObject)
res.status(200).json({ res.json({
payments, payments,
count: metadata.count, count: metadata.count,
offset: metadata.skip, offset: metadata.skip,
@@ -1,79 +1,57 @@
import { Type } from "class-transformer" import { z } from "zod"
import { IsBoolean, IsInt, IsOptional, ValidateNested } from "class-validator"
import { import {
DateComparisonOperator, createFindParams,
FindParams, createOperatorMap,
extendedFindParamsMixin, createSelectParams,
} from "../../../types/common" } from "../../utils/validators"
import { IsType } from "../../../utils"
export class AdminGetPaymentsPaymentParams extends FindParams {} export type AdminGetPaymentParamsType = z.infer<typeof AdminGetPaymentParams>
export const AdminGetPaymentParams = createSelectParams()
export class AdminGetPaymentsParams extends extendedFindParamsMixin({ export type AdminGetPaymentsParamsType = z.infer<typeof AdminGetPaymentsParams>
export const AdminGetPaymentsParams = createFindParams({
limit: 20, limit: 20,
offset: 0, offset: 0,
}) { }).merge(
/** z.object({
* IDs to filter users by. id: z.union([z.string(), z.array(z.string())]).optional(),
*/ created_at: createOperatorMap().optional(),
@IsOptional() updated_at: createOperatorMap().optional(),
@IsType([String, [String]]) deleted_at: createOperatorMap().optional(),
id?: string | string[] $and: z.lazy(() => AdminGetPaymentsParams.array()).optional(),
$or: z.lazy(() => AdminGetPaymentsParams.array()).optional(),
})
)
/** export type AdminGetPaymentProvidersParamsType = z.infer<
* Date filters to apply on the users' `update_at` date. typeof AdminGetPaymentProvidersParams
*/ >
@IsOptional() export const AdminGetPaymentProvidersParams = createFindParams({
@ValidateNested()
@Type(() => DateComparisonOperator)
updated_at?: DateComparisonOperator
/**
* Date filters to apply on the customer users' `created_at` date.
*/
@IsOptional()
@ValidateNested()
@Type(() => DateComparisonOperator)
created_at?: DateComparisonOperator
/**
* Date filters to apply on the users' `deleted_at` date.
*/
@IsOptional()
@ValidateNested()
@Type(() => DateComparisonOperator)
deleted_at?: DateComparisonOperator
}
export class AdminPostPaymentsCapturesReq {
@IsInt()
@IsOptional()
amount?: number
}
export class AdminPostPaymentsRefundsReq {
@IsInt()
@IsOptional()
amount?: number
}
export class AdminGetPaymentsPaymentProvidersParams extends extendedFindParamsMixin(
{
limit: 20, limit: 20,
offset: 0, offset: 0,
} }).merge(
) { z.object({
/** id: z.union([z.string(), z.array(z.string())]).optional(),
* IDs to filter users by. is_enabled: z.boolean().optional(),
*/ $and: z.lazy(() => AdminGetPaymentProvidersParams.array()).optional(),
@IsOptional() $or: z.lazy(() => AdminGetPaymentProvidersParams.array()).optional(),
@IsType([String, [String]]) })
id?: string | string[] )
/** export type AdminCreatePaymentCaptureType = z.infer<
* Filter providers by `enabled` flag typeof AdminCreatePaymentCapture
*/ >
@IsBoolean() export const AdminCreatePaymentCapture = z
@IsOptional() .object({
is_enabled?: boolean amount: z.number().optional(),
} })
.strict()
export type AdminCreatePaymentRefundType = z.infer<
typeof AdminCreatePaymentRefund
>
export const AdminCreatePaymentRefund = z
.object({
amount: z.number().optional(),
})
.strict()
@@ -1,12 +1,15 @@
import { MedusaContainer } from "@medusajs/types" import { MedusaContainer } from "@medusajs/types"
import { remoteQueryObjectFromString } from "@medusajs/utils" import {
ContainerRegistrationKeys,
remoteQueryObjectFromString,
} from "@medusajs/utils"
export const refetchProductType = async ( export const refetchProductType = async (
productTypeId: string, productTypeId: string,
scope: MedusaContainer, scope: MedusaContainer,
fields: string[] fields: string[]
) => { ) => {
const remoteQuery = scope.resolve("remoteQuery") const remoteQuery = scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
const queryObject = remoteQueryObjectFromString({ const queryObject = remoteQueryObjectFromString({
entryPoint: "product_type", entryPoint: "product_type",
variables: { variables: {
@@ -0,0 +1,15 @@
import { NextFunction } from "express"
import { MedusaRequest, MedusaResponse } from "../../types/routing"
import { MiddlewareFunction } from "../../types/middlewares"
// Due to how our route loader works, where we load all middlewares before routes, ambiguous routes end up having all middlewares on different routes executed before the route handler is.
// This function allows us to skip middlewares for particular routes, so we can temporarily solve this without completely breaking the route loader for everyone.
export const unlessPath =
(onPath: RegExp, middleware: MiddlewareFunction) =>
(req: MedusaRequest, res: MedusaResponse, next: NextFunction) => {
if (onPath.test(req.path)) {
return next()
} else {
return middleware(req, res, next)
}
}