feat: Update product category (#7092)

* feat: Update product category

* Disable V2 FF in product category tes

* clean up
This commit is contained in:
Oli Juhl authored and GitHub committed 2024-04-22 12:30:27 +02:00
1 parent 5c71e8304d
commit 67a21c3e45
15 files changed
+383 -110

No files matched your search

@@ -0,0 +1,61 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import {
IProductModuleService,
UpdateProductCategoryDTO,
} from "@medusajs/types"
import { getSelectsAndRelationsFromObjectArray } from "@medusajs/utils"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
type UpdateProductCategoryStepInput = {
id: string
data: UpdateProductCategoryDTO
}
export const updateProductCategoryStepId = "update-product-category"
export const updateProductCategoryStep = createStep(
updateProductCategoryStepId,
async (data: UpdateProductCategoryStepInput, { container }) => {
const service = container.resolve<IProductModuleService>(
ModuleRegistrationName.PRODUCT
)
const { selects, relations } = getSelectsAndRelationsFromObjectArray([
data.data,
])
const prevData = await service.listCategories(
{ id: data.id },
{
select: selects,
relations,
}
)
const updated = await service.updateCategory(data.id, data.data)
return new StepResponse(updated, prevData)
},
async (prevData, { container }) => {
if (!prevData?.length) {
return
}
const service = container.resolve<IProductModuleService>(
ModuleRegistrationName.PRODUCT
)
// TODO: Should be removed when bulk update is implemented
const category = prevData[0]
await service.updateCategory(category.id, {
name: category.name,
description: category.description,
is_active: category.is_active,
is_internal: category.is_internal,
rank: category.rank,
handle: category.handle,
metadata: category.metadata,
parent_category_id: category.parent_category_id,
})
}
)
@@ -1 +1,2 @@
export * from "./create-product-category"
export * from "./update-product-category"
@@ -0,0 +1,16 @@
import { ProductCategoryWorkflow } from "@medusajs/types"
import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk"
import { updateProductCategoryStep } from "../steps/update-product-category"
type WorkflowInputData =
ProductCategoryWorkflow.UpdateProductCategoryWorkflowInput
export const updateProductCategoryWorkflowId = "update-product-category"
export const updateProductCategoryWorkflow = createWorkflow(
updateProductCategoryWorkflowId,
(input: WorkflowData<WorkflowInputData>) => {
const category = updateProductCategoryStep(input)
return category
}
)
@@ -1,10 +1,14 @@
import { updateProductCategoryWorkflow } from "@medusajs/core-flows"
import { AdminProductCategoryResponse } from "@medusajs/types"
import {
AuthenticatedMedusaRequest,
MedusaResponse,
} from "../../../../types/routing"
import { AdminProductCategoryParamsType } from "../validators"
import { refetchCategory } from "../helpers"
import {
AdminProductCategoryParamsType,
AdminUpdateProductCategoryType,
} from "../validators"
export const GET = async (
req: AuthenticatedMedusaRequest<AdminProductCategoryParamsType>,
@@ -13,8 +17,35 @@ export const GET = async (
const category = await refetchCategory(
req.params.id,
req.scope,
req.remoteQueryConfig.fields
req.remoteQueryConfig.fields,
req.filterableFields
)
res.json({ product_category: category })
}
export const POST = async (
req: AuthenticatedMedusaRequest<AdminUpdateProductCategoryType>,
res: MedusaResponse<AdminProductCategoryResponse>
) => {
const { id } = req.params
// TODO: Should be converted to bulk update
const { errors } = await updateProductCategoryWorkflow(req.scope).run({
input: { id, data: req.validatedBody },
throwOnError: false,
})
if (Array.isArray(errors) && errors[0]) {
throw errors[0].error
}
const category = await refetchCategory(
req.params.id,
req.scope,
req.remoteQueryConfig.fields,
req.filterableFields
)
res.status(200).json({ product_category: category })
}
@@ -7,13 +7,14 @@ import {
export const refetchCategory = async (
categoryId: string,
scope: MedusaContainer,
fields: string[]
fields: string[],
filterableFields: Record<string, any> = {}
) => {
const remoteQuery = scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
const queryObject = remoteQueryObjectFromString({
entryPoint: "product_category",
variables: {
filters: { id: categoryId },
filters: { ...filterableFields, id: categoryId },
},
fields: fields,
})
@@ -7,6 +7,7 @@ import {
AdminCreateProductCategory,
AdminProductCategoriesParams,
AdminProductCategoryParams,
AdminUpdateProductCategory,
} from "./validators"
export const adminProductCategoryRoutesMiddlewares: MiddlewareRoute[] = [
@@ -46,4 +47,15 @@ export const adminProductCategoryRoutesMiddlewares: MiddlewareRoute[] = [
),
],
},
{
method: ["POST"],
matcher: "/admin/product-categories/:id",
middlewares: [
validateAndTransformBody(AdminUpdateProductCategory),
validateAndTransformQuery(
AdminProductCategoryParams,
QueryConfig.retrieveProductCategoryConfig
),
],
},
]
@@ -11,11 +11,11 @@ import {
AuthenticatedMedusaRequest,
MedusaResponse,
} from "../../../types/routing"
import { refetchCategory } from "./helpers"
import {
AdminCreateProductCategoryType,
AdminProductCategoriesParamsType,
} from "./validators"
import { refetchCategory } from "./helpers"
export const GET = async (
req: AuthenticatedMedusaRequest<AdminProductCategoriesParamsType>,
@@ -60,7 +60,8 @@ export const POST = async (
const category = await refetchCategory(
result.id,
req.scope,
req.remoteQueryConfig.fields
req.remoteQueryConfig.fields,
req.filterableFields
)
res.status(200).json({ product_category: category })
@@ -68,9 +68,27 @@ export const AdminCreateProductCategory = z
is_active: z.boolean().optional(),
parent_category_id: z.string().optional(),
metadata: z.record(z.unknown()).optional(),
rank: z.number().nonnegative().optional(),
})
.strict()
export type AdminCreateProductCategoryType = z.infer<
typeof AdminCreateProductCategory
>
export const AdminUpdateProductCategory = z
.object({
name: z.string().optional(),
description: z.string().optional(),
handle: z.string().optional(),
is_internal: z.boolean().optional(),
is_active: z.boolean().optional(),
parent_category_id: z.string().optional(),
metadata: z.record(z.unknown()).optional(),
rank: z.number().nonnegative().optional(),
})
.strict()
export type AdminUpdateProductCategoryType = z.infer<
typeof AdminUpdateProductCategory
>
@@ -1,12 +1,13 @@
import {
AuthenticatedMedusaRequest,
MedusaResponse,
} from "../../../../types/routing"
import {
deleteProductsWorkflow,
updateProductsWorkflow,
} from "@medusajs/core-flows"
import {
AuthenticatedMedusaRequest,
MedusaResponse,
} from "../../../../types/routing"
import { UpdateProductDTO } from "@medusajs/types"
import {
ContainerRegistrationKeys,
remoteQueryObjectFromString,
@@ -17,7 +18,6 @@ import {
remapProductResponse,
} from "../helpers"
import { AdminUpdateProductType } from "../validators"
import { UpdateProductDTO } from "@medusajs/types"
export const GET = async (
req: AuthenticatedMedusaRequest,
@@ -1,3 +1,4 @@
import { selectorConstraintsToString } from "@medusajs/utils"
import { isDefined, MedusaError } from "medusa-core-utils"
import { Between, EntityManager, MoreThanOrEqual, Not } from "typeorm"
import { EventBusService } from "."
@@ -17,7 +18,6 @@ import {
UpdateProductCategoryInput,
} from "../types/product-category"
import { buildQuery, nullableValue, setMetadata } from "../utils"
import {selectorConstraintsToString} from "@medusajs/utils";
type InjectedDependencies = {
manager: EntityManager
@@ -63,7 +63,9 @@ export class InitialSetup20240315083440 extends Migration {
this.addSql('create unique index if not exists "IDX_category_handle_unique" on "product_category" (handle) where deleted_at is null;')
this.addSql('create index if not exists "IDX_product_category_path" on "product_category" ("mpath") where deleted_at is null;');
this.addSql('create index if not exists "IDX_product_category_deleted_at" on "product_collection" ("deleted_at");');
// TODO: Batch updating composite unique index in MikroORM is faulty. Should be added back when issue has been resolved.
this.addSql(`drop index if exists "UniqProductCategoryParentIdRank";`)
/* --- PIVOT TABLES --- */
this.addSql('create table if not exists "product_tags" ("product_id" text not null, "product_tag_id" text not null, constraint "product_tags_pkey" primary key ("product_id", "product_tag_id"));');
this.addSql('create table if not exists "product_images" ("product_id" text not null, "image_id" text not null, constraint "product_images_pkey" primary key ("product_id", "image_id"));');
@@ -417,10 +417,10 @@ export class ProductCategoryRepository extends DALUtils.MikroOrmBaseTreeReposito
rankCondition = { $gte: targetRank }
} else if (originalRank > targetRank) {
shouldIncrementRank = true
rankCondition = { $gte: targetRank, $lt: originalRank }
rankCondition = { $gte: targetRank, $lte: originalRank }
} else {
shouldIncrementRank = false
rankCondition = { $gte: originalRank, $lt: targetRank }
rankCondition = { $gte: originalRank, $lte: targetRank }
}
// Scope out the list of siblings that we need to shift up or down
+18 -6
View File
@@ -287,23 +287,27 @@ export interface ProductCategoryDTO {
/**
* The description of the product category.
*/
description?: string
description: string
/**
* The handle of the product category. The handle can be used to create slug URL paths.
*/
handle?: string
handle: string
/**
* Whether the product category is active.
*/
is_active?: boolean
is_active: boolean
/**
* Whether the product category is internal. This can be used to only show the product category to admins and hide it from customers.
*/
is_internal?: boolean
is_internal: boolean
/**
* The ranking of the product category among sibling categories.
*/
rank?: number
rank: number
/**
* The ranking of the product category among sibling categories.
*/
metadata?: Record<string, unknown> | null
/**
* The associated parent category.
*
@@ -340,6 +344,10 @@ export interface CreateProductCategoryDTO {
* The product category's name.
*/
name: string
/**
* The product category's description.
*/
description?: string
/**
* The product category's handle.
*/
@@ -376,6 +384,10 @@ export interface UpdateProductCategoryDTO {
* The name of the product category.
*/
name?: string
/**
* The product category's description.
*/
description?: string
/**
* The handle of the product category.
*/
@@ -399,7 +411,7 @@ export interface UpdateProductCategoryDTO {
/**
* Holds custom data in key-value pairs.
*/
metadata?: Record<string, unknown>
metadata?: Record<string, unknown> | null
}
/**
@@ -1,5 +1,16 @@
import { CreateProductCategoryDTO } from "../../product"
import {
CreateProductCategoryDTO,
UpdateProductCategoryDTO,
} from "../../product"
export interface CreateProductCategoryWorkflowInput {
product_category: CreateProductCategoryDTO
}
// TODO: Should we converted to bulk update
export interface UpdateProductCategoryWorkflowInput {
// selector: FilterableProductCategoryProps
// data: UpdateProductCategoryDTO
id: string
data: UpdateProductCategoryDTO
}