feat: /store api product types (#2552)
## What Allow users to fetch ProductTypes from the storefront API. ## Why This endpoint will allow developers to implement better faceted product search in Medusa without the need for search plugin. Developers will be able to use this to render refinement lists based on types, like this:  ## How Endpoint `GET /store/products/types` and `GET /store/product-types` (use [product types listing in admin](https://github.com/medusajs/medusa/blob/master/packages/medusa/src/api/routes/admin/products/list-types.ts) as reference) Support added in @medusajs/medusa-js Support added in medusa-react ## Testing Similar automated tests as `GET /admin/products/types` and `GET /admin/product-types` --- Resolves CORE-699
This commit is contained in:
@@ -56,6 +56,7 @@ export * from "./routes/store/gift-cards"
|
||||
export * from "./routes/store/order-edits"
|
||||
export * from "./routes/store/orders"
|
||||
export * from "./routes/store/products"
|
||||
export * from "./routes/store/product-types"
|
||||
export * from "./routes/store/regions"
|
||||
export * from "./routes/store/return-reasons"
|
||||
export * from "./routes/store/returns"
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import {
|
||||
DateComparisonOperator,
|
||||
FindPaginationParams,
|
||||
StringComparisonOperator,
|
||||
} from "../../../../types/common"
|
||||
import { IsNumber, IsOptional, IsString } from "class-validator"
|
||||
import { IsOptional, IsString } from "class-validator"
|
||||
|
||||
import { IsType } from "../../../../utils/validators/is-type"
|
||||
import ProductTypeService from "../../../../services/product-type"
|
||||
import { Type } from "class-transformer"
|
||||
|
||||
/**
|
||||
* @oas [get] /product-types
|
||||
@@ -15,7 +15,7 @@ import { Type } from "class-transformer"
|
||||
* description: "Retrieve a list of Product Types."
|
||||
* x-authenticated: true
|
||||
* parameters:
|
||||
* - (query) limit=10 {integer} The number of types to return.
|
||||
* - (query) limit=20 {integer} The number of types to return.
|
||||
* - (query) offset=0 {integer} The number of items to skip before the results.
|
||||
* - (query) order {string} The field to sort items by.
|
||||
* - (query) discount_condition_id {string} The discount condition id on which to filter the product types.
|
||||
@@ -154,20 +154,8 @@ export default async (req, res) => {
|
||||
})
|
||||
}
|
||||
|
||||
export class AdminGetProductTypesPaginationParams {
|
||||
@IsNumber()
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
limit? = 10
|
||||
|
||||
@IsNumber()
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
offset? = 0
|
||||
}
|
||||
|
||||
// eslint-disable-next-line max-len
|
||||
export class AdminGetProductTypesParams extends AdminGetProductTypesPaginationParams {
|
||||
export class AdminGetProductTypesParams extends FindPaginationParams {
|
||||
@IsType([String, [String], StringComparisonOperator])
|
||||
@IsOptional()
|
||||
id?: string | string[] | StringComparisonOperator
|
||||
|
||||
@@ -2,6 +2,7 @@ import { ProductService } from "../../../../services"
|
||||
|
||||
/**
|
||||
* @oas [get] /products/types
|
||||
* deprecated: true
|
||||
* operationId: "GetProductsTypes"
|
||||
* summary: "List Product Types"
|
||||
* description: "Retrieves a list of Product Types."
|
||||
|
||||
@@ -9,6 +9,7 @@ import giftCardRoutes from "./gift-cards"
|
||||
import orderRoutes from "./orders"
|
||||
import orderEditRoutes from "./order-edits"
|
||||
import productRoutes from "./products"
|
||||
import productTypesRoutes from "../admin/product-types"
|
||||
import regionRoutes from "./regions"
|
||||
import returnReasonRoutes from "./return-reasons"
|
||||
import returnRoutes from "./returns"
|
||||
@@ -35,6 +36,7 @@ export default (app, container, config) => {
|
||||
collectionRoutes(route)
|
||||
customerRoutes(route, container)
|
||||
productRoutes(route)
|
||||
productTypesRoutes(route)
|
||||
orderRoutes(route)
|
||||
orderEditRoutes(route)
|
||||
cartRoutes(route, container)
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import { Router } from "express"
|
||||
import { ProductType } from "../../../.."
|
||||
import { PaginatedResponse } from "../../../../types/common"
|
||||
import middlewares, { transformQuery } from "../../../middlewares"
|
||||
import "reflect-metadata"
|
||||
import { StoreGetProductTypesParams } from "./list-product-types"
|
||||
|
||||
const route = Router()
|
||||
|
||||
export default (app) => {
|
||||
app.use("/product-types", route)
|
||||
|
||||
route.get(
|
||||
"/",
|
||||
transformQuery(StoreGetProductTypesParams, {
|
||||
defaultFields: defaultStoreProductTypeFields,
|
||||
defaultRelations: defaultStoreProductTypeRelations,
|
||||
allowedFields: allowedStoreProductTypeFields,
|
||||
isList: true,
|
||||
}),
|
||||
middlewares.wrap(require("./list-product-types").default)
|
||||
)
|
||||
|
||||
return app
|
||||
}
|
||||
|
||||
export const allowedStoreProductTypeFields = [
|
||||
"id",
|
||||
"value",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
]
|
||||
|
||||
export const defaultStoreProductTypeFields = [
|
||||
"id",
|
||||
"value",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
]
|
||||
export const defaultStoreProductTypeRelations = []
|
||||
|
||||
export type StoreProductTypesListRes = PaginatedResponse & {
|
||||
product_types: ProductType[]
|
||||
}
|
||||
|
||||
export type StoreProductTypesRes = {
|
||||
product_type: ProductType
|
||||
}
|
||||
|
||||
export * from "./list-product-types"
|
||||
@@ -0,0 +1,186 @@
|
||||
import {
|
||||
DateComparisonOperator,
|
||||
FindPaginationParams,
|
||||
StringComparisonOperator,
|
||||
} from "../../../../types/common"
|
||||
import { IsOptional, IsString } from "class-validator"
|
||||
|
||||
import { IsType } from "../../../../utils/validators/is-type"
|
||||
import ProductTypeService from "../../../../services/product-type"
|
||||
|
||||
/**
|
||||
* @oas [get] /product-types
|
||||
* operationId: "GetProductTypes"
|
||||
* summary: "List Product Types"
|
||||
* description: "Retrieve a list of Product Types."
|
||||
* x-authenticated: true
|
||||
* parameters:
|
||||
* - (query) limit=20 {integer} The number of types to return.
|
||||
* - (query) offset=0 {integer} The number of items to skip before the results.
|
||||
* - (query) order {string} The field to sort items by.
|
||||
* - (query) discount_condition_id {string} The discount condition id on which to filter the product types.
|
||||
* - in: query
|
||||
* name: value
|
||||
* style: form
|
||||
* explode: false
|
||||
* description: The type values to search for
|
||||
* schema:
|
||||
* type: array
|
||||
* items:
|
||||
* type: string
|
||||
* - in: query
|
||||
* name: id
|
||||
* style: form
|
||||
* explode: false
|
||||
* description: The type IDs to search for
|
||||
* schema:
|
||||
* type: array
|
||||
* items:
|
||||
* type: string
|
||||
* - (query) q {string} A query string to search values for
|
||||
* - in: query
|
||||
* name: created_at
|
||||
* description: Date comparison for when resulting product types were created.
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* lt:
|
||||
* type: string
|
||||
* description: filter by dates less than this date
|
||||
* format: date
|
||||
* gt:
|
||||
* type: string
|
||||
* description: filter by dates greater than this date
|
||||
* format: date
|
||||
* lte:
|
||||
* type: string
|
||||
* description: filter by dates less than or equal to this date
|
||||
* format: date
|
||||
* gte:
|
||||
* type: string
|
||||
* description: filter by dates greater than or equal to this date
|
||||
* format: date
|
||||
* - in: query
|
||||
* name: updated_at
|
||||
* description: Date comparison for when resulting product types were updated.
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* lt:
|
||||
* type: string
|
||||
* description: filter by dates less than this date
|
||||
* format: date
|
||||
* gt:
|
||||
* type: string
|
||||
* description: filter by dates greater than this date
|
||||
* format: date
|
||||
* lte:
|
||||
* type: string
|
||||
* description: filter by dates less than or equal to this date
|
||||
* format: date
|
||||
* gte:
|
||||
* type: string
|
||||
* description: filter by dates greater than or equal to this date
|
||||
* format: date
|
||||
* x-codeSamples:
|
||||
* - lang: JavaScript
|
||||
* label: JS Client
|
||||
* source: |
|
||||
* import Medusa from "@medusajs/medusa-js"
|
||||
* const medusa = new Medusa({ baseUrl: MEDUSA_BACKEND_URL, maxRetries: 3 })
|
||||
* // must be previously logged in or use api token
|
||||
* medusa.store.productTypes.list()
|
||||
* .then(({ product_types }) => {
|
||||
* console.log(product_types.length);
|
||||
* });
|
||||
* - lang: Shell
|
||||
* label: cURL
|
||||
* source: |
|
||||
* curl --location --request GET 'https://medusa-url.com/store/product-types' \
|
||||
* --header 'Authorization: Bearer {api_token}'
|
||||
* security:
|
||||
* - api_token: []
|
||||
* - cookie_auth: []
|
||||
* tags:
|
||||
* - Product Type
|
||||
* responses:
|
||||
* "200":
|
||||
* description: OK
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* properties:
|
||||
* product_types:
|
||||
* $ref: "#/components/schemas/product_type"
|
||||
* count:
|
||||
* type: integer
|
||||
* description: The total number of items available
|
||||
* offset:
|
||||
* type: integer
|
||||
* description: The number of items skipped before these items
|
||||
* limit:
|
||||
* type: integer
|
||||
* description: The number of items per page
|
||||
* "400":
|
||||
* $ref: "#/components/responses/400_error"
|
||||
* "401":
|
||||
* $ref: "#/components/responses/unauthorized"
|
||||
* "404":
|
||||
* $ref: "#/components/responses/not_found_error"
|
||||
* "409":
|
||||
* $ref: "#/components/responses/invalid_state_error"
|
||||
* "422":
|
||||
* $ref: "#/components/responses/invalid_request_error"
|
||||
* "500":
|
||||
* $ref: "#/components/responses/500_error"
|
||||
*/
|
||||
export default async (req, res) => {
|
||||
const typeService: ProductTypeService =
|
||||
req.scope.resolve("productTypeService")
|
||||
|
||||
const { listConfig, filterableFields } = req
|
||||
const { skip, take } = req.listConfig
|
||||
|
||||
const [types, count] = await typeService.listAndCount(
|
||||
filterableFields,
|
||||
listConfig
|
||||
)
|
||||
|
||||
res.status(200).json({
|
||||
product_types: types,
|
||||
count,
|
||||
offset: skip,
|
||||
limit: take,
|
||||
})
|
||||
}
|
||||
|
||||
// eslint-disable-next-line max-len
|
||||
export class StoreGetProductTypesParams extends FindPaginationParams {
|
||||
@IsType([String, [String], StringComparisonOperator])
|
||||
@IsOptional()
|
||||
id?: string | string[] | StringComparisonOperator
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
q?: string
|
||||
|
||||
@IsType([String, [String], StringComparisonOperator])
|
||||
@IsOptional()
|
||||
value?: string | string[] | StringComparisonOperator
|
||||
|
||||
@IsType([DateComparisonOperator])
|
||||
@IsOptional()
|
||||
created_at?: DateComparisonOperator
|
||||
|
||||
@IsType([DateComparisonOperator])
|
||||
@IsOptional()
|
||||
updated_at?: DateComparisonOperator
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
order?: string
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
discount_condition_id?: string
|
||||
}
|
||||
Reference in New Issue
Block a user