feat(product,dashboard): Allow re-ordering images (#10187)

* migration

* fix snapshot

* primarykey

* init work on dnd

* progress

* dnd

* undo changes

* undo changes

* undo changes

* undo changes

* fix firefox issue

* lint

* lint

* lint

* add changeset

* undo changes to product module

* set activator node

* init work on service layer

* alternative

* switch to OneToMany

* add tests

* progress

* update migration

* update approach and remove all references to images in product.ts tests

* handle delete images on empty array

* fix config and order type

* update changeset

* rm flag

* export type and fix type in test

* fix type

---------

Co-authored-by: Oli Juhl <59018053+olivermrbl@users.noreply.github.com>
This commit is contained in:
Kasper Fabricius Kristensen
2024-11-25 09:03:10 +01:00
committed by GitHub
co-authored by Oli Juhl
parent b12408dbd8
commit 1659c9be5d
32 changed files with 1257 additions and 617 deletions
@@ -1,6 +1,7 @@
import {
Context,
DAL,
FindConfig,
IEventBusModuleService,
InternalModuleDeclaration,
ModuleJoinerConfig,
@@ -8,10 +9,10 @@ import {
ProductTypes,
} from "@medusajs/framework/types"
import {
Image as ProductImage,
Product,
ProductCategory,
ProductCollection,
Image as ProductImage,
ProductOption,
ProductOptionValue,
ProductTag,
@@ -58,6 +59,7 @@ type InjectedDependencies = {
productCategoryService: ProductCategoryService
productCollectionService: ModulesSdkTypes.IMedusaInternalService<any>
productImageService: ModulesSdkTypes.IMedusaInternalService<any>
productImageProductService: ModulesSdkTypes.IMedusaInternalService<any>
productTypeService: ModulesSdkTypes.IMedusaInternalService<any>
productOptionService: ModulesSdkTypes.IMedusaInternalService<any>
productOptionValueService: ModulesSdkTypes.IMedusaInternalService<any>
@@ -151,6 +153,74 @@ export default class ProductModuleService
return joinerConfig
}
@InjectManager()
// @ts-ignore
async retrieveProduct(
productId: string,
config?: FindConfig<ProductTypes.ProductDTO>,
@MedusaContext() sharedContext?: Context
): Promise<ProductTypes.ProductDTO> {
const product = await this.productService_.retrieve(
productId,
this.getProductFindConfig_(config),
sharedContext
)
return this.baseRepository_.serialize<ProductTypes.ProductDTO>(product)
}
@InjectManager()
// @ts-ignore
async listProducts(
filters?: ProductTypes.FilterableProductProps,
config?: FindConfig<ProductTypes.ProductDTO>,
sharedContext?: Context
): Promise<ProductTypes.ProductDTO[]> {
const products = await this.productService_.list(
filters,
this.getProductFindConfig_(config),
sharedContext
)
return this.baseRepository_.serialize<ProductTypes.ProductDTO[]>(products)
}
@InjectManager()
// @ts-ignore
async listAndCountProducts(
filters?: ProductTypes.FilterableProductProps,
config?: FindConfig<ProductTypes.ProductDTO>,
sharedContext?: Context
): Promise<[ProductTypes.ProductDTO[], number]> {
const [products, count] = await this.productService_.listAndCount(
filters,
this.getProductFindConfig_(config),
sharedContext
)
const serializedProducts = await this.baseRepository_.serialize<
ProductTypes.ProductDTO[]
>(products)
return [serializedProducts, count]
}
protected getProductFindConfig_(
config?: FindConfig<ProductTypes.ProductDTO>
): FindConfig<ProductTypes.ProductDTO> {
const hasImagesRelation = config?.relations?.includes("images")
return {
...config,
order: {
...config?.order,
...(hasImagesRelation ? {
images: {
rank: "ASC",
},
} : {}),
},
}
}
// @ts-ignore
createProductVariants(
data: ProductTypes.CreateProductVariantDTO[],
@@ -1440,7 +1510,7 @@ export default class ProductModuleService
await this.productService_.upsertWithReplace(
normalizedInput,
{
relations: ["images", "tags", "categories"],
relations: ["tags", "categories"],
},
sharedContext
)
@@ -1480,6 +1550,27 @@ export default class ProductModuleService
)
upsertedProduct.variants = productVariants
}
if (Array.isArray(product.images)) {
if (product.images.length) {
const { entities: productImages } =
await this.productImageService_.upsertWithReplace(
product.images.map((image, rank) => ({
...image,
product_id: upsertedProduct.id,
rank,
})),
{},
sharedContext
)
upsertedProduct.images = productImages
} else {
await this.productImageService_.delete(
{ product_id: upsertedProduct.id },
sharedContext
)
}
}
})
)
@@ -1506,7 +1597,7 @@ export default class ProductModuleService
await this.productService_.upsertWithReplace(
normalizedInput,
{
relations: ["images", "tags", "categories"],
relations: ["tags", "categories"],
},
sharedContext
)
@@ -1585,6 +1676,27 @@ export default class ProductModuleService
sharedContext
)
}
if (Array.isArray(product.images)) {
if (product.images.length) {
const { entities: productImages } =
await this.productImageService_.upsertWithReplace(
product.images.map((image, rank) => ({
...image,
product_id: upsertedProduct.id,
rank,
})),
{},
sharedContext
)
upsertedProduct.images = productImages
} else {
await this.productImageService_.delete(
{ product_id: upsertedProduct.id },
sharedContext
)
}
}
})
)