feat: Add collection and category endpoints to store (#7155)
* feat: Add collection endpoints to store * feat: Add category store endpoints
This commit is contained in:
@@ -10,7 +10,7 @@ export const defaults = [
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"metadata",
|
||||
|
||||
"*parent_category",
|
||||
"*category_children",
|
||||
]
|
||||
|
||||
|
||||
@@ -34,9 +34,11 @@ import { adminWorkflowsExecutionsMiddlewares } from "./admin/workflows-execution
|
||||
import { authRoutesMiddlewares } from "./auth/middlewares"
|
||||
import { hooksRoutesMiddlewares } from "./hooks/middlewares"
|
||||
import { storeCartRoutesMiddlewares } from "./store/carts/middlewares"
|
||||
import { storeCollectionRoutesMiddlewares } from "./store/collections/middlewares"
|
||||
import { storeCurrencyRoutesMiddlewares } from "./store/currencies/middlewares"
|
||||
import { storeCustomerRoutesMiddlewares } from "./store/customers/middlewares"
|
||||
import { storeProductRoutesMiddlewares } from "./store/products/middlewares"
|
||||
import { storeProductCategoryRoutesMiddlewares } from "./store/product-categories/middlewares"
|
||||
import { storeRegionRoutesMiddlewares } from "./store/regions/middlewares"
|
||||
|
||||
export const config: MiddlewaresConfig = {
|
||||
@@ -48,6 +50,8 @@ export const config: MiddlewaresConfig = {
|
||||
...storeCartRoutesMiddlewares,
|
||||
...storeCustomerRoutesMiddlewares,
|
||||
...storeCartRoutesMiddlewares,
|
||||
...storeCollectionRoutesMiddlewares,
|
||||
...storeProductCategoryRoutesMiddlewares,
|
||||
...authRoutesMiddlewares,
|
||||
...adminWorkflowsExecutionsMiddlewares,
|
||||
...storeRegionRoutesMiddlewares,
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import {
|
||||
AuthenticatedMedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "../../../../types/routing"
|
||||
import { refetchCollection } from "../helpers"
|
||||
|
||||
export const GET = async (
|
||||
req: AuthenticatedMedusaRequest,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
const collection = await refetchCollection(
|
||||
req.params.id,
|
||||
req.scope,
|
||||
req.remoteQueryConfig.fields
|
||||
)
|
||||
|
||||
res.status(200).json({ collection })
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { MedusaContainer } from "@medusajs/types"
|
||||
import {
|
||||
ContainerRegistrationKeys,
|
||||
remoteQueryObjectFromString,
|
||||
} from "@medusajs/utils"
|
||||
|
||||
export const refetchCollection = async (
|
||||
collectionId: string,
|
||||
scope: MedusaContainer,
|
||||
fields: string[]
|
||||
) => {
|
||||
const remoteQuery = scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
|
||||
const queryObject = remoteQueryObjectFromString({
|
||||
entryPoint: "product_collection",
|
||||
variables: {
|
||||
filters: { id: collectionId },
|
||||
},
|
||||
fields: fields,
|
||||
})
|
||||
|
||||
const collections = await remoteQuery(queryObject)
|
||||
return collections[0]
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import * as QueryConfig from "./query-config"
|
||||
import { MiddlewareRoute } from "../../../loaders/helpers/routing/types"
|
||||
import { validateAndTransformQuery } from "../../utils/validate-query"
|
||||
import {
|
||||
StoreGetCollectionParams,
|
||||
StoreGetCollectionsParams,
|
||||
} from "./validators"
|
||||
|
||||
export const storeCollectionRoutesMiddlewares: MiddlewareRoute[] = [
|
||||
{
|
||||
method: ["GET"],
|
||||
matcher: "/store/collections",
|
||||
middlewares: [
|
||||
validateAndTransformQuery(
|
||||
StoreGetCollectionsParams,
|
||||
QueryConfig.listTransformQueryConfig
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
method: ["GET"],
|
||||
matcher: "/store/collections/:id",
|
||||
middlewares: [
|
||||
validateAndTransformQuery(
|
||||
StoreGetCollectionParams,
|
||||
QueryConfig.retrieveTransformQueryConfig
|
||||
),
|
||||
],
|
||||
},
|
||||
]
|
||||
@@ -0,0 +1,18 @@
|
||||
export const defaultStoreCollectionFields = [
|
||||
"id",
|
||||
"title",
|
||||
"handle",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
]
|
||||
|
||||
export const retrieveTransformQueryConfig = {
|
||||
defaults: defaultStoreCollectionFields,
|
||||
isList: false,
|
||||
}
|
||||
|
||||
export const listTransformQueryConfig = {
|
||||
...retrieveTransformQueryConfig,
|
||||
defaultLimit: 10,
|
||||
isList: true,
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import {
|
||||
AuthenticatedMedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "../../../types/routing"
|
||||
|
||||
import {
|
||||
ContainerRegistrationKeys,
|
||||
remoteQueryObjectFromString,
|
||||
} from "@medusajs/utils"
|
||||
|
||||
export const GET = async (
|
||||
req: AuthenticatedMedusaRequest,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
|
||||
|
||||
const query = remoteQueryObjectFromString({
|
||||
entryPoint: "product_collection",
|
||||
variables: {
|
||||
filters: req.filterableFields,
|
||||
...req.remoteQueryConfig.pagination,
|
||||
},
|
||||
fields: req.remoteQueryConfig.fields,
|
||||
})
|
||||
|
||||
const { rows: collections, metadata } = await remoteQuery(query)
|
||||
|
||||
res.json({
|
||||
collections,
|
||||
count: metadata.count,
|
||||
offset: metadata.skip,
|
||||
limit: metadata.take,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import {
|
||||
createFindParams,
|
||||
createOperatorMap,
|
||||
createSelectParams,
|
||||
} from "../../utils/validators"
|
||||
import { z } from "zod"
|
||||
|
||||
export const StoreGetCollectionParams = createSelectParams()
|
||||
|
||||
export type StoreGetCollectionsParamsType = z.infer<
|
||||
typeof StoreGetCollectionsParams
|
||||
>
|
||||
export const StoreGetCollectionsParams = createFindParams({
|
||||
offset: 0,
|
||||
limit: 10,
|
||||
order: "-created_at",
|
||||
}).merge(
|
||||
z.object({
|
||||
q: z.string().optional(),
|
||||
title: z.union([z.string(), z.array(z.string())]).optional(),
|
||||
handle: z.union([z.string(), z.array(z.string())]).optional(),
|
||||
created_at: createOperatorMap().optional(),
|
||||
updated_at: createOperatorMap().optional(),
|
||||
deleted_at: createOperatorMap().optional(),
|
||||
$and: z.lazy(() => StoreGetCollectionsParams.array()).optional(),
|
||||
$or: z.lazy(() => StoreGetCollectionsParams.array()).optional(),
|
||||
})
|
||||
)
|
||||
@@ -0,0 +1,28 @@
|
||||
import { StoreProductCategoryResponse } from "@medusajs/types"
|
||||
import {
|
||||
AuthenticatedMedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "../../../../types/routing"
|
||||
import { refetchCategory } from "../helpers"
|
||||
import { StoreProductCategoryParamsType } from "../validators"
|
||||
import { MedusaError } from "@medusajs/utils"
|
||||
|
||||
export const GET = async (
|
||||
req: AuthenticatedMedusaRequest<StoreProductCategoryParamsType>,
|
||||
res: MedusaResponse<StoreProductCategoryResponse>
|
||||
) => {
|
||||
const category = await refetchCategory(
|
||||
req.params.id,
|
||||
req.scope,
|
||||
req.remoteQueryConfig.fields,
|
||||
req.filterableFields
|
||||
)
|
||||
|
||||
if (!category) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_FOUND,
|
||||
`Product category with id: ${req.params.id} was not found`
|
||||
)
|
||||
}
|
||||
res.json({ product_category: category })
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import { MedusaContainer } from "@medusajs/types"
|
||||
import {
|
||||
ContainerRegistrationKeys,
|
||||
remoteQueryObjectFromString,
|
||||
} from "@medusajs/utils"
|
||||
|
||||
export const refetchCategory = async (
|
||||
categoryId: string,
|
||||
scope: MedusaContainer,
|
||||
fields: string[],
|
||||
filterableFields: Record<string, any> = {}
|
||||
) => {
|
||||
const remoteQuery = scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
|
||||
const queryObject = remoteQueryObjectFromString({
|
||||
entryPoint: "product_category",
|
||||
variables: {
|
||||
filters: { ...filterableFields, id: categoryId },
|
||||
},
|
||||
fields: fields,
|
||||
})
|
||||
|
||||
const categories = await remoteQuery(queryObject)
|
||||
return categories[0]
|
||||
}
|
||||
|
||||
export const applyCategoryFilters = (req, res, next) => {
|
||||
if (!req.filterableFields) {
|
||||
req.filterableFields = {}
|
||||
}
|
||||
|
||||
req.filterableFields = {
|
||||
...req.filterableFields,
|
||||
is_active: true,
|
||||
is_internal: false,
|
||||
}
|
||||
|
||||
next()
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import { MiddlewareRoute } from "../../../loaders/helpers/routing/types"
|
||||
import { validateAndTransformQuery } from "../../utils/validate-query"
|
||||
import { applyCategoryFilters } from "./helpers"
|
||||
import * as QueryConfig from "./query-config"
|
||||
import {
|
||||
StoreProductCategoriesParams,
|
||||
StoreProductCategoryParams,
|
||||
} from "./validators"
|
||||
|
||||
export const storeProductCategoryRoutesMiddlewares: MiddlewareRoute[] = [
|
||||
{
|
||||
method: ["GET"],
|
||||
matcher: "/store/product-categories",
|
||||
middlewares: [
|
||||
validateAndTransformQuery(
|
||||
StoreProductCategoriesParams,
|
||||
QueryConfig.listProductCategoryConfig
|
||||
),
|
||||
applyCategoryFilters,
|
||||
],
|
||||
},
|
||||
{
|
||||
method: ["GET"],
|
||||
matcher: "/store/product-categories/:id",
|
||||
middlewares: [
|
||||
validateAndTransformQuery(
|
||||
StoreProductCategoryParams,
|
||||
QueryConfig.retrieveProductCategoryConfig
|
||||
),
|
||||
applyCategoryFilters,
|
||||
],
|
||||
},
|
||||
]
|
||||
@@ -0,0 +1,24 @@
|
||||
export const defaults = [
|
||||
"id",
|
||||
"name",
|
||||
"description",
|
||||
"handle",
|
||||
"rank",
|
||||
"parent_category_id",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"metadata",
|
||||
"*parent_category",
|
||||
"*category_children",
|
||||
]
|
||||
|
||||
export const retrieveProductCategoryConfig = {
|
||||
defaults,
|
||||
isList: false,
|
||||
}
|
||||
|
||||
export const listProductCategoryConfig = {
|
||||
defaults,
|
||||
defaultLimit: 50,
|
||||
isList: true,
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import { StoreProductCategoryListResponse } from "@medusajs/types"
|
||||
import {
|
||||
ContainerRegistrationKeys,
|
||||
remoteQueryObjectFromString,
|
||||
} from "@medusajs/utils"
|
||||
import {
|
||||
AuthenticatedMedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "../../../types/routing"
|
||||
import { StoreProductCategoriesParamsType } from "./validators"
|
||||
|
||||
export const GET = async (
|
||||
req: AuthenticatedMedusaRequest<StoreProductCategoriesParamsType>,
|
||||
res: MedusaResponse<StoreProductCategoryListResponse>
|
||||
) => {
|
||||
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
|
||||
|
||||
const queryObject = remoteQueryObjectFromString({
|
||||
entryPoint: "product_category",
|
||||
variables: {
|
||||
filters: req.filterableFields,
|
||||
...req.remoteQueryConfig.pagination,
|
||||
},
|
||||
fields: req.remoteQueryConfig.fields,
|
||||
})
|
||||
|
||||
const { rows: product_categories, metadata } = await remoteQuery(queryObject)
|
||||
|
||||
res.json({
|
||||
product_categories,
|
||||
count: metadata.count,
|
||||
offset: metadata.skip,
|
||||
limit: metadata.take,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import { z } from "zod"
|
||||
import { optionalBooleanMapper } from "../../../utils/validators/is-boolean"
|
||||
import {
|
||||
createFindParams,
|
||||
createOperatorMap,
|
||||
createSelectParams,
|
||||
} from "../../utils/validators"
|
||||
|
||||
export type StoreProductCategoryParamsType = z.infer<
|
||||
typeof StoreProductCategoryParams
|
||||
>
|
||||
export const StoreProductCategoryParams = createSelectParams().merge(
|
||||
z.object({
|
||||
include_ancestors_tree: z.preprocess(
|
||||
(val: any) => optionalBooleanMapper.get(val?.toLowerCase()),
|
||||
z.boolean().optional()
|
||||
),
|
||||
include_descendants_tree: z.preprocess(
|
||||
(val: any) => optionalBooleanMapper.get(val?.toLowerCase()),
|
||||
z.boolean().optional()
|
||||
),
|
||||
})
|
||||
)
|
||||
|
||||
export type StoreProductCategoriesParamsType = z.infer<
|
||||
typeof StoreProductCategoriesParams
|
||||
>
|
||||
export const StoreProductCategoriesParams = createFindParams({
|
||||
offset: 0,
|
||||
limit: 50,
|
||||
}).merge(
|
||||
z.object({
|
||||
q: z.string().optional(),
|
||||
id: z.union([z.string(), z.array(z.string())]).optional(),
|
||||
description: z.union([z.string(), z.array(z.string())]).optional(),
|
||||
handle: z.union([z.string(), z.array(z.string())]).optional(),
|
||||
parent_category_id: z.union([z.string(), z.array(z.string())]).optional(),
|
||||
include_ancestors_tree: z.preprocess(
|
||||
(val: any) => optionalBooleanMapper.get(val?.toLowerCase()),
|
||||
z.boolean().optional()
|
||||
),
|
||||
include_descendants_tree: z.preprocess(
|
||||
(val: any) => optionalBooleanMapper.get(val?.toLowerCase()),
|
||||
z.boolean().optional()
|
||||
),
|
||||
created_at: createOperatorMap().optional(),
|
||||
updated_at: createOperatorMap().optional(),
|
||||
deleted_at: createOperatorMap().optional(),
|
||||
$and: z.lazy(() => StoreProductCategoriesParams.array()).optional(),
|
||||
$or: z.lazy(() => StoreProductCategoriesParams.array()).optional(),
|
||||
})
|
||||
)
|
||||
@@ -55,6 +55,7 @@ class ProductCategory {
|
||||
@Property({ columnType: "text", default: "", nullable: false })
|
||||
description?: string
|
||||
|
||||
@Searchable()
|
||||
@Property({ columnType: "text", nullable: false })
|
||||
handle?: string
|
||||
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { PaginatedResponse } from "../../common"
|
||||
import { ProductCategoryResponse } from "./common"
|
||||
|
||||
/**
|
||||
* @experimental
|
||||
*/
|
||||
export interface AdminProductCategoryResponse {
|
||||
product_category: ProductCategoryResponse
|
||||
}
|
||||
|
||||
/**
|
||||
* @experimental
|
||||
*/
|
||||
export interface AdminProductCategoryListResponse extends PaginatedResponse {
|
||||
product_categories: ProductCategoryResponse[]
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export * from "./product-category"
|
||||
@@ -1,34 +0,0 @@
|
||||
import { PaginatedResponse } from "../../../common"
|
||||
|
||||
/**
|
||||
* @experimental
|
||||
*/
|
||||
interface ProductCategoryResponse {
|
||||
id: string
|
||||
name: string
|
||||
description: string | null
|
||||
handle: string | null
|
||||
is_active: boolean
|
||||
is_internal: boolean
|
||||
rank: number | null
|
||||
parent_category_id: string | null
|
||||
created_at: string | Date
|
||||
updated_at: string | Date
|
||||
|
||||
parent_category: ProductCategoryResponse
|
||||
category_children: ProductCategoryResponse[]
|
||||
}
|
||||
|
||||
/**
|
||||
* @experimental
|
||||
*/
|
||||
export interface AdminProductCategoryResponse {
|
||||
product_category: ProductCategoryResponse
|
||||
}
|
||||
|
||||
/**
|
||||
* @experimental
|
||||
*/
|
||||
export interface AdminProductCategoryListResponse extends PaginatedResponse {
|
||||
product_categories: ProductCategoryResponse[]
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* @experimental
|
||||
*/
|
||||
export interface ProductCategoryResponse {
|
||||
id: string
|
||||
name: string
|
||||
description: string | null
|
||||
handle: string | null
|
||||
is_active: boolean
|
||||
is_internal: boolean
|
||||
rank: number | null
|
||||
parent_category_id: string | null
|
||||
created_at: string | Date
|
||||
updated_at: string | Date
|
||||
|
||||
parent_category: ProductCategoryResponse
|
||||
category_children: ProductCategoryResponse[]
|
||||
}
|
||||
@@ -1 +1,2 @@
|
||||
export * from "./admin"
|
||||
export * from "./store"
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { PaginatedResponse } from "../../common"
|
||||
import { ProductCategoryResponse } from "./common"
|
||||
|
||||
/**
|
||||
* @experimental
|
||||
*/
|
||||
export interface StoreProductCategoryResponse {
|
||||
product_category: ProductCategoryResponse
|
||||
}
|
||||
|
||||
/**
|
||||
* @experimental
|
||||
*/
|
||||
export interface StoreProductCategoryListResponse extends PaginatedResponse {
|
||||
product_categories: ProductCategoryResponse[]
|
||||
}
|
||||
Reference in New Issue
Block a user