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

This commit is contained in:
Stevche Radevski
2024-04-18 08:51:43 +02:00
committed by GitHub
parent 2a835cae13
commit 44829f296a
35 changed files with 708 additions and 1035 deletions
@@ -334,14 +334,14 @@ medusaIntegrationTestRunner({
it("should delete an inventory location level and create a new one", async () => {
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",
},
],
deletes: [locationId],
delete: [locationId],
},
adminHeaders
)
@@ -386,6 +386,7 @@ medusaIntegrationTestRunner({
id: expect.any(String),
object: "inventory-level",
deleted: true,
parent: expect.any(Object),
})
})
+1 -1
View File
@@ -32,7 +32,7 @@ export const joinerConfig: ModuleJoinerConfig = {
schema: moduleSchema,
alias: [
{
name: ["inventory_items", "inventory"],
name: ["inventory_items", "inventory_item", "inventory"],
args: {
entity: "InventoryItem",
},
@@ -1,8 +1,4 @@
import {
AdminFulfillmentSetsDeleteResponse,
IFulfillmentModuleService,
} from "@medusajs/types"
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { AdminFulfillmentSetsDeleteResponse } from "@medusajs/types"
import { deleteFulfillmentSetsWorkflow } from "@medusajs/core-flows"
import {
@@ -16,13 +12,6 @@ export const DELETE = async (
) => {
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({
input: { ids: [id] },
throwOnError: false,
@@ -1,17 +1,12 @@
import { createServiceZonesWorkflow } from "@medusajs/core-flows"
import {
ContainerRegistrationKeys,
remoteQueryObjectFromString,
} from "@medusajs/utils"
import { MedusaRequest, MedusaResponse } from "../../../../../types/routing"
import { AdminCreateFulfillmentSetServiceZonesType } from "../../validators"
import { refetchFulfillmentSet } from "../../helpers"
export const POST = async (
req: MedusaRequest<AdminCreateFulfillmentSetServiceZonesType>,
res: MedusaResponse
) => {
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
const workflowInput = {
data: [
{
@@ -31,15 +26,11 @@ export const POST = async (
throw errors[0].error
}
const [fulfillment_set] = await remoteQuery(
remoteQueryObjectFromString({
entryPoint: "fulfillment_sets",
variables: {
id: req.params.id,
},
fields: req.remoteQueryConfig.fields,
})
const fulfillmentSet = await refetchFulfillmentSet(
req.params.id,
req.scope,
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"],
matcher: "/admin/fulfillment-sets/:id/service-zones/:zone_id",
middlewares: [],
middlewares: [
validateAndTransformQuery(
AdminFulfillmentSetParams,
QueryConfig.retrieveTransformQueryConfig
),
],
},
{
method: ["DELETE"],
@@ -5,15 +5,19 @@ import {
} from "@medusajs/utils"
import { MedusaRequest, MedusaResponse } from "../../../../../../types/routing"
import { AdminPostInventoryItemsItemLocationLevelsLevelReq } from "../../../validators"
import { deleteInventoryLevelsWorkflow } from "@medusajs/core-flows"
import { updateInventoryLevelsWorkflow } from "@medusajs/core-flows"
import {
deleteInventoryLevelsWorkflow,
updateInventoryLevelsWorkflow,
} from "@medusajs/core-flows"
import { refetchInventoryItem } from "../../../helpers"
import { AdminUpdateInventoryLocationLevelType } from "../../../validators"
export const DELETE = async (req: MedusaRequest, res: MedusaResponse) => {
const { id, location_id } = req.params
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 }] =
await remoteQuery(
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({
id: levelId,
object: "inventory-level",
deleted: true,
parent: inventoryItem,
})
}
export const POST = async (
req: MedusaRequest<AdminPostInventoryItemsItemLocationLevelsLevelReq>,
req: MedusaRequest<AdminUpdateInventoryLocationLevelType>,
res: MedusaResponse
) => {
const { id: inventory_item_id, location_id } = req.params
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
const { id, location_id } = req.params
const { errors } = await updateInventoryLevelsWorkflow(req.scope).run({
input: {
updates: [{ inventory_item_id, location_id, ...req.validatedBody }],
updates: [{ ...req.validatedBody, inventory_item_id: id, location_id }],
},
throwOnError: false,
})
@@ -66,17 +75,13 @@ export const POST = async (
throw errors[0].error
}
const [inventory_item] = await remoteQuery(
remoteQueryObjectFromString({
entryPoint: "inventory",
variables: {
id: inventory_item_id,
},
fields: req.remoteQueryConfig.fields,
})
const inventoryItem = await refetchInventoryItem(
id,
req.scope,
req.remoteQueryConfig.fields
)
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"
import { MedusaRequest, MedusaResponse } from "../../../../../types/routing"
import { AdminPostInventoryItemsItemLocationLevelsReq } from "../../validators"
import { MedusaError } from "@medusajs/utils"
import { createInventoryLevelsWorkflow } from "@medusajs/core-flows"
import { defaultAdminInventoryItemFields } from "../../query-config"
import {
AdminCreateInventoryLocationLevelType,
AdminGetInventoryLocationLevelsParamsType,
} from "../../validators"
import { refetchInventoryItem } from "../../helpers"
export const POST = async (
req: MedusaRequest<AdminPostInventoryItemsItemLocationLevelsReq>,
req: MedusaRequest<AdminCreateInventoryLocationLevelType>,
res: MedusaResponse
) => {
const { id } = req.params
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
const workflow = createInventoryLevelsWorkflow(req.scope)
const { errors } = await workflow.run({
input: {
inventory_levels: [
{
inventory_item_id: id,
...req.validatedBody,
inventory_item_id: id,
},
],
},
@@ -34,20 +34,18 @@ export const POST = async (
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 })
const inventoryItem = await refetchInventoryItem(
id,
req.scope,
req.remoteQueryConfig.fields
)
res.status(200).json({ inventory_item: inventoryItem })
}
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 query = remoteQueryObjectFromString({
@@ -1,36 +1,26 @@
import {
ContainerRegistrationKeys,
MedusaError,
remoteQueryObjectFromString,
} from "@medusajs/utils"
import { MedusaError } from "@medusajs/utils"
import { MedusaRequest, MedusaResponse } from "../../../../types/routing"
import {
deleteInventoryItemWorkflow,
updateInventoryItemsWorkflow,
} from "@medusajs/core-flows"
import {
AdminGetInventoryItemParamsType,
AdminUpdateInventoryItemType,
} from "../validators"
import { refetchInventoryItem } from "../helpers"
import { AdminPostInventoryItemsInventoryItemReq } from "../validators"
export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
export const GET = async (
req: MedusaRequest<AdminGetInventoryItemParamsType>,
res: MedusaResponse
) => {
const { id } = req.params
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
const query = remoteQueryObjectFromString({
entryPoint: "inventory",
variables: {
filters: { id },
skip: 0,
take: 1,
},
fields: req.retrieveConfig.select as string[],
})
const { rows } = await remoteQuery(query)
const [inventory_item] = rows
if (!inventory_item) {
const inventoryItem = await refetchInventoryItem(
id,
req.scope,
req.remoteQueryConfig.fields
)
if (!inventoryItem) {
throw new MedusaError(
MedusaError.Types.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({
inventory_item,
inventory_item: inventoryItem,
})
}
// Update inventory item
export const POST = async (
req: MedusaRequest<AdminPostInventoryItemsInventoryItemReq>,
req: MedusaRequest<AdminUpdateInventoryItemType>,
res: MedusaResponse
) => {
const { id } = req.params
@@ -55,20 +45,14 @@ export const POST = async (
},
})
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
const [inventory_item] = await remoteQuery(
remoteQueryObjectFromString({
entryPoint: "inventory",
variables: {
id,
},
fields: req.retrieveConfig.select as string[],
})
const inventoryItem = await refetchInventoryItem(
id,
req.scope,
req.remoteQueryConfig.fields
)
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 {
AdminGetInventoryItemsItemLocationLevelsParams,
AdminGetInventoryItemsItemParams,
AdminGetInventoryItemsParams,
AdminPostInventoryItemsInventoryItemParams,
AdminPostInventoryItemsInventoryItemReq,
AdminPostInventoryItemsItemLocationLevelsBatchReq,
AdminPostInventoryItemsItemLocationLevelsLevelParams,
AdminPostInventoryItemsItemLocationLevelsLevelReq,
AdminPostInventoryItemsItemLocationLevelsReq,
AdminPostInventoryItemsReq,
} from "./validators"
import { transformBody, transformQuery } from "../../../api/middlewares"
import { MiddlewareRoute } from "../../../types/middlewares"
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[] = [
{
@@ -27,7 +25,7 @@ export const adminInventoryRoutesMiddlewares: MiddlewareRoute[] = [
method: ["GET"],
matcher: "/admin/inventory-items",
middlewares: [
transformQuery(
validateAndTransformQuery(
AdminGetInventoryItemsParams,
QueryConfig.listTransformQueryConfig
),
@@ -37,8 +35,8 @@ export const adminInventoryRoutesMiddlewares: MiddlewareRoute[] = [
method: ["GET"],
matcher: "/admin/inventory-items/:id",
middlewares: [
transformQuery(
AdminGetInventoryItemsItemParams,
validateAndTransformQuery(
AdminGetInventoryItemParams,
QueryConfig.retrieveTransformQueryConfig
),
],
@@ -47,47 +45,9 @@ export const adminInventoryRoutesMiddlewares: MiddlewareRoute[] = [
method: ["POST"],
matcher: "/admin/inventory-items",
middlewares: [
transformBody(AdminPostInventoryItemsReq),
transformQuery(
AdminGetInventoryItemsItemParams,
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,
validateAndTransformBody(AdminCreateInventoryItem),
validateAndTransformQuery(
AdminGetInventoryItemParams,
QueryConfig.retrieveTransformQueryConfig
),
],
@@ -96,11 +56,69 @@ export const adminInventoryRoutesMiddlewares: MiddlewareRoute[] = [
method: ["POST"],
matcher: "/admin/inventory-items/:id",
middlewares: [
transformBody(AdminPostInventoryItemsInventoryItemReq),
transformQuery(
AdminPostInventoryItemsInventoryItemParams,
validateAndTransformBody(AdminUpdateInventoryItem),
validateAndTransformQuery(
AdminGetInventoryItemParams,
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
export const defaultAdminLocationLevelFields = [
"id",
@@ -35,12 +32,7 @@ export const defaultAdminInventoryItemFields = [
"stocked_quantity",
"created_at",
"updated_at",
...defaultAdminLocationLevelFields.map(
(field) => `location_levels.${field.toString()}`
),
...defaultAdminProductsVariantFields
.filter((field) => !field.startsWith("*"))
.map((field) => `variant.${field}`),
"*location_levels",
]
export const retrieveTransformQueryConfig = {
@@ -7,36 +7,32 @@ import {
remoteQueryObjectFromString,
} from "@medusajs/utils"
import { AdminPostInventoryItemsReq } from "./validators"
import { createInventoryItemsWorkflow } from "@medusajs/core-flows"
import {
AdminCreateInventoryItemType,
AdminGetInventoryItemsParamsType,
} from "./validators"
import { refetchInventoryItem } from "./helpers"
// Create inventory-item
export const POST = async (
req: AuthenticatedMedusaRequest<AdminPostInventoryItemsReq>,
req: AuthenticatedMedusaRequest<AdminCreateInventoryItemType>,
res: MedusaResponse
) => {
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
const { result } = await createInventoryItemsWorkflow(req.scope).run({
input: { items: [req.validatedBody] },
})
const [inventory_item] = await remoteQuery(
remoteQueryObjectFromString({
entryPoint: "inventory_items",
variables: {
id: result[0].id,
},
fields: req.remoteQueryConfig.fields,
})
const inventoryItem = await refetchInventoryItem(
result[0].id,
req.scope,
req.remoteQueryConfig.fields
)
res.status(200).json({ inventory_item })
res.status(200).json({ inventory_item: inventoryItem })
}
// List inventory-items
export const GET = async (
req: AuthenticatedMedusaRequest,
req: AuthenticatedMedusaRequest<AdminGetInventoryItemsParamsType>,
res: MedusaResponse
) => {
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
@@ -1,408 +1,129 @@
import { z } from "zod"
import {
DateComparisonOperator,
FindParams,
NumericalComparisonOperator,
StringComparisonOperator,
extendedFindParamsMixin,
} from "../../../types/common"
import {
IsBoolean,
IsEmail,
IsNotEmpty,
IsNumber,
IsObject,
IsOptional,
IsString,
Min,
ValidateNested,
} from "class-validator"
import { Transform, Type } from "class-transformer"
createFindParams,
createOperatorMap,
createSelectParams,
} from "../../utils/validators"
import { optionalBooleanMapper } from "../../../utils/validators/is-boolean"
import { IsType } from "../../../utils"
export type AdminGetInventoryItemParamsType = z.infer<
typeof AdminGetInventoryItemParams
>
export const AdminGetInventoryItemParams = createSelectParams()
export class AdminGetInventoryItemsItemParams extends FindParams {}
/**
* Parameters used to filter and configure the pagination of the retrieved inventory items.
*/
export class AdminGetInventoryItemsParams extends extendedFindParamsMixin({
export type AdminGetInventoryItemsParamsType = z.infer<
typeof AdminGetInventoryItemsParams
>
export const AdminGetInventoryItemsParams = createFindParams({
limit: 20,
offset: 0,
}) {
/**
* IDs to filter inventory items by.
*/
@IsOptional()
@IsType([String, [String]])
id?: string | string[]
}).merge(
z.object({
q: z.string().optional(),
id: z.union([z.string(), z.array(z.string())]).optional(),
location_id: z.union([z.string(), z.array(z.string())]).optional(),
sku: z.union([z.string(), z.array(z.string())]).optional(),
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(),
})
)
/**
* Search terms to search inventory items' sku, title, and description.
*/
@IsOptional()
@IsString()
q?: string
export type AdminGetInventoryLocationLevelParamsType = z.infer<
typeof AdminGetInventoryLocationLevelParams
>
export const AdminGetInventoryLocationLevelParams = createSelectParams()
/**
* Location IDs to filter inventory items by.
*/
@IsOptional()
@IsType([String, [String]])
location_id?: string | string[]
export type AdminGetInventoryLocationLevelsParamsType = z.infer<
typeof AdminGetInventoryLocationLevelsParams
>
export const AdminGetInventoryLocationLevelsParams = createFindParams({
limit: 50,
offset: 0,
}).merge(
z.object({
location_id: z.union([z.string(), z.array(z.string())]).optional(),
$and: z
.lazy(() => AdminGetInventoryLocationLevelsParams.array())
.optional(),
$or: z.lazy(() => AdminGetInventoryLocationLevelsParams.array()).optional(),
})
)
/**
* SKUs to filter inventory items by.
*/
@IsOptional()
@IsType([String, [String]])
sku?: string | string[]
export type AdminCreateInventoryLocationLevelType = z.infer<
typeof AdminCreateInventoryLocationLevel
>
export const AdminCreateInventoryLocationLevel = z
.object({
location_id: z.string(),
stocked_quantity: z.number().min(0).optional(),
incoming_quantity: z.number().min(0).optional(),
})
.strict()
/**
* Origin countries to filter inventory items by.
*/
@IsOptional()
@IsType([String, [String]])
origin_country?: string | string[]
export type AdminUpdateInventoryLocationLevelType = z.infer<
typeof AdminUpdateInventoryLocationLevel
>
export const AdminUpdateInventoryLocationLevel = z
.object({
stocked_quantity: z.number().min(0).optional(),
incoming_quantity: z.number().min(0).optional(),
})
.strict()
/**
* MID codes to filter inventory items by.
*/
@IsOptional()
@IsType([String, [String]])
mid_code?: string | string[]
export type AdminCreateInventoryItemType = z.infer<
typeof AdminCreateInventoryItem
>
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()
/**
* 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,
offset: 0,
}
) {
/**
* Location IDs to filter location levels.
*/
@IsOptional()
@IsString({ each: true })
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()
@IsNumber()
@Min(0)
stocked_quantity?: number
}
// eslint-disable-next-line
export class AdminPostInventoryItemsItemLocationLevelsLevelParams extends FindParams {}
/**
* @schema AdminPostInventoryItemsInventoryItemReq
* type: object
* description: "The attributes to update in an inventory item."
* properties:
* 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
* 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 {
@IsString()
@IsOptional()
sku?: string
@IsOptional()
@IsString()
origin_country?: string
@IsOptional()
@IsString()
hs_code?: string
@IsOptional()
@IsString()
mid_code?: string
@IsOptional()
@IsString()
material?: string
@IsOptional()
@IsNumber()
weight?: number
@IsOptional()
@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 {}
export type AdminUpdateInventoryItemType = z.infer<
typeof AdminUpdateInventoryItem
>
export const AdminUpdateInventoryItem = 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()
@@ -1,6 +1,7 @@
import { MedusaRequest, MedusaResponse } from "../../../../../types/routing"
import { refreshInviteTokensWorkflow } from "@medusajs/core-flows"
import { refetchInvite } from "../../helpers"
export const POST = async (req: MedusaRequest, res: MedusaResponse) => {
const workflow = refreshInviteTokensWorkflow(req.scope)
@@ -9,7 +10,12 @@ export const POST = async (req: MedusaRequest, res: MedusaResponse) => {
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,
MedusaResponse,
} from "../../../../types/routing"
import {
ContainerRegistrationKeys,
MedusaError,
remoteQueryObjectFromString,
} from "@medusajs/utils"
import { MedusaError } from "@medusajs/utils"
import { deleteInvitesWorkflow } from "@medusajs/core-flows"
import { refetchInvite } from "../helpers"
// Get invite
export const GET = async (
req: AuthenticatedMedusaRequest,
res: MedusaResponse
) => {
const { id } = req.params
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
const query = remoteQueryObjectFromString({
entryPoint: "invite",
variables: {
id,
},
fields: req.retrieveConfig.select as string[],
})
const [invite] = await remoteQuery(query)
const invite = await refetchInvite(
id,
req.scope,
req.remoteQueryConfig.fields
)
if (!invite) {
throw new MedusaError(
@@ -38,7 +28,6 @@ export const GET = async (
res.status(200).json({ invite })
}
// delete invite
export const DELETE = async (
req: AuthenticatedMedusaRequest,
res: MedusaResponse
@@ -5,11 +5,10 @@ import {
AuthenticatedMedusaRequest,
MedusaResponse,
} from "../../../../types/routing"
import { AdminPostInvitesInviteAcceptReq } from "../validators"
import { AdminInviteAcceptType } from "../validators"
export const POST = async (
req: AuthenticatedMedusaRequest<AdminPostInvitesInviteAcceptReq>,
req: AuthenticatedMedusaRequest<AdminInviteAcceptType>,
res: MedusaResponse
) => {
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 {
AdminCreateInviteRequest,
AdminGetInvitesInviteParams,
AdminCreateInvite,
AdminGetInviteAcceptParams,
AdminGetInviteParams,
AdminGetInvitesParams,
AdminPostInvitesInviteAcceptParams,
AdminPostInvitesInviteAcceptReq,
AdminInviteAccept,
} from "./validators"
import { transformBody, transformQuery } from "../../../api/middlewares"
import { MiddlewareRoute } from "../../../types/middlewares"
import { authenticate } from "../../../utils/authenticate-middleware"
import { validateAndTransformQuery } from "../../utils/validate-query"
import { validateAndTransformBody } from "../../utils/validate-body"
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"],
matcher: "/admin/invites",
middlewares: [
transformQuery(
authenticate("admin", ["session", "bearer", "api-key"]),
validateAndTransformQuery(
AdminGetInvitesParams,
QueryConfig.listTransformQueryConfig
),
@@ -45,22 +28,52 @@ export const adminInviteRoutesMiddlewares: MiddlewareRoute[] = [
{
method: ["POST"],
matcher: "/admin/invites",
middlewares: [transformBody(AdminCreateInviteRequest)],
middlewares: [
authenticate("admin", ["session", "bearer", "api-key"]),
validateAndTransformBody(AdminCreateInvite),
validateAndTransformQuery(
AdminGetInviteParams,
QueryConfig.retrieveTransformQueryConfig
),
],
},
{
method: "POST",
matcher: "/admin/invites/accept",
middlewares: [
transformBody(AdminPostInvitesInviteAcceptReq),
transformQuery(AdminPostInvitesInviteAcceptParams),
authenticate("admin", ["session", "bearer"], {
allowUnregistered: true,
}),
validateAndTransformBody(AdminInviteAccept),
validateAndTransformQuery(
AdminGetInviteAcceptParams,
QueryConfig.retrieveTransformQueryConfig
),
],
},
{
method: ["GET"],
matcher: "/admin/invites/:id",
middlewares: [
transformQuery(
AdminGetInvitesInviteParams,
authenticate("admin", ["session", "bearer", "api-key"]),
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
),
],
@@ -1,5 +1,3 @@
export const defaultAdminInviteRelations = []
export const allowedAdminInviteRelations = []
export const defaultAdminInviteFields = [
"id",
"email",
@@ -13,9 +11,7 @@ export const defaultAdminInviteFields = [
]
export const retrieveTransformQueryConfig = {
defaultFields: defaultAdminInviteFields,
defaultRelations: defaultAdminInviteRelations,
allowedRelations: allowedAdminInviteRelations,
defaults: defaultAdminInviteFields,
isList: false,
}
@@ -7,32 +7,27 @@ import {
remoteQueryObjectFromString,
} from "@medusajs/utils"
import { CreateInviteDTO } from "@medusajs/types"
import { createInvitesWorkflow } from "@medusajs/core-flows"
import { AdminCreateInviteType, AdminGetInvitesParamsType } from "./validators"
import { refetchInvite } from "./helpers"
// List invites
export const GET = async (
req: AuthenticatedMedusaRequest,
req: AuthenticatedMedusaRequest<AdminGetInvitesParamsType>,
res: MedusaResponse
) => {
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
const query = remoteQueryObjectFromString({
const queryObject = remoteQueryObjectFromString({
entryPoint: "invite",
variables: {
filters: req.filterableFields,
order: req.listConfig.order,
skip: req.listConfig.skip,
take: req.listConfig.take,
...req.remoteQueryConfig.pagination,
},
fields: req.listConfig.select as string[],
fields: req.remoteQueryConfig.fields,
})
const { rows: invites, metadata } = await remoteQuery({
...query,
})
const { rows: invites, metadata } = await remoteQuery(queryObject)
res.status(200).json({
res.json({
invites,
count: metadata.count,
offset: metadata.skip,
@@ -40,9 +35,8 @@ export const GET = async (
})
}
// Create invite
export const POST = async (
req: AuthenticatedMedusaRequest<CreateInviteDTO>,
req: AuthenticatedMedusaRequest<AdminCreateInviteType>,
res: MedusaResponse
) => {
const workflow = createInvitesWorkflow(req.scope)
@@ -54,7 +48,11 @@ export const POST = async (
}
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 })
}
@@ -1,146 +1,51 @@
import { Type } from "class-transformer"
import { z } from "zod"
import {
IsEmail,
IsNotEmpty,
IsOptional,
IsString,
ValidateNested,
} from "class-validator"
import {
DateComparisonOperator,
FindParams,
extendedFindParamsMixin,
} from "../../../types/common"
import { IsType } from "../../../utils"
createFindParams,
createOperatorMap,
createSelectParams,
} from "../../utils/validators"
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,
offset: 0,
}) {
/**
* IDs to filter invites by.
*/
@IsOptional()
@IsType([String, [String]])
id?: string | string[]
}).merge(
z.object({
q: z.string().optional(),
id: z.union([z.string(), z.array(z.string())]).optional(),
email: z.union([z.string(), z.array(z.string())]).optional(),
created_at: createOperatorMap().optional(),
updated_at: createOperatorMap().optional(),
deleted_at: createOperatorMap().optional(),
$and: z.lazy(() => AdminGetInvitesParams.array()).optional(),
$or: z.lazy(() => AdminGetInvitesParams.array()).optional(),
})
)
/**
* 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 `-`.
*/
@IsString()
@IsOptional()
order?: string
export type AdminGetInviteAcceptParamsType = z.infer<
typeof AdminGetInviteAcceptParams
>
export const AdminGetInviteAcceptParams = createSelectParams().merge(
z.object({
token: z.string(),
})
)
/**
* Date filters to apply on the invites' `update_at` date.
*/
@IsOptional()
@ValidateNested()
@Type(() => DateComparisonOperator)
updated_at?: DateComparisonOperator
export type AdminCreateInviteType = z.infer<typeof AdminCreateInvite>
export const AdminCreateInvite = z
.object({
email: z.string(),
})
.strict()
/**
* Date filters to apply on the customer invites' `created_at` date.
*/
@IsOptional()
@ValidateNested()
@Type(() => DateComparisonOperator)
created_at?: DateComparisonOperator
/**
* 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
}
export type AdminInviteAcceptType = z.infer<typeof AdminInviteAccept>
export const AdminInviteAccept = z
.object({
email: z.string().optional(),
first_name: z.string().optional(),
last_name: z.string().optional(),
})
.strict()
@@ -1,21 +1,15 @@
import { capturePaymentWorkflow } from "@medusajs/core-flows"
import {
ContainerRegistrationKeys,
remoteQueryObjectFromString,
} from "@medusajs/utils"
import {
AuthenticatedMedusaRequest,
MedusaResponse,
} from "../../../../../types/routing"
import { defaultAdminPaymentFields } from "../../query-config"
import { AdminPostPaymentsCapturesReq } from "../../validators"
import { refetchPayment } from "../../helpers"
import { AdminCreatePaymentCaptureType } from "../../validators"
export const POST = async (
req: AuthenticatedMedusaRequest<AdminPostPaymentsCapturesReq>,
req: AuthenticatedMedusaRequest<AdminCreatePaymentCaptureType>,
res: MedusaResponse
) => {
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
const { id } = req.params
const { errors } = await capturePaymentWorkflow(req.scope).run({
@@ -31,13 +25,11 @@ export const POST = async (
throw errors[0].error
}
const query = remoteQueryObjectFromString({
entryPoint: "payments",
variables: { id },
fields: defaultAdminPaymentFields,
})
const [payment] = await remoteQuery(query)
const payment = await refetchPayment(
id,
req.scope,
req.remoteQueryConfig.fields
)
res.status(200).json({ payment })
}
@@ -1,23 +1,16 @@
import { refundPaymentWorkflow } from "@medusajs/core-flows"
import {
ContainerRegistrationKeys,
remoteQueryObjectFromString,
} from "@medusajs/utils"
import {
AuthenticatedMedusaRequest,
MedusaResponse,
} from "../../../../../types/routing"
import { defaultAdminPaymentFields } from "../../query-config"
import { AdminPostPaymentsRefundsReq } from "../../validators"
import { refetchPayment } from "../../helpers"
import { AdminCreatePaymentRefundType } from "../../validators"
export const POST = async (
req: AuthenticatedMedusaRequest<AdminPostPaymentsRefundsReq>,
req: AuthenticatedMedusaRequest<AdminCreatePaymentRefundType>,
res: MedusaResponse
) => {
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
const { id } = req.params
const { errors } = await refundPaymentWorkflow(req.scope).run({
input: {
payment_id: id,
@@ -31,13 +24,11 @@ export const POST = async (
throw errors[0].error
}
const query = remoteQueryObjectFromString({
entryPoint: "payments",
variables: { id },
fields: defaultAdminPaymentFields,
})
const [payment] = await remoteQuery(query)
const payment = await refetchPayment(
id,
req.scope,
req.remoteQueryConfig.fields
)
res.status(200).json({ payment })
}
@@ -1,28 +1,19 @@
import { Modules } from "@medusajs/modules-sdk"
import {
ContainerRegistrationKeys,
remoteQueryObjectFromString,
} from "@medusajs/utils"
import {
AuthenticatedMedusaRequest,
MedusaResponse,
} from "../../../../types/routing"
import { AdminGetPaymentParamsType } from "../validators"
import { refetchPayment } from "../helpers"
export const GET = async (
req: AuthenticatedMedusaRequest,
req: AuthenticatedMedusaRequest<AdminGetPaymentParamsType>,
res: MedusaResponse
) => {
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
const { id } = req.params
const query = remoteQueryObjectFromString({
entryPoint: Modules.PAYMENT,
variables: { id },
fields: req.retrieveConfig.select as string[],
})
const [payment] = await remoteQuery(query)
const payment = await refetchPayment(
req.params.id,
req.scope,
req.remoteQueryConfig.fields
)
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 { 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 {
AdminCreatePaymentCapture,
AdminCreatePaymentRefund,
AdminGetPaymentParams,
AdminGetPaymentProvidersParams,
AdminGetPaymentsParams,
AdminGetPaymentsPaymentProvidersParams,
AdminPostPaymentsCapturesReq,
AdminPostPaymentsRefundsReq,
} from "./validators"
export const adminPaymentRoutesMiddlewares: MiddlewareRoute[] = [
{
method: "ALL",
matcher: "/admin/payments",
middlewares: [authenticate("admin", ["session", "bearer"])],
middlewares: [authenticate("admin", ["session", "bearer", "api-key"])],
},
{
method: ["GET"],
matcher: "/admin/payments",
middlewares: [
transformQuery(
validateAndTransformQuery(
AdminGetPaymentsParams,
queryConfig.listTransformQueryConfig
),
@@ -29,8 +32,8 @@ export const adminPaymentRoutesMiddlewares: MiddlewareRoute[] = [
method: ["GET"],
matcher: "/admin/payments/payment-providers",
middlewares: [
transformQuery(
AdminGetPaymentsPaymentProvidersParams,
validateAndTransformQuery(
AdminGetPaymentProvidersParams,
queryConfig.listTransformPaymentProvidersQueryConfig
),
],
@@ -39,20 +42,35 @@ export const adminPaymentRoutesMiddlewares: MiddlewareRoute[] = [
method: ["GET"],
matcher: "/admin/payments/:id",
middlewares: [
transformQuery(
AdminGetPaymentsParams,
queryConfig.retrieveTransformQueryConfig
unlessPath(
new RegExp("/admin/payments/payment-providers"),
validateAndTransformQuery(
AdminGetPaymentParams,
queryConfig.retrieveTransformQueryConfig
)
),
],
},
{
method: ["POST"],
matcher: "/admin/payments/:id/capture",
middlewares: [transformBody(AdminPostPaymentsCapturesReq)],
middlewares: [
validateAndTransformBody(AdminCreatePaymentCapture),
validateAndTransformQuery(
AdminGetPaymentParams,
queryConfig.retrieveTransformQueryConfig
),
],
},
{
method: ["POST"],
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 {
AuthenticatedMedusaRequest,
MedusaResponse,
} from "../../../../types/routing"
import { AdminGetPaymentsPaymentProvidersParams } from "../validators"
import { AdminGetPaymentProvidersParamsType } from "../validators"
import {
ContainerRegistrationKeys,
remoteQueryObjectFromString,
} from "@medusajs/utils"
export const GET = async (
req: AuthenticatedMedusaRequest<AdminGetPaymentsPaymentProvidersParams>,
req: AuthenticatedMedusaRequest<AdminGetPaymentProvidersParamsType>,
res: MedusaResponse
) => {
const paymentModule = req.scope.resolve<IPaymentModuleService>(
ModuleRegistrationName.PAYMENT
)
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
const queryObject = remoteQueryObjectFromString({
entryPoint: "payment_provider",
variables: {
filters: req.filterableFields,
...req.remoteQueryConfig.pagination,
},
fields: req.remoteQueryConfig.fields,
})
const [payment_providers, count] =
await paymentModule.listAndCountPaymentProviders(req.filterableFields, {
skip: req.listConfig.skip,
take: req.listConfig.take,
})
const { rows: payment_providers, metadata } = await remoteQuery(queryObject)
res.status(200).json({
count,
res.json({
payment_providers,
offset: req.listConfig.skip,
limit: req.listConfig.take,
count: metadata.count,
offset: metadata.skip,
limit: metadata.take,
})
}
@@ -11,29 +11,19 @@ export const defaultAdminPaymentFields = [
"refunds.amount",
]
export const defaultAdminPaymentRelations = ["captures", "refunds"]
export const allowedAdminPaymentRelations = ["captures", "refunds"]
export const listTransformQueryConfig = {
defaultFields: defaultAdminPaymentFields,
defaultRelations: defaultAdminPaymentRelations,
allowedRelations: allowedAdminPaymentRelations,
defaults: defaultAdminPaymentFields,
isList: true,
}
export const retrieveTransformQueryConfig = {
defaultFields: defaultAdminPaymentFields,
defaultRelations: defaultAdminPaymentRelations,
allowedRelations: allowedAdminPaymentRelations,
defaults: defaultAdminPaymentFields,
isList: false,
}
export const defaultAdminPaymentPaymentProviderFields = ["id", "is_enabled"]
export const listTransformPaymentProvidersQueryConfig = {
defaultFields: defaultAdminPaymentPaymentProviderFields,
defaultRelations: [],
allowedRelations: [],
defaults: defaultAdminPaymentPaymentProviderFields,
isList: true,
}
@@ -1,4 +1,3 @@
import { Modules } from "@medusajs/modules-sdk"
import {
ContainerRegistrationKeys,
remoteQueryObjectFromString,
@@ -7,27 +6,25 @@ import {
AuthenticatedMedusaRequest,
MedusaResponse,
} from "../../../types/routing"
import { AdminGetPaymentsParamsType } from "./validators"
export const GET = async (
req: AuthenticatedMedusaRequest,
req: AuthenticatedMedusaRequest<AdminGetPaymentsParamsType>,
res: MedusaResponse
) => {
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
const query = remoteQueryObjectFromString({
entryPoint: Modules.PAYMENT,
const queryObject = remoteQueryObjectFromString({
entryPoint: "payment",
variables: {
filters: req.filterableFields,
order: req.listConfig.order,
skip: req.listConfig.skip,
take: req.listConfig.take,
...req.remoteQueryConfig.pagination,
},
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,
count: metadata.count,
offset: metadata.skip,
@@ -1,79 +1,57 @@
import { Type } from "class-transformer"
import { IsBoolean, IsInt, IsOptional, ValidateNested } from "class-validator"
import { z } from "zod"
import {
DateComparisonOperator,
FindParams,
extendedFindParamsMixin,
} from "../../../types/common"
import { IsType } from "../../../utils"
createFindParams,
createOperatorMap,
createSelectParams,
} from "../../utils/validators"
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,
offset: 0,
}) {
/**
* IDs to filter users by.
*/
@IsOptional()
@IsType([String, [String]])
id?: string | string[]
}).merge(
z.object({
id: z.union([z.string(), z.array(z.string())]).optional(),
created_at: createOperatorMap().optional(),
updated_at: createOperatorMap().optional(),
deleted_at: createOperatorMap().optional(),
$and: z.lazy(() => AdminGetPaymentsParams.array()).optional(),
$or: z.lazy(() => AdminGetPaymentsParams.array()).optional(),
})
)
/**
* Date filters to apply on the users' `update_at` date.
*/
@IsOptional()
@ValidateNested()
@Type(() => DateComparisonOperator)
updated_at?: DateComparisonOperator
export type AdminGetPaymentProvidersParamsType = z.infer<
typeof AdminGetPaymentProvidersParams
>
export const AdminGetPaymentProvidersParams = createFindParams({
limit: 20,
offset: 0,
}).merge(
z.object({
id: z.union([z.string(), z.array(z.string())]).optional(),
is_enabled: z.boolean().optional(),
$and: z.lazy(() => AdminGetPaymentProvidersParams.array()).optional(),
$or: z.lazy(() => AdminGetPaymentProvidersParams.array()).optional(),
})
)
/**
* Date filters to apply on the customer users' `created_at` date.
*/
@IsOptional()
@ValidateNested()
@Type(() => DateComparisonOperator)
created_at?: DateComparisonOperator
export type AdminCreatePaymentCaptureType = z.infer<
typeof AdminCreatePaymentCapture
>
export const AdminCreatePaymentCapture = z
.object({
amount: z.number().optional(),
})
.strict()
/**
* 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,
offset: 0,
}
) {
/**
* IDs to filter users by.
*/
@IsOptional()
@IsType([String, [String]])
id?: string | string[]
/**
* Filter providers by `enabled` flag
*/
@IsBoolean()
@IsOptional()
is_enabled?: boolean
}
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 { remoteQueryObjectFromString } from "@medusajs/utils"
import {
ContainerRegistrationKeys,
remoteQueryObjectFromString,
} from "@medusajs/utils"
export const refetchProductType = async (
productTypeId: string,
scope: MedusaContainer,
fields: string[]
) => {
const remoteQuery = scope.resolve("remoteQuery")
const remoteQuery = scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
const queryObject = remoteQueryObjectFromString({
entryPoint: "product_type",
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)
}
}