feat(medusa): Support transformQuery/Body middleware, introduction of pipe feature (#1593)
* feat(medusa): Support transformQuery middleware * feat(medusa): Add support for transformBody * test(medusa): Fix unit tests * feat(medusa): Cleanup import * feat(medusa): Update pipe required fields * feat(medusa): Cleanup * feat(medusa): Types cleanup * feat(medusa): Types cleanup * feat(medusa): Improve global typings and add one more example * fix(medusa): Wrong wording in the error for prepareListQuery utility
This commit is contained in:
@@ -5,6 +5,8 @@ import { default as wrap } from "./await-middleware"
|
|||||||
|
|
||||||
export { getRequestedBatchJob } from "./batch-job/get-requested-batch-job"
|
export { getRequestedBatchJob } from "./batch-job/get-requested-batch-job"
|
||||||
export { canAccessBatchJob } from "./batch-job/can-access-batch-job"
|
export { canAccessBatchJob } from "./batch-job/can-access-batch-job"
|
||||||
|
export { transformQuery } from "./transform-query"
|
||||||
|
export { transformBody } from "./transform-body"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
authenticate,
|
authenticate,
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { NextFunction, Request, Response } from "express"
|
||||||
|
import { ClassConstructor } from "../../types/global"
|
||||||
|
import { ValidatorOptions } from "class-validator"
|
||||||
|
import { validator } from "../../utils/validator"
|
||||||
|
|
||||||
|
export function transformBody<T>(
|
||||||
|
plainToClass: ClassConstructor<T>,
|
||||||
|
config: ValidatorOptions = {}
|
||||||
|
): (req: Request, res: Response, next: NextFunction) => Promise<void> {
|
||||||
|
return async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
try {
|
||||||
|
req.validatedBody = await validator(plainToClass, req.body, config)
|
||||||
|
next()
|
||||||
|
} catch (e) {
|
||||||
|
next(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
import { NextFunction, Request, Response } from "express"
|
||||||
|
import { ClassConstructor } from "../../types/global"
|
||||||
|
import { validator } from "../../utils/validator"
|
||||||
|
import { ValidatorOptions } from "class-validator"
|
||||||
|
import { default as normalizeQuery } from "./normalized-query"
|
||||||
|
import {
|
||||||
|
prepareListQuery,
|
||||||
|
prepareRetrieveQuery,
|
||||||
|
} from "../../utils/get-query-config"
|
||||||
|
import { BaseEntity } from "../../interfaces/models/base-entity"
|
||||||
|
import { FindConfig, QueryConfig, RequestQueryFields } from "../../types/common"
|
||||||
|
import { omit } from "lodash"
|
||||||
|
|
||||||
|
export function transformQuery<
|
||||||
|
T extends RequestQueryFields,
|
||||||
|
TEntity extends BaseEntity
|
||||||
|
>(
|
||||||
|
plainToClass: ClassConstructor<T>,
|
||||||
|
queryConfig?: QueryConfig<TEntity>,
|
||||||
|
config: ValidatorOptions = {}
|
||||||
|
): (req: Request, res: Response, next: NextFunction) => Promise<void> {
|
||||||
|
return async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
try {
|
||||||
|
normalizeQuery()(req, res, () => void 0)
|
||||||
|
const validated: T = await validator<T, Record<string, unknown>>(
|
||||||
|
plainToClass,
|
||||||
|
req.query,
|
||||||
|
config
|
||||||
|
)
|
||||||
|
req.validatedQuery = validated
|
||||||
|
|
||||||
|
req.filterableFields = omit(validated, [
|
||||||
|
"limit",
|
||||||
|
"offset",
|
||||||
|
"expand",
|
||||||
|
"fields",
|
||||||
|
"order",
|
||||||
|
])
|
||||||
|
|
||||||
|
if (queryConfig?.isList) {
|
||||||
|
req.listConfig = prepareListQuery(
|
||||||
|
validated,
|
||||||
|
queryConfig
|
||||||
|
) as FindConfig<unknown>
|
||||||
|
} else {
|
||||||
|
req.retrieveConfig = prepareRetrieveQuery(
|
||||||
|
validated,
|
||||||
|
queryConfig
|
||||||
|
) as FindConfig<unknown>
|
||||||
|
}
|
||||||
|
|
||||||
|
next()
|
||||||
|
} catch (e) {
|
||||||
|
next(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
import { Router } from "express"
|
import { Router } from "express"
|
||||||
import { BatchJob } from "../../../.."
|
import { BatchJob } from "../../../.."
|
||||||
import { DeleteResponse, PaginatedResponse } from "../../../../types/common"
|
import { DeleteResponse, PaginatedResponse } from "../../../../types/common"
|
||||||
import middlewares from "../../../middlewares"
|
import middlewares, { transformQuery } from "../../../middlewares"
|
||||||
|
import { AdminGetBatchParams } from "./list-batch-jobs"
|
||||||
|
|
||||||
export default (app) => {
|
export default (app) => {
|
||||||
const route = Router()
|
const route = Router()
|
||||||
@@ -10,7 +11,10 @@ export default (app) => {
|
|||||||
|
|
||||||
route.get(
|
route.get(
|
||||||
"/",
|
"/",
|
||||||
middlewares.normalizeQuery(),
|
transformQuery(AdminGetBatchParams, {
|
||||||
|
defaultFields: defaultAdminBatchFields,
|
||||||
|
isList: true,
|
||||||
|
}),
|
||||||
middlewares.wrap(require("./list-batch-jobs").default)
|
middlewares.wrap(require("./list-batch-jobs").default)
|
||||||
)
|
)
|
||||||
return app
|
return app
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { MedusaError } from "medusa-core-utils"
|
|
||||||
import { Type } from "class-transformer"
|
import { Type } from "class-transformer"
|
||||||
import {
|
import {
|
||||||
IsArray,
|
IsArray,
|
||||||
@@ -8,15 +7,12 @@ import {
|
|||||||
IsString,
|
IsString,
|
||||||
ValidateNested,
|
ValidateNested,
|
||||||
} from "class-validator"
|
} from "class-validator"
|
||||||
import { pickBy, omit, identity } from "lodash"
|
import { pickBy } from "lodash"
|
||||||
import { defaultAdminBatchFields } from "."
|
|
||||||
import BatchJobService from "../../../../services/batch-job"
|
import BatchJobService from "../../../../services/batch-job"
|
||||||
import { BatchJob } from "../../../../models"
|
|
||||||
import { BatchJobStatus } from "../../../../types/batch-job"
|
import { BatchJobStatus } from "../../../../types/batch-job"
|
||||||
import { DateComparisonOperator } from "../../../../types/common"
|
import { DateComparisonOperator } from "../../../../types/common"
|
||||||
import { IsType } from "../../../../utils/validators/is-type"
|
import { IsType } from "../../../../utils/validators/is-type"
|
||||||
import { getListConfig } from "../../../../utils/get-query-config"
|
import { Request } from "express"
|
||||||
import { validator } from "../../../../utils/validator"
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @oas [get] /batch
|
* @oas [get] /batch
|
||||||
@@ -47,47 +43,25 @@ import { validator } from "../../../../utils/validator"
|
|||||||
* batch_job:
|
* batch_job:
|
||||||
* $ref: "#/components/schemas/batch_job"
|
* $ref: "#/components/schemas/batch_job"
|
||||||
*/
|
*/
|
||||||
export default async (req, res) => {
|
export default async (req: Request, res) => {
|
||||||
const { fields, expand, order, limit, offset, ...filterableFields } =
|
|
||||||
await validator(AdminGetBatchParams, req.query)
|
|
||||||
|
|
||||||
const batchService: BatchJobService = req.scope.resolve("batchJobService")
|
const batchService: BatchJobService = req.scope.resolve("batchJobService")
|
||||||
|
|
||||||
let orderBy: { [k: symbol]: "DESC" | "ASC" } | undefined
|
const created_by = req.user?.id || req.user?.userId
|
||||||
if (typeof order !== "undefined") {
|
|
||||||
if (order.startsWith("-")) {
|
|
||||||
const [, field] = order.split("-")
|
|
||||||
orderBy = { [field]: "DESC" }
|
|
||||||
} else {
|
|
||||||
orderBy = { [order]: "ASC" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const listConfig = getListConfig<BatchJob>(
|
|
||||||
defaultAdminBatchFields as (keyof BatchJob)[],
|
|
||||||
[],
|
|
||||||
fields?.split(",") as (keyof BatchJob)[],
|
|
||||||
expand?.split(","),
|
|
||||||
limit,
|
|
||||||
offset,
|
|
||||||
orderBy
|
|
||||||
)
|
|
||||||
|
|
||||||
const created_by: string = req.user.id || req.user.userId
|
|
||||||
|
|
||||||
const [jobs, count] = await batchService.listAndCount(
|
const [jobs, count] = await batchService.listAndCount(
|
||||||
pickBy(
|
pickBy(
|
||||||
{ created_by, ...filterableFields },
|
{ created_by, ...(req.filterableFields ?? {}) },
|
||||||
(val) => typeof val !== "undefined"
|
(val) => typeof val !== "undefined"
|
||||||
),
|
),
|
||||||
listConfig
|
req.listConfig
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const { limit, offset } = req.validatedQuery
|
||||||
res.status(200).json({
|
res.status(200).json({
|
||||||
batch_jobs: jobs,
|
batch_jobs: jobs,
|
||||||
count,
|
count,
|
||||||
offset: offset,
|
offset,
|
||||||
limit: limit,
|
limit,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,11 +8,11 @@ import {
|
|||||||
} from "class-validator"
|
} from "class-validator"
|
||||||
import PriceListService from "../../../../services/price-list"
|
import PriceListService from "../../../../services/price-list"
|
||||||
import {
|
import {
|
||||||
AdminPriceListPricesCreateReq,
|
AdminPriceListPricesCreateReq, CreatePriceListInput,
|
||||||
PriceListStatus,
|
PriceListStatus,
|
||||||
PriceListType,
|
PriceListType,
|
||||||
} from "../../../../types/price-list"
|
} from "../../../../types/price-list"
|
||||||
import { validator } from "../../../../utils/validator"
|
import { Request } from "express"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @oas [post] /price_lists
|
* @oas [post] /price_lists
|
||||||
@@ -85,13 +85,13 @@ import { validator } from "../../../../utils/validator"
|
|||||||
* product:
|
* product:
|
||||||
* $ref: "#/components/schemas/price_list"
|
* $ref: "#/components/schemas/price_list"
|
||||||
*/
|
*/
|
||||||
export default async (req, res) => {
|
export default async (req: Request, res) => {
|
||||||
const validated = await validator(AdminPostPriceListsPriceListReq, req.body)
|
|
||||||
|
|
||||||
const priceListService: PriceListService =
|
const priceListService: PriceListService =
|
||||||
req.scope.resolve("priceListService")
|
req.scope.resolve("priceListService")
|
||||||
|
|
||||||
const priceList = await priceListService.create(validated)
|
const priceList = await priceListService.create(
|
||||||
|
req.validatedBody as CreatePriceListInput
|
||||||
|
)
|
||||||
|
|
||||||
res.json({ price_list: priceList })
|
res.json({ price_list: priceList })
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,18 @@ import { Router } from "express"
|
|||||||
import "reflect-metadata"
|
import "reflect-metadata"
|
||||||
import { PriceList } from "../../../.."
|
import { PriceList } from "../../../.."
|
||||||
import { DeleteResponse, PaginatedResponse } from "../../../../types/common"
|
import { DeleteResponse, PaginatedResponse } from "../../../../types/common"
|
||||||
import middlewares from "../../../middlewares"
|
import middlewares, {
|
||||||
|
transformQuery,
|
||||||
|
transformBody,
|
||||||
|
} from "../../../middlewares"
|
||||||
|
import { AdminGetPriceListPaginationParams } from "./list-price-lists"
|
||||||
|
import { AdminGetPriceListsPriceListProductsParams } from "./list-price-list-products"
|
||||||
|
import {
|
||||||
|
allowedAdminProductFields,
|
||||||
|
defaultAdminProductFields,
|
||||||
|
defaultAdminProductRelations,
|
||||||
|
} from "../products"
|
||||||
|
import { AdminPostPriceListsPriceListReq } from "./create-price-list"
|
||||||
|
|
||||||
const route = Router()
|
const route = Router()
|
||||||
|
|
||||||
@@ -13,12 +24,21 @@ export default (app) => {
|
|||||||
|
|
||||||
route.get(
|
route.get(
|
||||||
"/",
|
"/",
|
||||||
middlewares.normalizeQuery(),
|
transformQuery(AdminGetPriceListPaginationParams, { isList: true }),
|
||||||
middlewares.wrap(require("./list-price-lists").default)
|
middlewares.wrap(require("./list-price-lists").default)
|
||||||
)
|
)
|
||||||
|
|
||||||
route.get(
|
route.get(
|
||||||
"/:id/products",
|
"/:id/products",
|
||||||
|
transformQuery(AdminGetPriceListsPriceListProductsParams, {
|
||||||
|
allowedFields: allowedAdminProductFields,
|
||||||
|
defaultFields: defaultAdminProductFields,
|
||||||
|
defaultRelations: defaultAdminProductRelations.filter(
|
||||||
|
(r) => r !== "variants.prices"
|
||||||
|
),
|
||||||
|
defaultLimit: 50,
|
||||||
|
isList: true,
|
||||||
|
}),
|
||||||
middlewares.wrap(require("./list-price-list-products").default)
|
middlewares.wrap(require("./list-price-list-products").default)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -31,7 +51,11 @@ export default (app) => {
|
|||||||
middlewares.wrap(require("./delete-variant-prices").default)
|
middlewares.wrap(require("./delete-variant-prices").default)
|
||||||
)
|
)
|
||||||
|
|
||||||
route.post("/", middlewares.wrap(require("./create-price-list").default))
|
route.post(
|
||||||
|
"/",
|
||||||
|
transformBody(AdminPostPriceListsPriceListReq),
|
||||||
|
middlewares.wrap(require("./create-price-list").default)
|
||||||
|
)
|
||||||
|
|
||||||
route.post("/:id", middlewares.wrap(require("./update-price-list").default))
|
route.post("/:id", middlewares.wrap(require("./update-price-list").default))
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Type } from "class-transformer"
|
import { Type } from "class-transformer"
|
||||||
import { omit, pickBy } from "lodash"
|
import { pickBy } from "lodash"
|
||||||
import {
|
import {
|
||||||
IsArray,
|
IsArray,
|
||||||
IsBoolean,
|
IsBoolean,
|
||||||
@@ -8,19 +8,11 @@ import {
|
|||||||
IsString,
|
IsString,
|
||||||
ValidateNested,
|
ValidateNested,
|
||||||
} from "class-validator"
|
} from "class-validator"
|
||||||
import { Product } from "../../../../models"
|
|
||||||
import { DateComparisonOperator } from "../../../../types/common"
|
import { DateComparisonOperator } from "../../../../types/common"
|
||||||
import { validator } from "../../../../utils/validator"
|
|
||||||
import { FilterableProductProps } from "../../../../types/product"
|
import { FilterableProductProps } from "../../../../types/product"
|
||||||
import {
|
import { AdminGetProductsPaginationParams } from "../products"
|
||||||
AdminGetProductsPaginationParams,
|
|
||||||
allowedAdminProductFields,
|
|
||||||
defaultAdminProductFields,
|
|
||||||
defaultAdminProductRelations,
|
|
||||||
} from "../products"
|
|
||||||
import { MedusaError } from "medusa-core-utils"
|
|
||||||
import { getListConfig } from "../../../../utils/get-query-config"
|
|
||||||
import PriceListService from "../../../../services/price-list"
|
import PriceListService from "../../../../services/price-list"
|
||||||
|
import { Request } from "express"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @oas [get] /price-lists/:id/products
|
* @oas [get] /price-lists/:id/products
|
||||||
@@ -70,81 +62,22 @@ import PriceListService from "../../../../services/price-list"
|
|||||||
* items:
|
* items:
|
||||||
* $ref: "#/components/schemas/product"
|
* $ref: "#/components/schemas/product"
|
||||||
*/
|
*/
|
||||||
export default async (req, res) => {
|
export default async (req: Request, res) => {
|
||||||
const { id } = req.params
|
const { id } = req.params
|
||||||
|
const { offset, limit } = req.validatedQuery
|
||||||
const validatedParams = await validator(
|
|
||||||
AdminGetPriceListsPriceListProductsParams,
|
|
||||||
req.query
|
|
||||||
)
|
|
||||||
|
|
||||||
req.query.price_list_id = [id]
|
|
||||||
|
|
||||||
const query: FilterableProductProps = omit(req.query, [
|
|
||||||
"limit",
|
|
||||||
"offset",
|
|
||||||
"expand",
|
|
||||||
"fields",
|
|
||||||
"order",
|
|
||||||
])
|
|
||||||
|
|
||||||
const limit = validatedParams.limit ?? 50
|
|
||||||
const offset = validatedParams.offset ?? 0
|
|
||||||
const expand = validatedParams.expand
|
|
||||||
const fields = validatedParams.fields
|
|
||||||
const order = validatedParams.order
|
|
||||||
const allowedFields = allowedAdminProductFields
|
|
||||||
const defaultFields = defaultAdminProductFields as (keyof Product)[]
|
|
||||||
const defaultRelations = defaultAdminProductRelations.filter(
|
|
||||||
(r) => r !== "variants.prices"
|
|
||||||
)
|
|
||||||
|
|
||||||
const priceListService: PriceListService =
|
const priceListService: PriceListService =
|
||||||
req.scope.resolve("priceListService")
|
req.scope.resolve("priceListService")
|
||||||
|
|
||||||
let includeFields: (keyof Product)[] | undefined
|
const filterableFields: FilterableProductProps = {
|
||||||
if (fields) {
|
...req.filterableFields,
|
||||||
includeFields = fields.split(",") as (keyof Product)[]
|
price_list_id: [id],
|
||||||
}
|
}
|
||||||
|
|
||||||
let expandFields: string[] | undefined
|
|
||||||
if (expand) {
|
|
||||||
expandFields = expand.split(",")
|
|
||||||
}
|
|
||||||
|
|
||||||
let orderBy: { [k: symbol]: "DESC" | "ASC" } | undefined
|
|
||||||
if (typeof order !== "undefined") {
|
|
||||||
let orderField = order
|
|
||||||
if (order.startsWith("-")) {
|
|
||||||
const [, field] = order.split("-")
|
|
||||||
orderField = field
|
|
||||||
orderBy = { [field]: "DESC" }
|
|
||||||
} else {
|
|
||||||
orderBy = { [order]: "ASC" }
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(allowedFields || []).includes(orderField)) {
|
|
||||||
throw new MedusaError(
|
|
||||||
MedusaError.Types.INVALID_DATA,
|
|
||||||
"Order field must be a valid product field"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const listConfig = getListConfig<Product>(
|
|
||||||
defaultFields ?? [],
|
|
||||||
defaultRelations ?? [],
|
|
||||||
includeFields,
|
|
||||||
expandFields,
|
|
||||||
limit,
|
|
||||||
offset,
|
|
||||||
orderBy
|
|
||||||
)
|
|
||||||
|
|
||||||
const [products, count] = await priceListService.listProducts(
|
const [products, count] = await priceListService.listProducts(
|
||||||
id,
|
id,
|
||||||
pickBy(query, (val) => typeof val !== "undefined"),
|
pickBy(filterableFields, (val) => typeof val !== "undefined"),
|
||||||
listConfig
|
req.listConfig
|
||||||
)
|
)
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import PriceListService from "../../../../services/price-list"
|
|||||||
import { FindConfig } from "../../../../types/common"
|
import { FindConfig } from "../../../../types/common"
|
||||||
import { FilterablePriceListProps } from "../../../../types/price-list"
|
import { FilterablePriceListProps } from "../../../../types/price-list"
|
||||||
import { validator } from "../../../../utils/validator"
|
import { validator } from "../../../../utils/validator"
|
||||||
|
import { Request } from "express"
|
||||||
/**
|
/**
|
||||||
* @oas [get] /price-lists
|
* @oas [get] /price-lists
|
||||||
* operationId: "GetPriceLists"
|
* operationId: "GetPriceLists"
|
||||||
@@ -35,46 +36,15 @@ import { validator } from "../../../../utils/validator"
|
|||||||
* description: The limit of the Price List query.
|
* description: The limit of the Price List query.
|
||||||
* type: integer
|
* type: integer
|
||||||
*/
|
*/
|
||||||
export default async (req, res) => {
|
export default async (req: Request, res) => {
|
||||||
const validated = await validator(
|
const validated = req.validatedQuery
|
||||||
AdminGetPriceListPaginationParams,
|
|
||||||
req.query
|
|
||||||
)
|
|
||||||
|
|
||||||
const priceListService: PriceListService =
|
const priceListService: PriceListService =
|
||||||
req.scope.resolve("priceListService")
|
req.scope.resolve("priceListService")
|
||||||
|
|
||||||
let expandFields: string[] = []
|
|
||||||
if (validated.expand) {
|
|
||||||
expandFields = validated.expand.split(",")
|
|
||||||
}
|
|
||||||
|
|
||||||
const listConfig: FindConfig<FilterablePriceListProps> = {
|
|
||||||
relations: expandFields,
|
|
||||||
skip: validated.offset,
|
|
||||||
take: validated.limit,
|
|
||||||
order: { created_at: "DESC" } as { [k: string]: "DESC" },
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof validated.order !== "undefined") {
|
|
||||||
if (validated.order.startsWith("-")) {
|
|
||||||
const [, field] = validated.order.split("-")
|
|
||||||
listConfig.order = { [field]: "DESC" }
|
|
||||||
} else {
|
|
||||||
listConfig.order = { [validated.order]: "ASC" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const filterableFields: FilterablePriceListProps = omit(validated, [
|
|
||||||
"limit",
|
|
||||||
"offset",
|
|
||||||
"expand",
|
|
||||||
"order",
|
|
||||||
])
|
|
||||||
|
|
||||||
const [price_lists, count] = await priceListService.listAndCount(
|
const [price_lists, count] = await priceListService.listAndCount(
|
||||||
filterableFields,
|
req.filterableFields,
|
||||||
listConfig
|
req.listConfig
|
||||||
)
|
)
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
|
|||||||
@@ -24,10 +24,10 @@ const listAndCount = async (
|
|||||||
body?: object,
|
body?: object,
|
||||||
context: ListContext = { limit: 50, offset: 0 }
|
context: ListContext = { limit: 50, offset: 0 }
|
||||||
): Promise<AdminProductsListRes> => {
|
): Promise<AdminProductsListRes> => {
|
||||||
|
const productService: ProductService = scope.resolve("productService")
|
||||||
const { limit, offset, allowedFields, defaultFields, defaultRelations } =
|
const { limit, offset, allowedFields, defaultFields, defaultRelations } =
|
||||||
context
|
context
|
||||||
|
|
||||||
const productService: ProductService = scope.resolve("productService")
|
|
||||||
let includeFields: (keyof Product)[] | undefined
|
let includeFields: (keyof Product)[] | undefined
|
||||||
if (context.fields) {
|
if (context.fields) {
|
||||||
includeFields = context.fields.split(",") as (keyof Product)[]
|
includeFields = context.fields.split(",") as (keyof Product)[]
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
import "reflect-metadata"
|
import "reflect-metadata"
|
||||||
import { FindManyOptions, FindOperator, OrderByCondition } from "typeorm"
|
import { FindManyOptions, FindOperator, OrderByCondition } from "typeorm"
|
||||||
import { transformDate } from "../utils/validators/date-transform"
|
import { transformDate } from "../utils/validators/date-transform"
|
||||||
|
import { BaseEntity } from "../interfaces/models/base-entity"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility type used to remove some optional attributes (coming from K) from a type T
|
* Utility type used to remove some optional attributes (coming from K) from a type T
|
||||||
@@ -75,6 +76,22 @@ export interface CustomFindOptions<TModel, InKeys extends keyof TModel> {
|
|||||||
take?: number
|
take?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type QueryConfig<TEntity extends BaseEntity> = {
|
||||||
|
defaultFields?: (keyof TEntity | string)[]
|
||||||
|
defaultRelations?: string[]
|
||||||
|
allowedFields?: string[]
|
||||||
|
defaultLimit?: number
|
||||||
|
isList?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export type RequestQueryFields = {
|
||||||
|
expand?: string
|
||||||
|
fields?: string
|
||||||
|
offset?: number
|
||||||
|
limit?: number
|
||||||
|
order?: string
|
||||||
|
}
|
||||||
|
|
||||||
export type PaginatedResponse = { limit: number; offset: number; count: number }
|
export type PaginatedResponse = { limit: number; offset: number; count: number }
|
||||||
|
|
||||||
export type DeleteResponse = {
|
export type DeleteResponse = {
|
||||||
|
|||||||
@@ -2,13 +2,19 @@ import { AwilixContainer } from "awilix"
|
|||||||
import { Logger as _Logger } from "winston"
|
import { Logger as _Logger } from "winston"
|
||||||
import { LoggerOptions } from "typeorm"
|
import { LoggerOptions } from "typeorm"
|
||||||
import { Customer, User } from "../models"
|
import { Customer, User } from "../models"
|
||||||
|
import { FindConfig, RequestQueryFields } from "./common"
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||||
namespace Express {
|
namespace Express {
|
||||||
interface Request {
|
interface Request {
|
||||||
user?: User | Customer
|
user?: (User | Customer) & { userId?: string }
|
||||||
scope: MedusaContainer
|
scope: MedusaContainer
|
||||||
|
validatedQuery: RequestQueryFields & Record<string, unknown>
|
||||||
|
validatedBody: unknown
|
||||||
|
listConfig: FindConfig<unknown>
|
||||||
|
retrieveConfig: FindConfig<unknown>
|
||||||
|
filterableFields: Record<string, unknown>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -124,6 +124,8 @@ export type CreatePriceListInput = {
|
|||||||
status?: PriceListStatus
|
status?: PriceListStatus
|
||||||
prices: AdminPriceListPricesCreateReq[]
|
prices: AdminPriceListPricesCreateReq[]
|
||||||
customer_groups?: { id: string }[]
|
customer_groups?: { id: string }[]
|
||||||
|
starts_at?: Date
|
||||||
|
ends_at?: Date
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UpdatePriceListInput = Partial<
|
export type UpdatePriceListInput = Partial<
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
import { pick } from "lodash"
|
import { pick } from "lodash"
|
||||||
import { FindConfig } from "../types/common"
|
import { FindConfig, QueryConfig, RequestQueryFields } from "../types/common"
|
||||||
|
import { MedusaError } from "medusa-core-utils/dist"
|
||||||
type BaseEntity = {
|
import { BaseEntity } from "../interfaces/models/base-entity"
|
||||||
id: string
|
|
||||||
created_at: Date
|
|
||||||
}
|
|
||||||
|
|
||||||
export function pickByConfig<TModel extends BaseEntity>(
|
export function pickByConfig<TModel extends BaseEntity>(
|
||||||
obj: TModel | TModel[],
|
obj: TModel | TModel[],
|
||||||
@@ -81,3 +78,73 @@ export function getListConfig<TModel extends BaseEntity>(
|
|||||||
order: orderBy,
|
order: orderBy,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function prepareListQuery<
|
||||||
|
T extends RequestQueryFields,
|
||||||
|
TEntity extends BaseEntity
|
||||||
|
>(validated: T, queryConfig?: QueryConfig<TEntity>) {
|
||||||
|
const { order, fields, expand, limit, offset } = validated
|
||||||
|
|
||||||
|
let expandRelations: string[] | undefined = undefined
|
||||||
|
if (expand) {
|
||||||
|
expandRelations = expand.split(",")
|
||||||
|
}
|
||||||
|
|
||||||
|
let expandFields: (keyof TEntity)[] | undefined = undefined
|
||||||
|
if (fields) {
|
||||||
|
expandFields = fields.split(",") as (keyof TEntity)[]
|
||||||
|
}
|
||||||
|
|
||||||
|
let orderBy: { [k: symbol]: "DESC" | "ASC" } | undefined
|
||||||
|
if (typeof order !== "undefined") {
|
||||||
|
let orderField = order
|
||||||
|
if (order.startsWith("-")) {
|
||||||
|
const [, field] = order.split("-")
|
||||||
|
orderField = field
|
||||||
|
orderBy = { [field]: "DESC" }
|
||||||
|
} else {
|
||||||
|
orderBy = { [order]: "ASC" }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (queryConfig?.allowedFields?.length && !queryConfig?.allowedFields.includes(orderField)) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.INVALID_DATA,
|
||||||
|
`Order field ${orderField} is not valid`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return getListConfig<TEntity>(
|
||||||
|
queryConfig?.defaultFields as (keyof TEntity)[],
|
||||||
|
(queryConfig?.defaultRelations ?? []) as string[],
|
||||||
|
expandFields,
|
||||||
|
expandRelations,
|
||||||
|
limit ?? queryConfig?.defaultLimit,
|
||||||
|
offset ?? 0,
|
||||||
|
orderBy
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function prepareRetrieveQuery<
|
||||||
|
T extends RequestQueryFields,
|
||||||
|
TEntity extends BaseEntity
|
||||||
|
>(validated: T, queryConfig?: QueryConfig<TEntity>) {
|
||||||
|
const { fields, expand } = validated
|
||||||
|
|
||||||
|
let expandRelations: string[] = []
|
||||||
|
if (expand) {
|
||||||
|
expandRelations = expand.split(",")
|
||||||
|
}
|
||||||
|
|
||||||
|
let expandFields: (keyof TEntity)[] = []
|
||||||
|
if (fields) {
|
||||||
|
expandFields = fields.split(",") as (keyof TEntity)[]
|
||||||
|
}
|
||||||
|
|
||||||
|
return getRetrieveConfig<TEntity>(
|
||||||
|
queryConfig?.defaultFields as (keyof TEntity)[],
|
||||||
|
(queryConfig?.defaultRelations ?? []) as string[],
|
||||||
|
expandFields,
|
||||||
|
expandRelations
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user