feat(product, types, utils): tags, types, options, categories and collections for product module (#4535)
* Feat: create product with product module * feat: create product wip * feat: create product wip * feat: update product relation and generate image migration * lint * conitnue implementation * continue implementation and add integration tests for produceService.create * Add integration tests for product creation at the module level for the complete flow * only use persist since write operations are always wrapped in a transaction which will be committed and flushed * simplify the transaction wrapper to make future changes easier * feat: move some utils to the utils package to simplify its usage * tests: fix unit tests * feat: create variants along side the product * Add more integration tests an update migrations * chore: Update actions workflow to include packages integration tests * small types and utils cleanup * chore: Add support for database debug option * chore: Add missing types in package.json from types and util, validate that all the models are sync with medusa * expose retrieve method * fix types issues * fix unit tests and move integration tests workflow with the plugins integration tests * chore: remove migration function export from the definition to prevent them to be ran by the medusa cli just in case * fix package.json script * chore: workflows * feat: start creating the create product workflow * feat: add empty step for prices and sales channel * tests: update scripts and action envs * fix imports * feat: Add proper soft deleted support + add product deletion service public api * chore: update migrations * chore: update migrations * chore: update todo * feat: Add product deletion to the create-product workflow as compensation * chore: cleanup product utils * feat: Add support for cascade soft-remove * feat: refactor repository to take into account withDeleted * fix integration tests * Add support for force delete -> delete, cleanup repositories and improvements * Add support for restoring a product and add integration tests * cleaup + tests * types * fix integration tests * remove unnecessary comments * move specific mikro orm usage to the DAL * Cleanup workflow functions * Make deleted_at optional at the property level and add url index for the images * address feedback + cleanup * fix export * merge migrations into one * feat(product, types): added missing product variant methods (#4475) * chore: added missing product variant methods * chore: address PR feedback * chore: catch undefined case for retrieve + specs for variant service * chore: align TEntity + add changeset * chore: revert changeset, TEntity to ProductVariant * chore: write tests for pagination, unskip the test * Create chilled-mice-deliver.md * update integration fixtuers * update pipeline node version * rename github action * fix pipeline * feat(medusa, types): added missing category tests and service methods (#4499) * chore: added missing category tests and service methods * chore: added type changes to module service * chore: address pr feedback * update repositories manager usage and serialisation from the write public API * move serializisation to the DAL * rename template args * chore: added collection methods for module and collection service (#4505) * chore: added collection methods for module and collection service * Create fresh-islands-teach.md * chore: move retrieve entity to utils package * chore: make products optional in DTO type --------- Co-authored-by: Oliver Windall Juhl <59018053+olivermrbl@users.noreply.github.com> * feat(product): Apply transaction decorators to the services (#4512) * chore: added tags and types methods for product module * chore: remove console log * chore: fork managers on every repository call * chore: add forked manager decorator * chore: rename to inject manager from inject forked manager * chore: added product tag and type create/update/delete * chore: added collection create/update/delete * chore: fix naming * update injectManager and related behaviour * simplify get active manager * chore: reset package.json * makes the base repository methods related to manager generic * makes some wrapper generic enough * add some todos * chore: added options CRUD * clean up * chore: fix issue with deleted_at not being updated * wip * chore: added categories write operations * chore: remove async nature of injection decorator * chore: added changeset * chore: reset package.json * chore: add manager injection to all contextable methods * chore: fix unit tests --------- Co-authored-by: adrien2p <adrien.deperetti@gmail.com> Co-authored-by: Oliver Windall Juhl <59018053+olivermrbl@users.noreply.github.com> Co-authored-by: Carlos R. L. Rodrigues <37986729+carlos-r-l-rodrigues@users.noreply.github.com>
This commit is contained in:
7
.changeset/few-cars-act.md
Normal file
7
.changeset/few-cars-act.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"@medusajs/product": patch
|
||||
"@medusajs/types": patch
|
||||
"@medusajs/utils": patch
|
||||
---
|
||||
|
||||
feat(product,types,utils): Add tags, types, categories, collection and options CRUD to product module services
|
||||
@@ -2,7 +2,7 @@ export const productCategoriesData = [
|
||||
{
|
||||
id: "category-0",
|
||||
name: "category 0",
|
||||
parent_category_id: null
|
||||
parent_category_id: null,
|
||||
},
|
||||
{
|
||||
id: "category-1",
|
||||
@@ -26,3 +26,43 @@ export const productCategoriesData = [
|
||||
parent_category_id: "category-1-b"
|
||||
},
|
||||
]
|
||||
|
||||
export const productCategoriesRankData = [
|
||||
{
|
||||
id: "category-0-0",
|
||||
name: "category 0 0",
|
||||
parent_category_id: null,
|
||||
rank: 0,
|
||||
},
|
||||
{
|
||||
id: "category-0-1",
|
||||
name: "category 0 1",
|
||||
parent_category_id: null,
|
||||
rank: 1,
|
||||
},
|
||||
{
|
||||
id: "category-0-2",
|
||||
name: "category 0 2",
|
||||
parent_category_id: null,
|
||||
rank: 2,
|
||||
},
|
||||
{
|
||||
id: "category-0-0-0",
|
||||
name: "category 0 0-0",
|
||||
parent_category_id: "category-0-0",
|
||||
rank: 0,
|
||||
},
|
||||
{
|
||||
id: "category-0-0-1",
|
||||
name: "category 0 0-1",
|
||||
parent_category_id: "category-0-0",
|
||||
rank: 1,
|
||||
},
|
||||
{
|
||||
id: "category-0-0-2",
|
||||
name: "category 0 0-2",
|
||||
parent_category_id: "category-0-0",
|
||||
rank: 2,
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
@@ -30,6 +30,24 @@ export async function createProductAndTags(
|
||||
return products
|
||||
}
|
||||
|
||||
export async function createProductAndTypes(
|
||||
manager: SqlEntityManager,
|
||||
data: {
|
||||
id?: string
|
||||
title: string
|
||||
status: ProductTypes.ProductStatus
|
||||
type?: { id: string; value: string }
|
||||
}[]
|
||||
) {
|
||||
const products: any[] = data.map((productData) => {
|
||||
return manager.create(Product, productData)
|
||||
})
|
||||
|
||||
await manager.persistAndFlush(products)
|
||||
|
||||
return products
|
||||
}
|
||||
|
||||
export async function createProductVariants(
|
||||
manager: SqlEntityManager,
|
||||
data: any[]
|
||||
|
||||
@@ -5,7 +5,7 @@ import { ProductCategoryRepository } from "@repositories"
|
||||
import { ProductCategoryService } from "@services"
|
||||
|
||||
import { createProductCategories } from "../../../__fixtures__/product-category"
|
||||
import { productCategoriesData } from "../../../__fixtures__/product-category/data"
|
||||
import { productCategoriesData, productCategoriesRankData } from "../../../__fixtures__/product-category/data"
|
||||
import { TestDatabase } from "../../../utils"
|
||||
|
||||
jest.setTimeout(30000)
|
||||
@@ -24,7 +24,7 @@ describe("Product category Service", () => {
|
||||
manager: repositoryManager,
|
||||
})
|
||||
|
||||
service = new ProductCategoryService({
|
||||
service = new ProductCategoryService<ProductCategory>({
|
||||
productCategoryRepository,
|
||||
})
|
||||
})
|
||||
@@ -560,4 +560,287 @@ describe("Product category Service", () => {
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe("create", () => {
|
||||
it("should create a category successfully", async () => {
|
||||
await service.create({
|
||||
name: "New Category",
|
||||
parent_category_id: null,
|
||||
})
|
||||
|
||||
const [productCategory] = await service.list({
|
||||
name: "New Category"
|
||||
}, {
|
||||
select: ["name", "rank"]
|
||||
})
|
||||
|
||||
expect(productCategory).toEqual(
|
||||
expect.objectContaining({
|
||||
name: "New Category",
|
||||
rank: "0",
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it("should append rank from an existing category depending on parent", async () => {
|
||||
await service.create({
|
||||
name: "New Category",
|
||||
parent_category_id: null,
|
||||
rank: 0
|
||||
})
|
||||
|
||||
await service.create({
|
||||
name: "New Category 2",
|
||||
parent_category_id: null,
|
||||
})
|
||||
|
||||
const [productCategoryNew] = await service.list({
|
||||
name: "New Category 2"
|
||||
}, {
|
||||
select: ["name", "rank"]
|
||||
})
|
||||
|
||||
expect(productCategoryNew).toEqual(
|
||||
expect.objectContaining({
|
||||
name: "New Category 2",
|
||||
rank: "1",
|
||||
})
|
||||
)
|
||||
|
||||
await service.create({
|
||||
name: "New Category 2.1",
|
||||
parent_category_id: productCategoryNew.id,
|
||||
})
|
||||
|
||||
const [productCategoryWithParent] = await service.list({
|
||||
name: "New Category 2.1"
|
||||
}, {
|
||||
select: ["name", "rank", "parent_category_id"]
|
||||
})
|
||||
|
||||
expect(productCategoryWithParent).toEqual(
|
||||
expect.objectContaining({
|
||||
name: "New Category 2.1",
|
||||
parent_category_id: productCategoryNew.id,
|
||||
rank: "0",
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("update", () => {
|
||||
let productCategoryZero
|
||||
let productCategoryOne
|
||||
let productCategoryTwo
|
||||
let productCategoryZeroZero
|
||||
let productCategoryZeroOne
|
||||
let productCategoryZeroTwo
|
||||
let categories
|
||||
|
||||
beforeEach(async () => {
|
||||
testManager = await TestDatabase.forkManager()
|
||||
|
||||
categories = await createProductCategories(
|
||||
testManager,
|
||||
productCategoriesRankData
|
||||
)
|
||||
|
||||
productCategoryZero = categories[0]
|
||||
productCategoryOne = categories[1]
|
||||
productCategoryTwo = categories[2]
|
||||
productCategoryZeroZero = categories[3]
|
||||
productCategoryZeroOne = categories[4]
|
||||
productCategoryZeroTwo = categories[5]
|
||||
})
|
||||
|
||||
it("should update the name of the category successfully", async () => {
|
||||
await service.update(productCategoryZero.id, {
|
||||
name: "New Category"
|
||||
})
|
||||
|
||||
const productCategory = await service.retrieve(productCategoryZero.id, {
|
||||
select: ["name"]
|
||||
})
|
||||
|
||||
expect(productCategory.name).toEqual("New Category")
|
||||
})
|
||||
|
||||
it("should throw an error when an id does not exist", async () => {
|
||||
let error
|
||||
|
||||
try {
|
||||
await service.update("does-not-exist", {
|
||||
name: "New Category"
|
||||
})
|
||||
} catch (e) {
|
||||
error = e
|
||||
}
|
||||
|
||||
expect(error.message).toEqual(`ProductCategory not found ({ id: 'does-not-exist' })`)
|
||||
})
|
||||
|
||||
it("should reorder rank successfully in the same parent", async () => {
|
||||
await service.update(productCategoryTwo.id, {
|
||||
rank: 0,
|
||||
})
|
||||
|
||||
const productCategories = await service.list({
|
||||
parent_category_id: null
|
||||
}, {
|
||||
select: ["name", "rank"]
|
||||
})
|
||||
|
||||
expect(productCategories).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: productCategoryTwo.id,
|
||||
rank: "0",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: productCategoryZero.id,
|
||||
rank: "1",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: productCategoryOne.id,
|
||||
rank: "2",
|
||||
})
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
it("should reorder rank successfully when changing parent", async () => {
|
||||
await service.update(productCategoryTwo.id, {
|
||||
rank: 0,
|
||||
parent_category_id: productCategoryZero.id
|
||||
})
|
||||
|
||||
const productCategories = await service.list({
|
||||
parent_category_id: productCategoryZero.id
|
||||
}, {
|
||||
select: ["name", "rank"]
|
||||
})
|
||||
|
||||
expect(productCategories).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: productCategoryTwo.id,
|
||||
rank: "0",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: productCategoryZeroZero.id,
|
||||
rank: "1",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: productCategoryZeroOne.id,
|
||||
rank: "2",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: productCategoryZeroTwo.id,
|
||||
rank: "3",
|
||||
})
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
it("should reorder rank successfully when changing parent and in first position", async () => {
|
||||
await service.update(productCategoryTwo.id, {
|
||||
rank: 0,
|
||||
parent_category_id: productCategoryZero.id
|
||||
})
|
||||
|
||||
const productCategories = await service.list({
|
||||
parent_category_id: productCategoryZero.id
|
||||
}, {
|
||||
select: ["name", "rank"]
|
||||
})
|
||||
|
||||
expect(productCategories).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: productCategoryTwo.id,
|
||||
rank: "0",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: productCategoryZeroZero.id,
|
||||
rank: "1",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: productCategoryZeroOne.id,
|
||||
rank: "2",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: productCategoryZeroTwo.id,
|
||||
rank: "3",
|
||||
})
|
||||
])
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("delete", () => {
|
||||
let productCategoryZero
|
||||
let productCategoryOne
|
||||
let productCategoryTwo
|
||||
let categories
|
||||
|
||||
beforeEach(async () => {
|
||||
testManager = await TestDatabase.forkManager()
|
||||
|
||||
categories = await createProductCategories(
|
||||
testManager,
|
||||
productCategoriesRankData
|
||||
)
|
||||
|
||||
productCategoryZero = categories[0]
|
||||
productCategoryOne = categories[1]
|
||||
productCategoryTwo = categories[2]
|
||||
})
|
||||
|
||||
it("should throw an error when an id does not exist", async () => {
|
||||
let error
|
||||
|
||||
try {
|
||||
await service.delete("does-not-exist")
|
||||
} catch (e) {
|
||||
error = e
|
||||
}
|
||||
|
||||
expect(error.message).toEqual(`ProductCategory not found ({ id: 'does-not-exist' })`)
|
||||
})
|
||||
|
||||
it("should throw an error when it has children", async () => {
|
||||
let error
|
||||
|
||||
try {
|
||||
await service.delete(productCategoryZero.id)
|
||||
} catch (e) {
|
||||
error = e
|
||||
}
|
||||
|
||||
expect(error.message).toEqual(`Deleting ProductCategory (category-0-0) with category children is not allowed`)
|
||||
})
|
||||
|
||||
it("should reorder siblings rank successfully on deleting", async () => {
|
||||
await service.delete(productCategoryOne.id)
|
||||
|
||||
const productCategories = await service.list({
|
||||
parent_category_id: null
|
||||
}, {
|
||||
select: ["id", "rank"]
|
||||
})
|
||||
|
||||
expect(productCategories).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: productCategoryZero.id,
|
||||
rank: "0",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: productCategoryTwo.id,
|
||||
rank: "1",
|
||||
})
|
||||
])
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -273,4 +273,90 @@ describe("Product collection Service", () => {
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("delete", () => {
|
||||
const collectionId = "collection-1"
|
||||
const collectionData = {
|
||||
id: collectionId,
|
||||
title: "collection 1",
|
||||
}
|
||||
|
||||
beforeEach(async () => {
|
||||
testManager = await TestDatabase.forkManager()
|
||||
|
||||
await createCollections(testManager, [collectionData])
|
||||
})
|
||||
|
||||
it("should delete the product collection given an ID successfully", async () => {
|
||||
await service.delete(
|
||||
[collectionId],
|
||||
)
|
||||
|
||||
const collections = await service.list({
|
||||
id: collectionId
|
||||
})
|
||||
|
||||
expect(collections).toHaveLength(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe("update", () => {
|
||||
const collectionId = "collection-1"
|
||||
const collectionData = {
|
||||
id: collectionId,
|
||||
title: "collection 1",
|
||||
}
|
||||
|
||||
beforeEach(async () => {
|
||||
testManager = await TestDatabase.forkManager()
|
||||
|
||||
await createCollections(testManager, [collectionData])
|
||||
})
|
||||
|
||||
it("should update the value of the collection successfully", async () => {
|
||||
await service.update(
|
||||
[{
|
||||
id: collectionId,
|
||||
title: "New Collection"
|
||||
}]
|
||||
)
|
||||
|
||||
const productCollection = await service.retrieve(collectionId)
|
||||
|
||||
expect(productCollection.title).toEqual("New Collection")
|
||||
})
|
||||
|
||||
it("should throw an error when an id does not exist", async () => {
|
||||
let error
|
||||
|
||||
try {
|
||||
await service.update([
|
||||
{
|
||||
id: "does-not-exist",
|
||||
title: "New Collection"
|
||||
}
|
||||
])
|
||||
} catch (e) {
|
||||
error = e
|
||||
}
|
||||
|
||||
expect(error.message).toEqual('ProductCollection with id "does-not-exist" not found')
|
||||
})
|
||||
})
|
||||
|
||||
describe("create", () => {
|
||||
it("should create a collection successfully", async () => {
|
||||
await service.create(
|
||||
[{
|
||||
title: "New Collection"
|
||||
}]
|
||||
)
|
||||
|
||||
const [productCollection] = await service.list({
|
||||
title: "New Collection"
|
||||
})
|
||||
|
||||
expect(productCollection.title).toEqual("New Collection")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -6,6 +6,7 @@ import { ProductTypes } from "@medusajs/types"
|
||||
import { initialize } from "../../../../src"
|
||||
import { DB_URL, TestDatabase } from "../../../utils"
|
||||
import { createProductCategories } from "../../../__fixtures__/product-category"
|
||||
import { productCategoriesRankData } from "../../../__fixtures__/product-category/data"
|
||||
|
||||
describe("ProductModuleService product categories", () => {
|
||||
let service: IProductModuleService
|
||||
@@ -201,7 +202,9 @@ describe("ProductModuleService product categories", () => {
|
||||
|
||||
describe("retrieveCategory", () => {
|
||||
it("should return the requested category", async () => {
|
||||
const result = await service.retrieveCategory(productCategoryOne.id)
|
||||
const result = await service.retrieveCategory(productCategoryOne.id, {
|
||||
select: ["id", "name"]
|
||||
})
|
||||
|
||||
expect(result).toEqual(
|
||||
expect.objectContaining({
|
||||
@@ -244,5 +247,288 @@ describe("ProductModuleService product categories", () => {
|
||||
expect(error.message).toEqual("ProductCategory with id: does-not-exist was not found")
|
||||
})
|
||||
})
|
||||
|
||||
describe("createCategory", () => {
|
||||
it("should create a category successfully", async () => {
|
||||
await service.createCategory({
|
||||
name: "New Category",
|
||||
parent_category_id: productCategoryOne.id,
|
||||
})
|
||||
|
||||
const [productCategory] = await service.listCategories({
|
||||
name: "New Category"
|
||||
}, {
|
||||
select: ["name", "rank"]
|
||||
})
|
||||
|
||||
expect(productCategory).toEqual(
|
||||
expect.objectContaining({
|
||||
name: "New Category",
|
||||
rank: "0",
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it("should append rank from an existing category depending on parent", async () => {
|
||||
await service.createCategory({
|
||||
name: "New Category",
|
||||
parent_category_id: productCategoryOne.id,
|
||||
rank: 0
|
||||
})
|
||||
|
||||
await service.createCategory({
|
||||
name: "New Category 2",
|
||||
parent_category_id: productCategoryOne.id,
|
||||
})
|
||||
|
||||
const [productCategoryNew] = await service.listCategories({
|
||||
name: "New Category 2"
|
||||
}, {
|
||||
select: ["name", "rank"]
|
||||
})
|
||||
|
||||
expect(productCategoryNew).toEqual(
|
||||
expect.objectContaining({
|
||||
name: "New Category 2",
|
||||
rank: "1",
|
||||
})
|
||||
)
|
||||
|
||||
await service.createCategory({
|
||||
name: "New Category 2.1",
|
||||
parent_category_id: productCategoryNew.id,
|
||||
})
|
||||
|
||||
const [productCategoryWithParent] = await service.listCategories({
|
||||
name: "New Category 2.1"
|
||||
}, {
|
||||
select: ["name", "rank", "parent_category_id"]
|
||||
})
|
||||
|
||||
expect(productCategoryWithParent).toEqual(
|
||||
expect.objectContaining({
|
||||
name: "New Category 2.1",
|
||||
parent_category_id: productCategoryNew.id,
|
||||
rank: "0",
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("updateCategory", () => {
|
||||
let productCategoryZero
|
||||
let productCategoryOne
|
||||
let productCategoryTwo
|
||||
let productCategoryZeroZero
|
||||
let productCategoryZeroOne
|
||||
let productCategoryZeroTwo
|
||||
let categories
|
||||
|
||||
beforeEach(async () => {
|
||||
testManager = await TestDatabase.forkManager()
|
||||
|
||||
categories = await createProductCategories(
|
||||
testManager,
|
||||
productCategoriesRankData
|
||||
)
|
||||
|
||||
productCategoryZero = categories[0]
|
||||
productCategoryOne = categories[1]
|
||||
productCategoryTwo = categories[2]
|
||||
productCategoryZeroZero = categories[3]
|
||||
productCategoryZeroOne = categories[4]
|
||||
productCategoryZeroTwo = categories[5]
|
||||
})
|
||||
|
||||
it("should update the name of the category successfully", async () => {
|
||||
await service.updateCategory(productCategoryZero.id, {
|
||||
name: "New Category"
|
||||
})
|
||||
|
||||
const productCategory = await service.retrieveCategory(productCategoryZero.id, {
|
||||
select: ["name"]
|
||||
})
|
||||
|
||||
expect(productCategory.name).toEqual("New Category")
|
||||
})
|
||||
|
||||
it("should throw an error when an id does not exist", async () => {
|
||||
let error
|
||||
|
||||
try {
|
||||
await service.updateCategory("does-not-exist", {
|
||||
name: "New Category"
|
||||
})
|
||||
} catch (e) {
|
||||
error = e
|
||||
}
|
||||
|
||||
expect(error.message).toEqual(`ProductCategory not found ({ id: 'does-not-exist' })`)
|
||||
})
|
||||
|
||||
it("should reorder rank successfully in the same parent", async () => {
|
||||
await service.updateCategory(productCategoryTwo.id, {
|
||||
rank: 0,
|
||||
})
|
||||
|
||||
const productCategories = await service.listCategories({
|
||||
parent_category_id: null
|
||||
}, {
|
||||
select: ["name", "rank"]
|
||||
})
|
||||
|
||||
expect(productCategories).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: productCategoryTwo.id,
|
||||
rank: "0",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: productCategoryZero.id,
|
||||
rank: "1",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: productCategoryOne.id,
|
||||
rank: "2",
|
||||
})
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
it("should reorder rank successfully when changing parent", async () => {
|
||||
await service.updateCategory(productCategoryTwo.id, {
|
||||
rank: 0,
|
||||
parent_category_id: productCategoryZero.id
|
||||
})
|
||||
|
||||
const productCategories = await service.listCategories({
|
||||
parent_category_id: productCategoryZero.id
|
||||
}, {
|
||||
select: ["name", "rank"]
|
||||
})
|
||||
|
||||
expect(productCategories).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: productCategoryTwo.id,
|
||||
rank: "0",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: productCategoryZeroZero.id,
|
||||
rank: "1",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: productCategoryZeroOne.id,
|
||||
rank: "2",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: productCategoryZeroTwo.id,
|
||||
rank: "3",
|
||||
})
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
it("should reorder rank successfully when changing parent and in first position", async () => {
|
||||
await service.updateCategory(productCategoryTwo.id, {
|
||||
rank: 0,
|
||||
parent_category_id: productCategoryZero.id
|
||||
})
|
||||
|
||||
const productCategories = await service.listCategories({
|
||||
parent_category_id: productCategoryZero.id
|
||||
}, {
|
||||
select: ["name", "rank"]
|
||||
})
|
||||
|
||||
expect(productCategories).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: productCategoryTwo.id,
|
||||
rank: "0",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: productCategoryZeroZero.id,
|
||||
rank: "1",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: productCategoryZeroOne.id,
|
||||
rank: "2",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: productCategoryZeroTwo.id,
|
||||
rank: "3",
|
||||
})
|
||||
])
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("deleteCategory", () => {
|
||||
let productCategoryZero
|
||||
let productCategoryOne
|
||||
let productCategoryTwo
|
||||
let categories
|
||||
|
||||
beforeEach(async () => {
|
||||
testManager = await TestDatabase.forkManager()
|
||||
|
||||
categories = await createProductCategories(
|
||||
testManager,
|
||||
productCategoriesRankData
|
||||
)
|
||||
|
||||
productCategoryZero = categories[0]
|
||||
productCategoryOne = categories[1]
|
||||
productCategoryTwo = categories[2]
|
||||
})
|
||||
|
||||
it("should throw an error when an id does not exist", async () => {
|
||||
let error
|
||||
|
||||
try {
|
||||
await service.deleteCategory("does-not-exist")
|
||||
} catch (e) {
|
||||
error = e
|
||||
}
|
||||
|
||||
expect(error.message).toEqual(`ProductCategory not found ({ id: 'does-not-exist' })`)
|
||||
})
|
||||
|
||||
it("should throw an error when it has children", async () => {
|
||||
let error
|
||||
|
||||
try {
|
||||
await service.deleteCategory(productCategoryZero.id)
|
||||
} catch (e) {
|
||||
error = e
|
||||
}
|
||||
|
||||
expect(error.message).toEqual(`Deleting ProductCategory (category-0-0) with category children is not allowed`)
|
||||
})
|
||||
|
||||
it("should reorder siblings rank successfully on deleting", async () => {
|
||||
await service.deleteCategory(productCategoryOne.id)
|
||||
|
||||
const productCategories = await service.listCategories({
|
||||
parent_category_id: null
|
||||
}, {
|
||||
select: ["id", "rank"]
|
||||
})
|
||||
|
||||
expect(productCategories).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: productCategoryZero.id,
|
||||
rank: "0",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: productCategoryTwo.id,
|
||||
rank: "1",
|
||||
})
|
||||
])
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -246,5 +246,71 @@ describe("ProductModuleService product collections", () => {
|
||||
expect(error.message).toEqual("ProductCollection with id: does-not-exist was not found")
|
||||
})
|
||||
})
|
||||
|
||||
describe("deleteCollections", () => {
|
||||
const collectionId = "test-1"
|
||||
|
||||
it("should delete the product collection given an ID successfully", async () => {
|
||||
await service.deleteCollections(
|
||||
[collectionId],
|
||||
)
|
||||
|
||||
const collections = await service.listCollections({
|
||||
id: collectionId
|
||||
})
|
||||
|
||||
expect(collections).toHaveLength(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe("updateCollections", () => {
|
||||
const collectionId = "test-1"
|
||||
|
||||
it("should update the value of the collection successfully", async () => {
|
||||
await service.updateCollections(
|
||||
[{
|
||||
id: collectionId,
|
||||
title: "New Collection"
|
||||
}]
|
||||
)
|
||||
|
||||
const productCollection = await service.retrieveCollection(collectionId)
|
||||
|
||||
expect(productCollection.title).toEqual("New Collection")
|
||||
})
|
||||
|
||||
it("should throw an error when an id does not exist", async () => {
|
||||
let error
|
||||
|
||||
try {
|
||||
await service.updateCollections([
|
||||
{
|
||||
id: "does-not-exist",
|
||||
title: "New Collection"
|
||||
}
|
||||
])
|
||||
} catch (e) {
|
||||
error = e
|
||||
}
|
||||
|
||||
expect(error.message).toEqual('ProductCollection with id "does-not-exist" not found')
|
||||
})
|
||||
})
|
||||
|
||||
describe("createCollections", () => {
|
||||
it("should create a collection successfully", async () => {
|
||||
const res = await service.createCollections(
|
||||
[{
|
||||
title: "New Collection"
|
||||
}]
|
||||
)
|
||||
|
||||
const [productCollection] = await service.listCollections({
|
||||
title: "New Collection"
|
||||
})
|
||||
|
||||
expect(productCollection.title).toEqual("New Collection")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -0,0 +1,300 @@
|
||||
import { initialize } from "../../../../src"
|
||||
import { DB_URL, TestDatabase } from "../../../utils"
|
||||
import { IProductModuleService } from "@medusajs/types"
|
||||
import { Product, ProductOption } from "@models"
|
||||
import { SqlEntityManager } from "@mikro-orm/postgresql"
|
||||
import { ProductTypes } from "@medusajs/types"
|
||||
|
||||
describe("ProductModuleService product options", () => {
|
||||
let service: IProductModuleService
|
||||
let testManager: SqlEntityManager
|
||||
let repositoryManager: SqlEntityManager
|
||||
let optionOne: ProductOption
|
||||
let optionTwo: ProductOption
|
||||
let productOne: Product
|
||||
let productTwo: Product
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestDatabase.setupDatabase()
|
||||
repositoryManager = await TestDatabase.forkManager()
|
||||
|
||||
service = await initialize({
|
||||
database: {
|
||||
clientUrl: DB_URL,
|
||||
schema: process.env.MEDUSA_PRODUCT_DB_SCHEMA,
|
||||
},
|
||||
})
|
||||
|
||||
testManager = await TestDatabase.forkManager()
|
||||
productOne = testManager.create(Product, {
|
||||
id: "product-1",
|
||||
title: "product 1",
|
||||
status: ProductTypes.ProductStatus.PUBLISHED,
|
||||
})
|
||||
|
||||
productTwo = testManager.create(Product, {
|
||||
id: "product-2",
|
||||
title: "product 2",
|
||||
status: ProductTypes.ProductStatus.PUBLISHED,
|
||||
})
|
||||
|
||||
optionOne = testManager.create(ProductOption, {
|
||||
id: "option-1",
|
||||
title: "option 1",
|
||||
product: productOne,
|
||||
})
|
||||
|
||||
optionTwo = testManager.create(ProductOption, {
|
||||
id: "option-2",
|
||||
title: "option 1",
|
||||
product: productTwo,
|
||||
})
|
||||
|
||||
await testManager.persistAndFlush([optionOne, optionTwo])
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
await TestDatabase.clearDatabase()
|
||||
})
|
||||
|
||||
describe("listOptions", () => {
|
||||
it("should return options and count queried by ID", async () => {
|
||||
const options = await service.listOptions({
|
||||
id: optionOne.id,
|
||||
})
|
||||
|
||||
expect(options).toEqual([
|
||||
expect.objectContaining({
|
||||
id: optionOne.id,
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it("should return options and count based on the options and filter parameter", async () => {
|
||||
let options = await service.listOptions(
|
||||
{
|
||||
id: optionOne.id,
|
||||
},
|
||||
{
|
||||
take: 1,
|
||||
}
|
||||
)
|
||||
|
||||
expect(options).toEqual([
|
||||
expect.objectContaining({
|
||||
id: optionOne.id,
|
||||
}),
|
||||
])
|
||||
|
||||
options = await service.listOptions({}, { take: 1, skip: 1 })
|
||||
|
||||
expect(options).toEqual([
|
||||
expect.objectContaining({
|
||||
id: optionTwo.id,
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it("should return only requested fields and relations for options", async () => {
|
||||
const options = await service.listOptions(
|
||||
{
|
||||
id: optionOne.id,
|
||||
},
|
||||
{
|
||||
select: ["title", "product.id"],
|
||||
relations: ["product"],
|
||||
take: 1
|
||||
}
|
||||
)
|
||||
|
||||
expect(options).toEqual([
|
||||
{
|
||||
id: optionOne.id,
|
||||
title: optionOne.title,
|
||||
product_id: productOne.id,
|
||||
product: {
|
||||
id: productOne.id,
|
||||
},
|
||||
},
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe("listAndCountOptions", () => {
|
||||
it("should return options and count queried by ID", async () => {
|
||||
const [options, count] = await service.listAndCountOptions({
|
||||
id: optionOne.id,
|
||||
})
|
||||
|
||||
expect(count).toEqual(1)
|
||||
expect(options).toEqual([
|
||||
expect.objectContaining({
|
||||
id: optionOne.id,
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it("should return options and count based on the options and filter parameter", async () => {
|
||||
let [options, count] = await service.listAndCountOptions(
|
||||
{
|
||||
id: optionOne.id,
|
||||
},
|
||||
{
|
||||
take: 1,
|
||||
}
|
||||
)
|
||||
|
||||
expect(count).toEqual(1)
|
||||
expect(options).toEqual([
|
||||
expect.objectContaining({
|
||||
id: optionOne.id,
|
||||
}),
|
||||
])
|
||||
|
||||
;[options, count] = await service.listAndCountOptions({}, { take: 1 })
|
||||
|
||||
expect(count).toEqual(2)
|
||||
|
||||
;[options, count] = await service.listAndCountOptions({}, { take: 1, skip: 1 })
|
||||
|
||||
expect(count).toEqual(2)
|
||||
expect(options).toEqual([
|
||||
expect.objectContaining({
|
||||
id: optionTwo.id,
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it("should return only requested fields and relations for options", async () => {
|
||||
const [options, count] = await service.listAndCountOptions(
|
||||
{
|
||||
id: optionOne.id,
|
||||
},
|
||||
{
|
||||
select: ["title", "product.id"],
|
||||
relations: ["product"],
|
||||
take: 1
|
||||
}
|
||||
)
|
||||
|
||||
expect(count).toEqual(1)
|
||||
expect(options).toEqual([{
|
||||
id: optionOne.id,
|
||||
title: optionOne.title,
|
||||
product_id: productOne.id,
|
||||
product: {
|
||||
id: productOne.id,
|
||||
},
|
||||
}])
|
||||
})
|
||||
})
|
||||
|
||||
describe("retrieveOption", () => {
|
||||
it("should return the requested option", async () => {
|
||||
const option = await service.retrieveOption(optionOne.id)
|
||||
|
||||
expect(option).toEqual(
|
||||
expect.objectContaining({
|
||||
id: optionOne.id,
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it("should return requested attributes when requested through config", async () => {
|
||||
const option = await service.retrieveOption(
|
||||
optionOne.id,
|
||||
{
|
||||
select: ["id", "product.title"],
|
||||
relations: ["product"],
|
||||
}
|
||||
)
|
||||
|
||||
expect(option).toEqual(
|
||||
expect.objectContaining({
|
||||
id: optionOne.id,
|
||||
product: {
|
||||
id: "product-1",
|
||||
title: "product 1",
|
||||
},
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it("should throw an error when a option with ID does not exist", async () => {
|
||||
let error
|
||||
|
||||
try {
|
||||
await service.retrieveOption("does-not-exist")
|
||||
} catch (e) {
|
||||
error = e
|
||||
}
|
||||
|
||||
expect(error.message).toEqual("ProductOption with id: does-not-exist was not found")
|
||||
})
|
||||
})
|
||||
|
||||
describe("deleteOptions", () => {
|
||||
const optionId = "option-1"
|
||||
|
||||
it("should delete the product option given an ID successfully", async () => {
|
||||
await service.deleteOptions(
|
||||
[optionId],
|
||||
)
|
||||
|
||||
const options = await service.listOptions({
|
||||
id: optionId
|
||||
})
|
||||
|
||||
expect(options).toHaveLength(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe("updateOptions", () => {
|
||||
const optionId = "option-1"
|
||||
|
||||
it("should update the title of the option successfully", async () => {
|
||||
await service.updateOptions(
|
||||
[{
|
||||
id: optionId,
|
||||
title: "new test"
|
||||
}]
|
||||
)
|
||||
|
||||
const productOption = await service.retrieveOption(optionId)
|
||||
|
||||
expect(productOption.title).toEqual("new test")
|
||||
})
|
||||
|
||||
it("should throw an error when an id does not exist", async () => {
|
||||
let error
|
||||
|
||||
try {
|
||||
await service.updateOptions([
|
||||
{
|
||||
id: "does-not-exist",
|
||||
}
|
||||
])
|
||||
} catch (e) {
|
||||
error = e
|
||||
}
|
||||
|
||||
expect(error.message).toEqual('ProductOption with id "does-not-exist" not found')
|
||||
})
|
||||
})
|
||||
|
||||
describe("createOptions", () => {
|
||||
it("should create a option successfully", async () => {
|
||||
const res = await service.createOptions([{
|
||||
title: "test",
|
||||
product_id: productOne.id
|
||||
}])
|
||||
|
||||
const productOption = await service.listOptions({
|
||||
title: "test"
|
||||
})
|
||||
|
||||
expect(productOption[0]?.title).toEqual("test")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -0,0 +1,303 @@
|
||||
import { initialize } from "../../../../src"
|
||||
import { DB_URL, TestDatabase } from "../../../utils"
|
||||
import { IProductModuleService } from "@medusajs/types"
|
||||
import { Product, ProductTag } from "@models"
|
||||
import { SqlEntityManager } from "@mikro-orm/postgresql"
|
||||
import { ProductTypes } from "@medusajs/types"
|
||||
|
||||
describe("ProductModuleService product tags", () => {
|
||||
let service: IProductModuleService
|
||||
let testManager: SqlEntityManager
|
||||
let repositoryManager: SqlEntityManager
|
||||
let tagOne: ProductTag
|
||||
let tagTwo: ProductTag
|
||||
let productOne: Product
|
||||
let productTwo: Product
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestDatabase.setupDatabase()
|
||||
repositoryManager = await TestDatabase.forkManager()
|
||||
|
||||
service = await initialize({
|
||||
database: {
|
||||
clientUrl: DB_URL,
|
||||
schema: process.env.MEDUSA_PRODUCT_DB_SCHEMA,
|
||||
},
|
||||
})
|
||||
|
||||
testManager = await TestDatabase.forkManager()
|
||||
productOne = testManager.create(Product, {
|
||||
id: "product-1",
|
||||
title: "product 1",
|
||||
status: ProductTypes.ProductStatus.PUBLISHED,
|
||||
})
|
||||
|
||||
productTwo = testManager.create(Product, {
|
||||
id: "product-2",
|
||||
title: "product 2",
|
||||
status: ProductTypes.ProductStatus.PUBLISHED,
|
||||
})
|
||||
|
||||
tagOne = testManager.create(ProductTag, {
|
||||
id: "tag-1",
|
||||
value: "tag 1",
|
||||
products: [productOne],
|
||||
})
|
||||
|
||||
tagTwo = testManager.create(ProductTag, {
|
||||
id: "tag-2",
|
||||
value: "tag",
|
||||
products: [productTwo],
|
||||
})
|
||||
|
||||
await testManager.persistAndFlush([tagOne, tagTwo])
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
await TestDatabase.clearDatabase()
|
||||
})
|
||||
|
||||
describe("listTags", () => {
|
||||
it("should return tags and count queried by ID", async () => {
|
||||
const tags = await service.listTags({
|
||||
id: tagOne.id,
|
||||
})
|
||||
|
||||
expect(tags).toEqual([
|
||||
expect.objectContaining({
|
||||
id: tagOne.id,
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it("should return tags and count based on the options and filter parameter", async () => {
|
||||
let tags = await service.listTags(
|
||||
{
|
||||
id: tagOne.id,
|
||||
},
|
||||
{
|
||||
take: 1,
|
||||
}
|
||||
)
|
||||
|
||||
expect(tags).toEqual([
|
||||
expect.objectContaining({
|
||||
id: tagOne.id,
|
||||
}),
|
||||
])
|
||||
|
||||
tags = await service.listTags({}, { take: 1, skip: 1 })
|
||||
|
||||
expect(tags).toEqual([
|
||||
expect.objectContaining({
|
||||
id: tagTwo.id,
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it("should return only requested fields and relations for tags", async () => {
|
||||
const tags = await service.listTags(
|
||||
{
|
||||
id: tagOne.id,
|
||||
},
|
||||
{
|
||||
select: ["value", "products.id"],
|
||||
relations: ["products"],
|
||||
take: 1
|
||||
}
|
||||
)
|
||||
|
||||
expect(tags).toEqual([
|
||||
{
|
||||
id: tagOne.id,
|
||||
value: tagOne.value,
|
||||
products: [{
|
||||
id: productOne.id,
|
||||
}],
|
||||
},
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe("listAndCountTags", () => {
|
||||
it("should return tags and count queried by ID", async () => {
|
||||
const [tags, count] = await service.listAndCountTags({
|
||||
id: tagOne.id,
|
||||
})
|
||||
|
||||
expect(count).toEqual(1)
|
||||
expect(tags).toEqual([
|
||||
expect.objectContaining({
|
||||
id: tagOne.id,
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it("should return tags and count based on the options and filter parameter", async () => {
|
||||
let [tags, count] = await service.listAndCountTags(
|
||||
{
|
||||
id: tagOne.id,
|
||||
},
|
||||
{
|
||||
take: 1,
|
||||
}
|
||||
)
|
||||
|
||||
expect(count).toEqual(1)
|
||||
expect(tags).toEqual([
|
||||
expect.objectContaining({
|
||||
id: tagOne.id,
|
||||
}),
|
||||
])
|
||||
|
||||
;[tags, count] = await service.listAndCountTags({}, { take: 1 })
|
||||
|
||||
expect(count).toEqual(2)
|
||||
|
||||
;[tags, count] = await service.listAndCountTags({}, { take: 1, skip: 1 })
|
||||
|
||||
expect(count).toEqual(2)
|
||||
expect(tags).toEqual([
|
||||
expect.objectContaining({
|
||||
id: tagTwo.id,
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it("should return only requested fields and relations for tags", async () => {
|
||||
const [tags, count] = await service.listAndCountTags(
|
||||
{
|
||||
id: tagOne.id,
|
||||
},
|
||||
{
|
||||
select: ["value", "products.id"],
|
||||
relations: ["products"],
|
||||
take: 1
|
||||
}
|
||||
)
|
||||
|
||||
expect(count).toEqual(1)
|
||||
expect(tags).toEqual([
|
||||
{
|
||||
id: tagOne.id,
|
||||
value: tagOne.value,
|
||||
products: [{
|
||||
id: productOne.id,
|
||||
}],
|
||||
},
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe("retrieveTag", () => {
|
||||
it("should return the requested tag", async () => {
|
||||
const tag = await service.retrieveTag(tagOne.id)
|
||||
|
||||
expect(tag).toEqual(
|
||||
expect.objectContaining({
|
||||
id: tagOne.id,
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it("should return requested attributes when requested through config", async () => {
|
||||
const tag = await service.retrieveTag(
|
||||
tagOne.id,
|
||||
{
|
||||
select: ["id", "value", "products.title"],
|
||||
relations: ["products"],
|
||||
}
|
||||
)
|
||||
|
||||
expect(tag).toEqual(
|
||||
expect.objectContaining({
|
||||
id: tagOne.id,
|
||||
value: tagOne.value,
|
||||
products: [{
|
||||
id: "product-1",
|
||||
title: "product 1",
|
||||
}],
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it("should throw an error when a tag with ID does not exist", async () => {
|
||||
let error
|
||||
|
||||
try {
|
||||
await service.retrieveTag("does-not-exist")
|
||||
} catch (e) {
|
||||
error = e
|
||||
}
|
||||
|
||||
expect(error.message).toEqual("ProductTag with id: does-not-exist was not found")
|
||||
})
|
||||
})
|
||||
|
||||
describe("deleteTags", () => {
|
||||
const tagId = "tag-1"
|
||||
|
||||
it("should delete the product tag given an ID successfully", async () => {
|
||||
await service.deleteTags(
|
||||
[tagId],
|
||||
)
|
||||
|
||||
const tags = await service.listTags({
|
||||
id: tagId
|
||||
})
|
||||
|
||||
expect(tags).toHaveLength(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe("updateTags", () => {
|
||||
const tagId = "tag-1"
|
||||
|
||||
it("should update the value of the tag successfully", async () => {
|
||||
await service.updateTags(
|
||||
[{
|
||||
id: tagId,
|
||||
value: "UK"
|
||||
}]
|
||||
)
|
||||
|
||||
const productTag = await service.retrieveTag(tagId)
|
||||
|
||||
expect(productTag.value).toEqual("UK")
|
||||
})
|
||||
|
||||
it("should throw an error when an id does not exist", async () => {
|
||||
let error
|
||||
|
||||
try {
|
||||
await service.updateTags([
|
||||
{
|
||||
id: "does-not-exist",
|
||||
value: "UK"
|
||||
}
|
||||
])
|
||||
} catch (e) {
|
||||
error = e
|
||||
}
|
||||
|
||||
expect(error.message).toEqual('ProductTag with id "does-not-exist" not found')
|
||||
})
|
||||
})
|
||||
|
||||
describe("createTags", () => {
|
||||
it("should create a tag successfully", async () => {
|
||||
const res = await service.createTags(
|
||||
[{
|
||||
value: "UK"
|
||||
}]
|
||||
)
|
||||
|
||||
const productTag = await service.listTags({
|
||||
value: "UK"
|
||||
})
|
||||
|
||||
expect(productTag[0]?.value).toEqual("UK")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -0,0 +1,275 @@
|
||||
import { initialize } from "../../../../src"
|
||||
import { DB_URL, TestDatabase } from "../../../utils"
|
||||
import { IProductModuleService } from "@medusajs/types"
|
||||
import { ProductType } from "@models"
|
||||
import { SqlEntityManager } from "@mikro-orm/postgresql"
|
||||
import { ProductTypes } from "@medusajs/types"
|
||||
|
||||
describe("ProductModuleService product types", () => {
|
||||
let service: IProductModuleService
|
||||
let testManager: SqlEntityManager
|
||||
let repositoryManager: SqlEntityManager
|
||||
let typeOne: ProductType
|
||||
let typeTwo: ProductType
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestDatabase.setupDatabase()
|
||||
repositoryManager = await TestDatabase.forkManager()
|
||||
|
||||
service = await initialize({
|
||||
database: {
|
||||
clientUrl: DB_URL,
|
||||
schema: process.env.MEDUSA_PRODUCT_DB_SCHEMA,
|
||||
},
|
||||
})
|
||||
|
||||
testManager = await TestDatabase.forkManager()
|
||||
|
||||
typeOne = testManager.create(ProductType, {
|
||||
id: "type-1",
|
||||
value: "type 1",
|
||||
})
|
||||
|
||||
typeTwo = testManager.create(ProductType, {
|
||||
id: "type-2",
|
||||
value: "type",
|
||||
})
|
||||
|
||||
await testManager.persistAndFlush([typeOne, typeTwo])
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
await TestDatabase.clearDatabase()
|
||||
})
|
||||
|
||||
describe("listTypes", () => {
|
||||
it("should return types and count queried by ID", async () => {
|
||||
const types = await service.listTypes({
|
||||
id: typeOne.id,
|
||||
})
|
||||
|
||||
expect(types).toEqual([
|
||||
expect.objectContaining({
|
||||
id: typeOne.id,
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it("should return types and count based on the options and filter parameter", async () => {
|
||||
let types = await service.listTypes(
|
||||
{
|
||||
id: typeOne.id,
|
||||
},
|
||||
{
|
||||
take: 1,
|
||||
}
|
||||
)
|
||||
|
||||
expect(types).toEqual([
|
||||
expect.objectContaining({
|
||||
id: typeOne.id,
|
||||
}),
|
||||
])
|
||||
|
||||
types = await service.listTypes({}, { take: 1, skip: 1 })
|
||||
|
||||
expect(types).toEqual([
|
||||
expect.objectContaining({
|
||||
id: typeTwo.id,
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it("should return only requested fields for types", async () => {
|
||||
const types = await service.listTypes(
|
||||
{
|
||||
id: typeOne.id,
|
||||
},
|
||||
{
|
||||
select: ["value"],
|
||||
take: 1
|
||||
}
|
||||
)
|
||||
|
||||
expect(types).toEqual([
|
||||
{
|
||||
id: typeOne.id,
|
||||
value: typeOne.value,
|
||||
},
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe("listAndCountTypes", () => {
|
||||
it("should return types and count queried by ID", async () => {
|
||||
const [types, count] = await service.listAndCountTypes({
|
||||
id: typeOne.id,
|
||||
})
|
||||
|
||||
expect(count).toEqual(1)
|
||||
expect(types).toEqual([
|
||||
expect.objectContaining({
|
||||
id: typeOne.id,
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it("should return types and count based on the options and filter parameter", async () => {
|
||||
let [types, count] = await service.listAndCountTypes(
|
||||
{
|
||||
id: typeOne.id,
|
||||
},
|
||||
{
|
||||
take: 1,
|
||||
}
|
||||
)
|
||||
|
||||
expect(count).toEqual(1)
|
||||
expect(types).toEqual([
|
||||
expect.objectContaining({
|
||||
id: typeOne.id,
|
||||
}),
|
||||
])
|
||||
|
||||
;[types, count] = await service.listAndCountTypes({}, { take: 1 })
|
||||
|
||||
expect(count).toEqual(2)
|
||||
|
||||
;[types, count] = await service.listAndCountTypes({}, { take: 1, skip: 1 })
|
||||
|
||||
expect(count).toEqual(2)
|
||||
expect(types).toEqual([
|
||||
expect.objectContaining({
|
||||
id: typeTwo.id,
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it("should return only requested fields for types", async () => {
|
||||
const [types, count] = await service.listAndCountTypes(
|
||||
{
|
||||
id: typeOne.id,
|
||||
},
|
||||
{
|
||||
select: ["value"],
|
||||
take: 1
|
||||
}
|
||||
)
|
||||
|
||||
expect(count).toEqual(1)
|
||||
expect(types).toEqual([
|
||||
{
|
||||
id: typeOne.id,
|
||||
value: typeOne.value,
|
||||
},
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe("retrieveType", () => {
|
||||
it("should return the requested type", async () => {
|
||||
const type = await service.retrieveType(typeOne.id)
|
||||
|
||||
expect(type).toEqual(
|
||||
expect.objectContaining({
|
||||
id: typeOne.id,
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it("should return requested attributes when requested through config", async () => {
|
||||
const type = await service.retrieveType(
|
||||
typeOne.id,
|
||||
{
|
||||
select: ["id", "value"],
|
||||
}
|
||||
)
|
||||
|
||||
expect(type).toEqual(
|
||||
{
|
||||
id: typeOne.id,
|
||||
value: typeOne.value,
|
||||
},
|
||||
)
|
||||
})
|
||||
|
||||
it("should throw an error when a type with ID does not exist", async () => {
|
||||
let error
|
||||
|
||||
try {
|
||||
await service.retrieveType("does-not-exist")
|
||||
} catch (e) {
|
||||
error = e
|
||||
}
|
||||
|
||||
expect(error.message).toEqual("ProductType with id: does-not-exist was not found")
|
||||
})
|
||||
})
|
||||
|
||||
describe("deleteTypes", () => {
|
||||
const typeId = "type-1"
|
||||
|
||||
it("should delete the product type given an ID successfully", async () => {
|
||||
await service.deleteTypes(
|
||||
[typeId],
|
||||
)
|
||||
|
||||
const types = await service.listTypes({
|
||||
id: typeId
|
||||
})
|
||||
|
||||
expect(types).toHaveLength(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe("updateTypes", () => {
|
||||
const typeId = "type-1"
|
||||
|
||||
it("should update the value of the type successfully", async () => {
|
||||
await service.updateTypes(
|
||||
[{
|
||||
id: typeId,
|
||||
value: "UK"
|
||||
}]
|
||||
)
|
||||
|
||||
const productType = await service.retrieveType(typeId)
|
||||
|
||||
expect(productType.value).toEqual("UK")
|
||||
})
|
||||
|
||||
it("should throw an error when an id does not exist", async () => {
|
||||
let error
|
||||
|
||||
try {
|
||||
await service.updateTypes([
|
||||
{
|
||||
id: "does-not-exist",
|
||||
value: "UK"
|
||||
}
|
||||
])
|
||||
} catch (e) {
|
||||
error = e
|
||||
}
|
||||
|
||||
expect(error.message).toEqual('ProductType with id "does-not-exist" not found')
|
||||
})
|
||||
})
|
||||
|
||||
describe("createTypes", () => {
|
||||
it("should create a type successfully", async () => {
|
||||
const res = await service.createTypes(
|
||||
[{
|
||||
value: "UK"
|
||||
}]
|
||||
)
|
||||
|
||||
const productType = await service.listTypes({
|
||||
value: "UK"
|
||||
})
|
||||
|
||||
expect(productType[0]?.value).toEqual("UK")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -0,0 +1,320 @@
|
||||
import { SqlEntityManager } from "@mikro-orm/postgresql"
|
||||
|
||||
import { ProductOptionService } from "@services"
|
||||
import { ProductOptionRepository } from "@repositories"
|
||||
import { Product } from "@models"
|
||||
|
||||
import { TestDatabase } from "../../../utils"
|
||||
import { createOptions } from "../../../__fixtures__/product"
|
||||
import { ProductTypes } from "@medusajs/types"
|
||||
|
||||
jest.setTimeout(30000)
|
||||
|
||||
describe("ProductOption Service", () => {
|
||||
let service: ProductOptionService
|
||||
let testManager: SqlEntityManager
|
||||
let repositoryManager: SqlEntityManager
|
||||
let productOne: Product
|
||||
let productTwo: Product
|
||||
let data!: Product[]
|
||||
|
||||
const productOneData = {
|
||||
id: "product-1",
|
||||
title: "product 1",
|
||||
status: ProductTypes.ProductStatus.PUBLISHED,
|
||||
}
|
||||
|
||||
const productTwoData = {
|
||||
id: "product-2",
|
||||
title: "product 2",
|
||||
status: ProductTypes.ProductStatus.PUBLISHED,
|
||||
}
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestDatabase.setupDatabase()
|
||||
repositoryManager = await TestDatabase.forkManager()
|
||||
|
||||
const productOptionRepository = new ProductOptionRepository({
|
||||
manager: repositoryManager,
|
||||
})
|
||||
|
||||
service = new ProductOptionService({
|
||||
productOptionRepository,
|
||||
})
|
||||
|
||||
testManager = await TestDatabase.forkManager()
|
||||
productOne = testManager.create(Product, productOneData)
|
||||
productTwo = testManager.create(Product, productTwoData)
|
||||
|
||||
data = await createOptions(testManager, [
|
||||
{
|
||||
id: "option-1",
|
||||
title: "Option 1",
|
||||
product: productOne,
|
||||
},
|
||||
{
|
||||
id: "option-2",
|
||||
title: "Option 2",
|
||||
product: productOne,
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
await TestDatabase.clearDatabase()
|
||||
})
|
||||
|
||||
describe("list", () => {
|
||||
it("list product option", async () => {
|
||||
const optionResults = await service.list()
|
||||
|
||||
expect(optionResults).toEqual([
|
||||
expect.objectContaining({
|
||||
id: "option-1",
|
||||
title: "Option 1",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: "option-2",
|
||||
title: "Option 2",
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it("list product option by id", async () => {
|
||||
const optionResults = await service.list({ id: "option-2" })
|
||||
|
||||
expect(optionResults).toEqual([
|
||||
expect.objectContaining({
|
||||
id: "option-2",
|
||||
title: "Option 2",
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it("list product option by title matching string", async () => {
|
||||
const optionResults = await service.list({ title: "Option 1" })
|
||||
|
||||
expect(optionResults).toEqual([
|
||||
expect.objectContaining({
|
||||
id: "option-1",
|
||||
title: "Option 1",
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe("listAndCount", () => {
|
||||
it("should return product option and count", async () => {
|
||||
const [optionResults, count] = await service.listAndCount()
|
||||
|
||||
expect(count).toEqual(2)
|
||||
expect(optionResults).toEqual([
|
||||
expect.objectContaining({
|
||||
id: "option-1",
|
||||
title: "Option 1",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: "option-2",
|
||||
title: "Option 2",
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it("should return product option and count when filtered", async () => {
|
||||
const [optionResults, count] = await service.listAndCount({ id: "option-2" })
|
||||
|
||||
expect(count).toEqual(1)
|
||||
expect(optionResults).toEqual([
|
||||
expect.objectContaining({
|
||||
id: "option-2",
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it("should return product option and count when using skip and take", async () => {
|
||||
const [optionResults, count] = await service.listAndCount({}, { skip: 1, take: 1 })
|
||||
|
||||
expect(count).toEqual(2)
|
||||
expect(optionResults).toEqual([
|
||||
expect.objectContaining({
|
||||
id: "option-2",
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it("should return requested fields", async () => {
|
||||
const [optionResults, count] = await service.listAndCount({}, {
|
||||
take: 1,
|
||||
select: ["title"],
|
||||
})
|
||||
|
||||
const serialized = JSON.parse(JSON.stringify(optionResults))
|
||||
|
||||
expect(count).toEqual(2)
|
||||
expect(serialized).toEqual([
|
||||
expect.objectContaining({
|
||||
id: "option-1",
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe("retrieve", () => {
|
||||
const optionId = "option-1"
|
||||
const optionValue = "Option 1"
|
||||
|
||||
it("should return option for the given id", async () => {
|
||||
const option = await service.retrieve(
|
||||
optionId,
|
||||
)
|
||||
|
||||
expect(option).toEqual(
|
||||
expect.objectContaining({
|
||||
id: optionId
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it("should throw an error when option with id does not exist", async () => {
|
||||
let error
|
||||
|
||||
try {
|
||||
await service.retrieve("does-not-exist")
|
||||
} catch (e) {
|
||||
error = e
|
||||
}
|
||||
|
||||
expect(error.message).toEqual('ProductOption with id: does-not-exist was not found')
|
||||
})
|
||||
|
||||
it("should throw an error when an id is not provided", async () => {
|
||||
let error
|
||||
|
||||
try {
|
||||
await service.retrieve(undefined as unknown as string)
|
||||
} catch (e) {
|
||||
error = e
|
||||
}
|
||||
|
||||
expect(error.message).toEqual('"productOptionId" must be defined')
|
||||
})
|
||||
|
||||
it("should return option based on config select param", async () => {
|
||||
const option = await service.retrieve(
|
||||
optionId,
|
||||
{
|
||||
select: ["id", "title"],
|
||||
}
|
||||
)
|
||||
|
||||
const serialized = JSON.parse(JSON.stringify(option))
|
||||
|
||||
expect(serialized).toEqual(
|
||||
{
|
||||
id: optionId,
|
||||
title: optionValue,
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("delete", () => {
|
||||
const optionId = "option-1"
|
||||
|
||||
it("should delete the product option given an ID successfully", async () => {
|
||||
|
||||
await service.delete(
|
||||
[optionId],
|
||||
)
|
||||
|
||||
const options = await service.list({
|
||||
id: optionId
|
||||
})
|
||||
|
||||
expect(options).toHaveLength(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe("update", () => {
|
||||
const optionId = "option-1"
|
||||
|
||||
it("should update the title of the option successfully", async () => {
|
||||
await service.update(
|
||||
[{
|
||||
id: optionId,
|
||||
title: "UK",
|
||||
}]
|
||||
)
|
||||
|
||||
const productOption = await service.retrieve(optionId)
|
||||
|
||||
expect(productOption.title).toEqual("UK")
|
||||
})
|
||||
|
||||
it("should update the relationship of the option successfully", async () => {
|
||||
await service.update(
|
||||
[{
|
||||
id: optionId,
|
||||
product_id: productTwo.id,
|
||||
}]
|
||||
)
|
||||
|
||||
const productOption = await service.retrieve(optionId, {
|
||||
relations: ["product"]
|
||||
})
|
||||
|
||||
expect(productOption).toEqual(
|
||||
expect.objectContaining({
|
||||
id: optionId,
|
||||
product: expect.objectContaining({
|
||||
id: productTwo.id
|
||||
})
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it("should throw an error when an id does not exist", async () => {
|
||||
let error
|
||||
|
||||
try {
|
||||
await service.update([
|
||||
{
|
||||
id: "does-not-exist",
|
||||
title: "UK",
|
||||
}
|
||||
])
|
||||
} catch (e) {
|
||||
error = e
|
||||
}
|
||||
|
||||
expect(error.message).toEqual('ProductOption with id "does-not-exist" not found')
|
||||
})
|
||||
})
|
||||
|
||||
describe("create", () => {
|
||||
it("should create a option successfully", async () => {
|
||||
await service.create(
|
||||
[{
|
||||
title: "UK",
|
||||
product: productOne
|
||||
}]
|
||||
)
|
||||
|
||||
const [productOption] = await service.list({
|
||||
title: "UK"
|
||||
}, {
|
||||
relations: ["product"],
|
||||
})
|
||||
|
||||
|
||||
expect(productOption).toEqual(
|
||||
expect.objectContaining({
|
||||
title: "UK",
|
||||
product: expect.objectContaining({
|
||||
id: productOne.id
|
||||
})
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -10,12 +10,45 @@ import { TestDatabase } from "../../../utils"
|
||||
|
||||
jest.setTimeout(30000)
|
||||
|
||||
describe("Product tag Service", () => {
|
||||
describe("ProductTag Service", () => {
|
||||
let service: ProductTagService
|
||||
let testManager: SqlEntityManager
|
||||
let repositoryManager: SqlEntityManager
|
||||
let data!: Product[]
|
||||
|
||||
const productsData = [
|
||||
{
|
||||
id: "test-1",
|
||||
title: "product 1",
|
||||
status: ProductTypes.ProductStatus.PUBLISHED,
|
||||
tags: [
|
||||
{
|
||||
id: "tag-1",
|
||||
value: "France",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "test-2",
|
||||
title: "product",
|
||||
status: ProductTypes.ProductStatus.PUBLISHED,
|
||||
tags: [
|
||||
{
|
||||
id: "tag-2",
|
||||
value: "Germany",
|
||||
},
|
||||
{
|
||||
id: "tag-3",
|
||||
value: "United States",
|
||||
},
|
||||
{
|
||||
id: "tag-4",
|
||||
value: "United Kingdom",
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestDatabase.setupDatabase()
|
||||
repositoryManager = await TestDatabase.forkManager()
|
||||
@@ -27,6 +60,10 @@ describe("Product tag Service", () => {
|
||||
service = new ProductTagService({
|
||||
productTagRepository,
|
||||
})
|
||||
|
||||
testManager = await TestDatabase.forkManager()
|
||||
|
||||
data = await createProductAndTags(testManager, productsData)
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
@@ -34,45 +71,6 @@ describe("Product tag Service", () => {
|
||||
})
|
||||
|
||||
describe("list", () => {
|
||||
const productsData = [
|
||||
{
|
||||
id: "test-1",
|
||||
title: "product 1",
|
||||
status: ProductTypes.ProductStatus.PUBLISHED,
|
||||
tags: [
|
||||
{
|
||||
id: "tag-1",
|
||||
value: "France",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "test-2",
|
||||
title: "product",
|
||||
status: ProductTypes.ProductStatus.PUBLISHED,
|
||||
tags: [
|
||||
{
|
||||
id: "tag-2",
|
||||
value: "Germany",
|
||||
},
|
||||
{
|
||||
id: "tag-3",
|
||||
value: "United States",
|
||||
},
|
||||
{
|
||||
id: "tag-4",
|
||||
value: "United Kingdom",
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
beforeEach(async () => {
|
||||
testManager = await TestDatabase.forkManager()
|
||||
|
||||
data = await createProductAndTags(testManager, productsData)
|
||||
})
|
||||
|
||||
it("list product tags", async () => {
|
||||
const tagsResults = await service.list()
|
||||
|
||||
@@ -118,4 +116,223 @@ describe("Product tag Service", () => {
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe("listAndCount", () => {
|
||||
it("should return product tags and count", async () => {
|
||||
const [tagsResults, count] = await service.listAndCount()
|
||||
|
||||
expect(count).toEqual(4)
|
||||
expect(tagsResults).toEqual([
|
||||
expect.objectContaining({
|
||||
id: "tag-1",
|
||||
value: "France",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: "tag-2",
|
||||
value: "Germany",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: "tag-3",
|
||||
value: "United States",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: "tag-4",
|
||||
value: "United Kingdom",
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it("should return product tags and count when filtered", async () => {
|
||||
const [tagsResults, count] = await service.listAndCount({ id: data[0].tags![0].id })
|
||||
|
||||
expect(count).toEqual(1)
|
||||
expect(tagsResults).toEqual([
|
||||
expect.objectContaining({
|
||||
id: "tag-1",
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it("should return product tags and count when using skip and take", async () => {
|
||||
const [tagsResults, count] = await service.listAndCount({}, { skip: 1, take: 2 })
|
||||
|
||||
expect(count).toEqual(4)
|
||||
expect(tagsResults).toEqual([
|
||||
expect.objectContaining({
|
||||
id: "tag-2",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: "tag-3",
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it("should return requested fields and relations", async () => {
|
||||
const [tagsResults, count] = await service.listAndCount({}, {
|
||||
take: 1,
|
||||
select: ["value", "products.id"],
|
||||
relations: ["products"]
|
||||
})
|
||||
|
||||
const serialized = JSON.parse(JSON.stringify(tagsResults))
|
||||
|
||||
expect(count).toEqual(4)
|
||||
expect(serialized).toEqual([
|
||||
expect.objectContaining({
|
||||
id: "tag-1",
|
||||
products: [{
|
||||
id: "test-1"
|
||||
}]
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe("retrieve", () => {
|
||||
const tagId = "tag-1"
|
||||
const tagValue = "France"
|
||||
const productId = "test-1"
|
||||
|
||||
it("should return tag for the given id", async () => {
|
||||
const tag = await service.retrieve(
|
||||
tagId,
|
||||
)
|
||||
|
||||
expect(tag).toEqual(
|
||||
expect.objectContaining({
|
||||
id: tagId
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it("should throw an error when tag with id does not exist", async () => {
|
||||
let error
|
||||
|
||||
try {
|
||||
await service.retrieve("does-not-exist")
|
||||
} catch (e) {
|
||||
error = e
|
||||
}
|
||||
|
||||
expect(error.message).toEqual('ProductTag with id: does-not-exist was not found')
|
||||
})
|
||||
|
||||
it("should throw an error when an id is not provided", async () => {
|
||||
let error
|
||||
|
||||
try {
|
||||
await service.retrieve(undefined as unknown as string)
|
||||
} catch (e) {
|
||||
error = e
|
||||
}
|
||||
|
||||
expect(error.message).toEqual('"productTagId" must be defined')
|
||||
})
|
||||
|
||||
it("should return tag based on config select param", async () => {
|
||||
const tag = await service.retrieve(
|
||||
tagId,
|
||||
{
|
||||
select: ["id", "value"],
|
||||
}
|
||||
)
|
||||
|
||||
const serialized = JSON.parse(JSON.stringify(tag))
|
||||
|
||||
expect(serialized).toEqual(
|
||||
{
|
||||
id: tagId,
|
||||
value: tagValue,
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it("should return tag based on config relation param", async () => {
|
||||
const tag = await service.retrieve(
|
||||
tagId,
|
||||
{
|
||||
select: ["id", "value", "products.id"],
|
||||
relations: ["products"]
|
||||
}
|
||||
)
|
||||
|
||||
const serialized = JSON.parse(JSON.stringify(tag))
|
||||
|
||||
expect(serialized).toEqual(
|
||||
{
|
||||
id: tagId,
|
||||
value: tagValue,
|
||||
products: [{
|
||||
id: productId
|
||||
}]
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("delete", () => {
|
||||
const tagId = "tag-1"
|
||||
|
||||
it("should delete the product tag given an ID successfully", async () => {
|
||||
await service.delete(
|
||||
[tagId],
|
||||
)
|
||||
|
||||
const tags = await service.list({
|
||||
id: tagId
|
||||
})
|
||||
|
||||
expect(tags).toHaveLength(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe("update", () => {
|
||||
const tagId = "tag-1"
|
||||
|
||||
it("should update the value of the tag successfully", async () => {
|
||||
await service.update(
|
||||
[{
|
||||
id: tagId,
|
||||
value: "UK"
|
||||
}]
|
||||
)
|
||||
|
||||
const productTag = await service.retrieve(tagId)
|
||||
|
||||
expect(productTag.value).toEqual("UK")
|
||||
})
|
||||
|
||||
it("should throw an error when an id does not exist", async () => {
|
||||
let error
|
||||
|
||||
try {
|
||||
await service.update([
|
||||
{
|
||||
id: "does-not-exist",
|
||||
value: "UK"
|
||||
}
|
||||
])
|
||||
} catch (e) {
|
||||
error = e
|
||||
}
|
||||
|
||||
expect(error.message).toEqual('ProductTag with id "does-not-exist" not found')
|
||||
})
|
||||
})
|
||||
|
||||
describe("create", () => {
|
||||
it("should create a tag successfully", async () => {
|
||||
await service.create(
|
||||
[{
|
||||
value: "UK"
|
||||
}]
|
||||
)
|
||||
|
||||
const [productTag] = await service.list({
|
||||
value: "UK"
|
||||
})
|
||||
|
||||
expect(productTag.value).toEqual("UK")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -0,0 +1,280 @@
|
||||
import { SqlEntityManager } from "@mikro-orm/postgresql"
|
||||
|
||||
import { ProductTypeService } from "@services"
|
||||
import { ProductTypeRepository } from "@repositories"
|
||||
import { Product } from "@models"
|
||||
|
||||
import { TestDatabase } from "../../../utils"
|
||||
import { createProductAndTypes } from "../../../__fixtures__/product"
|
||||
import { ProductTypes } from "@medusajs/types"
|
||||
|
||||
jest.setTimeout(30000)
|
||||
|
||||
describe("ProductType Service", () => {
|
||||
let service: ProductTypeService
|
||||
let testManager: SqlEntityManager
|
||||
let repositoryManager: SqlEntityManager
|
||||
let data!: Product[]
|
||||
|
||||
const productsData = [
|
||||
{
|
||||
id: "product-1",
|
||||
title: "product 1",
|
||||
status: ProductTypes.ProductStatus.PUBLISHED,
|
||||
type: {
|
||||
id: "type-1",
|
||||
value: "Type 1",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "product-2",
|
||||
title: "product",
|
||||
status: ProductTypes.ProductStatus.PUBLISHED,
|
||||
type: {
|
||||
id: "type-2",
|
||||
value: "Type 2",
|
||||
}
|
||||
},
|
||||
]
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestDatabase.setupDatabase()
|
||||
repositoryManager = await TestDatabase.forkManager()
|
||||
|
||||
const productTypeRepository = new ProductTypeRepository({
|
||||
manager: repositoryManager,
|
||||
})
|
||||
|
||||
service = new ProductTypeService({
|
||||
productTypeRepository,
|
||||
})
|
||||
|
||||
testManager = await TestDatabase.forkManager()
|
||||
|
||||
data = await createProductAndTypes(testManager, productsData)
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
await TestDatabase.clearDatabase()
|
||||
})
|
||||
|
||||
describe("list", () => {
|
||||
it("list product type", async () => {
|
||||
const typeResults = await service.list()
|
||||
|
||||
expect(typeResults).toEqual([
|
||||
expect.objectContaining({
|
||||
id: "type-1",
|
||||
value: "Type 1",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: "type-2",
|
||||
value: "Type 2",
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it("list product type by id", async () => {
|
||||
const typeResults = await service.list({ id: data[0].type.id })
|
||||
|
||||
expect(typeResults).toEqual([
|
||||
expect.objectContaining({
|
||||
id: "type-1",
|
||||
value: "Type 1",
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it("list product type by value matching string", async () => {
|
||||
const typeResults = await service.list({ value: "Type 1" })
|
||||
|
||||
expect(typeResults).toEqual([
|
||||
expect.objectContaining({
|
||||
id: "type-1",
|
||||
value: "Type 1",
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe("listAndCount", () => {
|
||||
it("should return product type and count", async () => {
|
||||
const [typeResults, count] = await service.listAndCount()
|
||||
|
||||
expect(count).toEqual(2)
|
||||
expect(typeResults).toEqual([
|
||||
expect.objectContaining({
|
||||
id: "type-1",
|
||||
value: "Type 1",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: "type-2",
|
||||
value: "Type 2",
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it("should return product type and count when filtered", async () => {
|
||||
const [typeResults, count] = await service.listAndCount({ id: data[0].type.id })
|
||||
|
||||
expect(count).toEqual(1)
|
||||
expect(typeResults).toEqual([
|
||||
expect.objectContaining({
|
||||
id: "type-1",
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it("should return product type and count when using skip and take", async () => {
|
||||
const [typeResults, count] = await service.listAndCount({}, { skip: 1, take: 1 })
|
||||
|
||||
expect(count).toEqual(2)
|
||||
expect(typeResults).toEqual([
|
||||
expect.objectContaining({
|
||||
id: "type-2",
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it("should return requested fields", async () => {
|
||||
const [typeResults, count] = await service.listAndCount({}, {
|
||||
take: 1,
|
||||
select: ["value"],
|
||||
})
|
||||
|
||||
const serialized = JSON.parse(JSON.stringify(typeResults))
|
||||
|
||||
expect(count).toEqual(2)
|
||||
expect(serialized).toEqual([
|
||||
expect.objectContaining({
|
||||
id: "type-1",
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe("retrieve", () => {
|
||||
const typeId = "type-1"
|
||||
const typeValue = "Type 1"
|
||||
|
||||
it("should return type for the given id", async () => {
|
||||
const type = await service.retrieve(
|
||||
typeId,
|
||||
)
|
||||
|
||||
expect(type).toEqual(
|
||||
expect.objectContaining({
|
||||
id: typeId
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it("should throw an error when type with id does not exist", async () => {
|
||||
let error
|
||||
|
||||
try {
|
||||
await service.retrieve("does-not-exist")
|
||||
} catch (e) {
|
||||
error = e
|
||||
}
|
||||
|
||||
expect(error.message).toEqual('ProductType with id: does-not-exist was not found')
|
||||
})
|
||||
|
||||
it("should throw an error when an id is not provided", async () => {
|
||||
let error
|
||||
|
||||
try {
|
||||
await service.retrieve(undefined as unknown as string)
|
||||
} catch (e) {
|
||||
error = e
|
||||
}
|
||||
|
||||
expect(error.message).toEqual('"productTypeId" must be defined')
|
||||
})
|
||||
|
||||
it("should return type based on config select param", async () => {
|
||||
const type = await service.retrieve(
|
||||
typeId,
|
||||
{
|
||||
select: ["id", "value"],
|
||||
}
|
||||
)
|
||||
|
||||
const serialized = JSON.parse(JSON.stringify(type))
|
||||
|
||||
expect(serialized).toEqual(
|
||||
{
|
||||
id: typeId,
|
||||
value: typeValue,
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("delete", () => {
|
||||
const typeId = "type-1"
|
||||
|
||||
it("should delete the product type given an ID successfully", async () => {
|
||||
await service.delete(
|
||||
[typeId],
|
||||
)
|
||||
|
||||
const types = await service.list({
|
||||
id: typeId
|
||||
})
|
||||
|
||||
expect(types).toHaveLength(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe("update", () => {
|
||||
const typeId = "type-1"
|
||||
|
||||
it("should update the value of the type successfully", async () => {
|
||||
await service.update(
|
||||
[{
|
||||
id: typeId,
|
||||
value: "UK"
|
||||
}]
|
||||
)
|
||||
|
||||
const productType = await service.retrieve(typeId)
|
||||
|
||||
expect(productType.value).toEqual("UK")
|
||||
})
|
||||
|
||||
it("should throw an error when an id does not exist", async () => {
|
||||
let error
|
||||
|
||||
try {
|
||||
await service.update([
|
||||
{
|
||||
id: "does-not-exist",
|
||||
value: "UK"
|
||||
}
|
||||
])
|
||||
} catch (e) {
|
||||
error = e
|
||||
}
|
||||
|
||||
expect(error.message).toEqual('ProductType with id "does-not-exist" not found')
|
||||
})
|
||||
})
|
||||
|
||||
describe("create", () => {
|
||||
it("should create a type successfully", async () => {
|
||||
await service.create(
|
||||
[{
|
||||
value: "UK"
|
||||
}]
|
||||
)
|
||||
|
||||
const [productType] = await service.list({
|
||||
value: "UK"
|
||||
})
|
||||
|
||||
expect(productType.value).toEqual("UK")
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -50,10 +50,10 @@ class ProductCategory {
|
||||
rank?: number
|
||||
|
||||
@Property({ columnType: "text", nullable: true })
|
||||
parent_category_id?: string
|
||||
parent_category_id?: string | null
|
||||
|
||||
@ManyToOne(() => ProductCategory, { nullable: true })
|
||||
parent_category: ProductCategory
|
||||
parent_category?: ProductCategory
|
||||
|
||||
@OneToMany({
|
||||
entity: () => ProductCategory,
|
||||
@@ -62,14 +62,14 @@ class ProductCategory {
|
||||
category_children = new Collection<ProductCategory>(this)
|
||||
|
||||
@Property({ onCreate: () => new Date(), columnType: "timestamptz" })
|
||||
created_at: Date
|
||||
created_at?: Date
|
||||
|
||||
@Property({
|
||||
onCreate: () => new Date(),
|
||||
onUpdate: () => new Date(),
|
||||
columnType: "timestamptz",
|
||||
})
|
||||
updated_at: Date
|
||||
updated_at?: Date
|
||||
|
||||
@ManyToMany(() => Product, (product) => product.categories)
|
||||
products = new Collection<Product>(this)
|
||||
|
||||
@@ -7,9 +7,9 @@ import {
|
||||
} from "@medusajs/utils"
|
||||
import { serialize } from "@mikro-orm/core"
|
||||
|
||||
// TODO: Should we create a mikro orm specific package for this and the soft deletable decorator util?
|
||||
// TODO: move to utils package
|
||||
|
||||
async function transactionWrapper(
|
||||
async function transactionWrapper<TManager = unknown>(
|
||||
this: any,
|
||||
task: (transactionManager: unknown) => Promise<any>,
|
||||
{
|
||||
@@ -18,7 +18,7 @@ async function transactionWrapper(
|
||||
enableNestedTransactions = false,
|
||||
}: {
|
||||
isolationLevel?: string
|
||||
transaction?: unknown
|
||||
transaction?: TManager
|
||||
enableNestedTransactions?: boolean
|
||||
} = {}
|
||||
): Promise<any> {
|
||||
@@ -40,26 +40,34 @@ async function transactionWrapper(
|
||||
return await (this.manager_ as SqlEntityManager).transactional(task, options)
|
||||
}
|
||||
|
||||
const updateDeletedAtRecursively = async <T extends object = any>(
|
||||
manager: SqlEntityManager,
|
||||
// TODO: move to utils package
|
||||
const mikroOrmUpdateDeletedAtRecursively = async <T extends object = any>(
|
||||
manager: any,
|
||||
entities: T[],
|
||||
value: Date | null
|
||||
) => {
|
||||
for await (const entity of entities) {
|
||||
for (const entity of entities) {
|
||||
if (!("deleted_at" in entity)) continue
|
||||
|
||||
;(entity as any).deleted_at = value
|
||||
|
||||
const relations = manager
|
||||
.getDriver()
|
||||
.getMetadata()
|
||||
.get(entities[0].constructor.name).relations
|
||||
.get(entity.constructor.name).relations
|
||||
|
||||
const relationsToCascade = relations.filter((relation) =>
|
||||
relation.cascade.includes("soft-remove" as any)
|
||||
)
|
||||
|
||||
for (const relation of relationsToCascade) {
|
||||
const relationEntities = (await entity[relation.name].init()).getItems({
|
||||
let collectionRelation = entity[relation.name]
|
||||
|
||||
if (!collectionRelation.isInitialized()) {
|
||||
await collectionRelation.init()
|
||||
}
|
||||
|
||||
const relationEntities = await collectionRelation.getItems({
|
||||
filters: {
|
||||
[DAL.SoftDeletableFilterKey]: {
|
||||
withDeleted: true,
|
||||
@@ -67,10 +75,10 @@ const updateDeletedAtRecursively = async <T extends object = any>(
|
||||
},
|
||||
})
|
||||
|
||||
await updateDeletedAtRecursively(manager, relationEntities, value)
|
||||
await mikroOrmUpdateDeletedAtRecursively(manager, relationEntities, value)
|
||||
}
|
||||
|
||||
await manager.persist(entities)
|
||||
await manager.persist(entity)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,17 +91,29 @@ const serializer = <TOutput extends object>(
|
||||
return result as unknown as Promise<TOutput>
|
||||
}
|
||||
|
||||
export abstract class AbstractBaseRepository<T = any>
|
||||
implements DAL.RepositoryService<T>
|
||||
{
|
||||
// TODO: move to utils package
|
||||
class AbstractBase<T = any> {
|
||||
protected readonly manager_: SqlEntityManager
|
||||
|
||||
protected constructor({ manager }) {
|
||||
this.manager_ = manager
|
||||
}
|
||||
|
||||
async transaction(
|
||||
task: (transactionManager: unknown) => Promise<any>,
|
||||
getFreshManager<TManager = unknown>(): TManager {
|
||||
return (this.manager_.fork
|
||||
? this.manager_.fork()
|
||||
: this.manager_) as unknown as TManager
|
||||
}
|
||||
|
||||
getActiveManager<TManager = unknown>(
|
||||
@MedusaContext()
|
||||
{ transactionManager, manager }: Context = {}
|
||||
): TManager {
|
||||
return (transactionManager ?? manager ?? this.manager_) as TManager
|
||||
}
|
||||
|
||||
async transaction<TManager = unknown>(
|
||||
task: (transactionManager: TManager) => Promise<any>,
|
||||
{
|
||||
transaction,
|
||||
isolationLevel,
|
||||
@@ -101,7 +121,7 @@ export abstract class AbstractBaseRepository<T = any>
|
||||
}: {
|
||||
isolationLevel?: string
|
||||
enableNestedTransactions?: boolean
|
||||
transaction?: unknown
|
||||
transaction?: TManager
|
||||
} = {}
|
||||
): Promise<any> {
|
||||
return await transactionWrapper.apply(this, arguments)
|
||||
@@ -113,7 +133,12 @@ export abstract class AbstractBaseRepository<T = any>
|
||||
): Promise<TOutput> {
|
||||
return await serializer<TOutput>(data, options)
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class AbstractBaseRepository<T = any>
|
||||
extends AbstractBase
|
||||
implements DAL.RepositoryService<T>
|
||||
{
|
||||
abstract find(options?: DAL.FindOptions<T>, context?: Context)
|
||||
|
||||
abstract findAndCount(
|
||||
@@ -132,9 +157,9 @@ export abstract class AbstractBaseRepository<T = any>
|
||||
{ transactionManager: manager }: Context = {}
|
||||
): Promise<T[]> {
|
||||
const entities = await this.find({ where: { id: { $in: ids } } as any })
|
||||
|
||||
const date = new Date()
|
||||
await updateDeletedAtRecursively(
|
||||
|
||||
await mikroOrmUpdateDeletedAtRecursively(
|
||||
manager as SqlEntityManager,
|
||||
entities,
|
||||
date
|
||||
@@ -158,7 +183,7 @@ export abstract class AbstractBaseRepository<T = any>
|
||||
|
||||
const entities = await this.find(query)
|
||||
|
||||
await updateDeletedAtRecursively(
|
||||
await mikroOrmUpdateDeletedAtRecursively(
|
||||
manager as SqlEntityManager,
|
||||
entities,
|
||||
null
|
||||
@@ -168,8 +193,9 @@ export abstract class AbstractBaseRepository<T = any>
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: move to utils package
|
||||
export abstract class AbstractTreeRepositoryBase<T = any>
|
||||
extends AbstractBaseRepository<T>
|
||||
extends AbstractBase<T>
|
||||
implements DAL.TreeRepositoryService<T>
|
||||
{
|
||||
protected constructor({ manager }) {
|
||||
@@ -188,12 +214,18 @@ export abstract class AbstractTreeRepositoryBase<T = any>
|
||||
transformOptions?: RepositoryTransformOptions,
|
||||
context?: Context
|
||||
): Promise<[T[], number]>
|
||||
|
||||
abstract create(data: unknown, context?: Context): Promise<T>
|
||||
|
||||
abstract delete(id: string, context?: Context): Promise<void>
|
||||
}
|
||||
|
||||
// TODO: move to utils package
|
||||
/**
|
||||
* Only used internally in order to be able to wrap in transaction from a
|
||||
* non identified repository
|
||||
*/
|
||||
|
||||
export class BaseRepository extends AbstractBaseRepository {
|
||||
constructor({ manager }) {
|
||||
// @ts-ignore
|
||||
@@ -219,3 +251,35 @@ export class BaseRepository extends AbstractBaseRepository {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class BaseTreeRepository extends AbstractTreeRepositoryBase {
|
||||
constructor({ manager }) {
|
||||
// @ts-ignore
|
||||
super(...arguments)
|
||||
}
|
||||
|
||||
find(
|
||||
options?: DAL.FindOptions,
|
||||
transformOptions?: RepositoryTransformOptions,
|
||||
context?: Context
|
||||
): Promise<any[]> {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
|
||||
findAndCount(
|
||||
options?: DAL.FindOptions,
|
||||
transformOptions?: RepositoryTransformOptions,
|
||||
context?: Context
|
||||
): Promise<[any[], number]> {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
|
||||
create(data: unknown, context?: Context): Promise<any> {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
|
||||
delete(id: string, context?: Context): Promise<void> {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,11 +6,26 @@ import {
|
||||
import { Product, ProductCategory } from "@models"
|
||||
import { Context, DAL, ProductCategoryTransformOptions } from "@medusajs/types"
|
||||
import groupBy from "lodash/groupBy"
|
||||
import { AbstractTreeRepositoryBase } from "./base"
|
||||
import { BaseTreeRepository } from "./base"
|
||||
import { SqlEntityManager } from "@mikro-orm/postgresql"
|
||||
import { InjectTransactionManager, MedusaContext } from "@medusajs/utils"
|
||||
import { InjectTransactionManager, MedusaContext, isDefined, MedusaError } from "@medusajs/utils"
|
||||
|
||||
export class ProductCategoryRepository extends AbstractTreeRepositoryBase<ProductCategory> {
|
||||
import { ProductCategoryServiceTypes } from "../types"
|
||||
|
||||
export type ReorderConditions = {
|
||||
targetCategoryId: string
|
||||
originalParentId: string | null
|
||||
targetParentId: string | null | undefined
|
||||
originalRank: number
|
||||
targetRank: number | undefined
|
||||
shouldChangeParent: boolean
|
||||
shouldChangeRank: boolean
|
||||
shouldIncrementRank: boolean
|
||||
shouldDeleteElement: boolean
|
||||
}
|
||||
|
||||
export const tempReorderRank = 99999
|
||||
export class ProductCategoryRepository extends BaseTreeRepository {
|
||||
protected readonly manager_: SqlEntityManager
|
||||
|
||||
constructor({ manager }: { manager: SqlEntityManager }) {
|
||||
@@ -24,8 +39,7 @@ export class ProductCategoryRepository extends AbstractTreeRepositoryBase<Produc
|
||||
transformOptions: ProductCategoryTransformOptions = {},
|
||||
context: Context = {}
|
||||
): Promise<ProductCategory[]> {
|
||||
const manager = (context.transactionManager ??
|
||||
this.manager_) as SqlEntityManager
|
||||
const manager = this.getActiveManager<SqlEntityManager>(context)
|
||||
|
||||
const findOptions_ = { ...findOptions }
|
||||
const { includeDescendantsTree } = transformOptions
|
||||
@@ -65,8 +79,7 @@ export class ProductCategoryRepository extends AbstractTreeRepositoryBase<Produc
|
||||
findOptions: DAL.FindOptions<ProductCategory> = { where: {} },
|
||||
context: Context = {}
|
||||
): Promise<ProductCategory[]> {
|
||||
const manager = (context.transactionManager ??
|
||||
this.manager_) as SqlEntityManager
|
||||
const manager = this.getActiveManager<SqlEntityManager>(context)
|
||||
|
||||
for (let productCategory of productCategories) {
|
||||
const whereOptions = {
|
||||
@@ -112,8 +125,7 @@ export class ProductCategoryRepository extends AbstractTreeRepositoryBase<Produc
|
||||
transformOptions: ProductCategoryTransformOptions = {},
|
||||
context: Context = {}
|
||||
): Promise<[ProductCategory[], number]> {
|
||||
const manager = (context.transactionManager ??
|
||||
this.manager_) as SqlEntityManager
|
||||
const manager = this.getActiveManager<SqlEntityManager>(context)
|
||||
|
||||
const findOptions_ = { ...findOptions }
|
||||
const { includeDescendantsTree } = transformOptions
|
||||
@@ -153,21 +165,267 @@ export class ProductCategoryRepository extends AbstractTreeRepositoryBase<Produc
|
||||
|
||||
@InjectTransactionManager()
|
||||
async delete(
|
||||
ids: string[],
|
||||
id: string,
|
||||
@MedusaContext()
|
||||
{ transactionManager: manager }: Context = {}
|
||||
context: Context = {}
|
||||
): Promise<void> {
|
||||
const manager = this.getActiveManager<SqlEntityManager>(context)
|
||||
const productCategory = await manager.findOneOrFail(
|
||||
ProductCategory,
|
||||
{ id },
|
||||
{
|
||||
populate: ["category_children"],
|
||||
}
|
||||
)
|
||||
|
||||
if (productCategory.category_children.length > 0) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
`Deleting ProductCategory (${id}) with category children is not allowed`
|
||||
)
|
||||
}
|
||||
|
||||
const conditions = this.fetchReorderConditions(
|
||||
productCategory,
|
||||
{
|
||||
parent_category_id: productCategory.parent_category_id,
|
||||
rank: productCategory.rank,
|
||||
},
|
||||
true
|
||||
)
|
||||
|
||||
await this.performReordering(manager, conditions)
|
||||
await (manager as SqlEntityManager).nativeDelete(
|
||||
Product,
|
||||
{ id: { $in: ids } },
|
||||
ProductCategory,
|
||||
{ id: id },
|
||||
{}
|
||||
)
|
||||
}
|
||||
|
||||
@InjectTransactionManager()
|
||||
async create(
|
||||
data: unknown[],
|
||||
context: Context = {}
|
||||
): Promise<ProductCategory[]> {
|
||||
throw new Error("Method not implemented.")
|
||||
data: ProductCategoryServiceTypes.CreateProductCategoryDTO,
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<ProductCategory> {
|
||||
const categoryData = { ...data }
|
||||
const manager = this.getActiveManager<SqlEntityManager>(sharedContext)
|
||||
const siblings = await manager.find(
|
||||
ProductCategory,
|
||||
{
|
||||
parent_category_id: categoryData?.parent_category_id || null
|
||||
},
|
||||
)
|
||||
|
||||
if (!isDefined(categoryData.rank)) {
|
||||
categoryData.rank = siblings.length
|
||||
}
|
||||
|
||||
const productCategory = manager.create(ProductCategory, categoryData)
|
||||
|
||||
await manager.persist(productCategory)
|
||||
|
||||
return productCategory
|
||||
}
|
||||
|
||||
@InjectTransactionManager()
|
||||
async update(
|
||||
id: string,
|
||||
data: ProductCategoryServiceTypes.UpdateProductCategoryDTO,
|
||||
@MedusaContext() context: Context = {}
|
||||
): Promise<ProductCategory> {
|
||||
const categoryData = { ...data }
|
||||
const manager = this.getActiveManager<SqlEntityManager>(context)
|
||||
const productCategory = await manager.findOneOrFail(ProductCategory, { id })
|
||||
|
||||
const conditions = this.fetchReorderConditions(
|
||||
productCategory,
|
||||
categoryData
|
||||
)
|
||||
|
||||
if (conditions.shouldChangeRank || conditions.shouldChangeParent) {
|
||||
categoryData.rank = tempReorderRank
|
||||
}
|
||||
|
||||
// await this.transformParentIdToEntity(categoryData)
|
||||
|
||||
for (const key in categoryData) {
|
||||
if (isDefined(categoryData[key])) {
|
||||
productCategory[key] = categoryData[key]
|
||||
}
|
||||
}
|
||||
|
||||
manager.assign(productCategory, categoryData)
|
||||
manager.persist(productCategory)
|
||||
|
||||
await this.performReordering(manager, conditions)
|
||||
|
||||
return productCategory
|
||||
}
|
||||
|
||||
protected fetchReorderConditions(
|
||||
productCategory: ProductCategory,
|
||||
data: ProductCategoryServiceTypes.UpdateProductCategoryDTO,
|
||||
shouldDeleteElement: boolean = false
|
||||
): ReorderConditions {
|
||||
const originalParentId = productCategory.parent_category_id || null
|
||||
const targetParentId = data.parent_category_id
|
||||
const originalRank = productCategory.rank || 0
|
||||
const targetRank = data.rank
|
||||
const shouldChangeParent =
|
||||
targetParentId !== undefined && targetParentId !== originalParentId
|
||||
const shouldChangeRank =
|
||||
shouldChangeParent ||
|
||||
(isDefined(targetRank) && originalRank !== targetRank)
|
||||
|
||||
return {
|
||||
targetCategoryId: productCategory.id,
|
||||
originalParentId,
|
||||
targetParentId,
|
||||
originalRank,
|
||||
targetRank,
|
||||
shouldChangeParent,
|
||||
shouldChangeRank,
|
||||
shouldIncrementRank: false,
|
||||
shouldDeleteElement,
|
||||
}
|
||||
}
|
||||
|
||||
protected async performReordering(
|
||||
manager: SqlEntityManager,
|
||||
conditions: ReorderConditions
|
||||
): Promise<void> {
|
||||
const { shouldChangeParent, shouldChangeRank, shouldDeleteElement } =
|
||||
conditions
|
||||
|
||||
if (!(shouldChangeParent || shouldChangeRank || shouldDeleteElement)) {
|
||||
return
|
||||
}
|
||||
|
||||
// If we change parent, we need to shift the siblings to eliminate the
|
||||
// rank occupied by the targetCategory in the original parent.
|
||||
shouldChangeParent &&
|
||||
(await this.shiftSiblings(manager, {
|
||||
...conditions,
|
||||
targetRank: conditions.originalRank,
|
||||
targetParentId: conditions.originalParentId,
|
||||
}))
|
||||
|
||||
// If we change parent, we need to shift the siblings of the new parent
|
||||
// to create a rank that the targetCategory will occupy.
|
||||
shouldChangeParent &&
|
||||
shouldChangeRank &&
|
||||
(await this.shiftSiblings(manager, {
|
||||
...conditions,
|
||||
shouldIncrementRank: true,
|
||||
}))
|
||||
|
||||
// If we only change rank, we need to shift the siblings
|
||||
// to create a rank that the targetCategory will occupy.
|
||||
;((!shouldChangeParent && shouldChangeRank) || shouldDeleteElement) &&
|
||||
(await this.shiftSiblings(manager, {
|
||||
...conditions,
|
||||
targetParentId: conditions.originalParentId,
|
||||
}))
|
||||
}
|
||||
|
||||
protected async shiftSiblings(
|
||||
manager: SqlEntityManager,
|
||||
conditions: ReorderConditions
|
||||
): Promise<void> {
|
||||
let { shouldIncrementRank, targetRank } = conditions
|
||||
const {
|
||||
shouldChangeParent,
|
||||
originalRank,
|
||||
targetParentId,
|
||||
targetCategoryId,
|
||||
shouldDeleteElement,
|
||||
} = conditions
|
||||
|
||||
// The current sibling count will replace targetRank if
|
||||
// targetRank is greater than the count of siblings.
|
||||
const siblingCount = await manager.count(
|
||||
ProductCategory,
|
||||
{
|
||||
parent_category_id: targetParentId || null,
|
||||
id: { $ne: targetCategoryId },
|
||||
}
|
||||
)
|
||||
|
||||
// The category record that will be placed at the requested rank
|
||||
// We've temporarily placed it at a temporary rank that is
|
||||
// beyond a reasonable value (tempReorderRank)
|
||||
const targetCategory = await manager.findOne(
|
||||
ProductCategory,
|
||||
{
|
||||
id: targetCategoryId,
|
||||
parent_category_id: targetParentId || null,
|
||||
rank: tempReorderRank,
|
||||
}
|
||||
)
|
||||
|
||||
// If the targetRank is not present, or if targetRank is beyond the
|
||||
// rank of the last category, we set the rank as the last rank
|
||||
if (targetRank === undefined || targetRank > siblingCount) {
|
||||
targetRank = siblingCount
|
||||
}
|
||||
|
||||
let rankCondition
|
||||
|
||||
// If parent doesn't change, we only need to get the ranks
|
||||
// in between the original rank and the target rank.
|
||||
if (shouldChangeParent || shouldDeleteElement) {
|
||||
rankCondition = { $gte: targetRank }
|
||||
} else if (originalRank > targetRank) {
|
||||
shouldIncrementRank = true
|
||||
rankCondition = { $gte: targetRank, $lt: originalRank }
|
||||
} else {
|
||||
shouldIncrementRank = false
|
||||
rankCondition = { $gte: originalRank, $lt: targetRank }
|
||||
}
|
||||
|
||||
// Scope out the list of siblings that we need to shift up or down
|
||||
const siblingsToShift = await manager.find(
|
||||
ProductCategory,
|
||||
{
|
||||
parent_category_id: targetParentId || null,
|
||||
rank: rankCondition,
|
||||
id: { $ne: targetCategoryId },
|
||||
},
|
||||
{
|
||||
orderBy: { rank: shouldIncrementRank ? "DESC" : "ASC" },
|
||||
}
|
||||
)
|
||||
|
||||
// Depending on the conditions, we get a subset of the siblings
|
||||
// and independently shift them up or down a rank
|
||||
for (let index = 0; index < siblingsToShift.length; index++) {
|
||||
const sibling = siblingsToShift[index]
|
||||
|
||||
// Depending on the condition, we could also have the targetCategory
|
||||
// in the siblings list, we skip shifting the target until all other siblings
|
||||
// have been shifted.
|
||||
if (sibling.id === targetCategoryId) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (!isDefined(sibling.rank)) {
|
||||
throw "error"
|
||||
}
|
||||
|
||||
const rank = shouldIncrementRank ? ++sibling.rank : --sibling.rank
|
||||
|
||||
manager.assign(sibling, { rank })
|
||||
manager.persist(sibling)
|
||||
}
|
||||
|
||||
// The targetCategory will not be present in the query when we are shifting
|
||||
// siblings of the old parent of the targetCategory.
|
||||
if (!targetCategory) {
|
||||
return
|
||||
}
|
||||
|
||||
// Place the targetCategory in the requested rank
|
||||
manager.assign(targetCategory, { rank: targetRank })
|
||||
manager.persist(targetCategory)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,20 @@
|
||||
import { Product, ProductCollection } from "@models"
|
||||
import { ProductCollection } from "@models"
|
||||
import {
|
||||
FilterQuery as MikroFilterQuery,
|
||||
FindOptions as MikroOptions,
|
||||
LoadStrategy,
|
||||
} from "@mikro-orm/core"
|
||||
import { Context, DAL } from "@medusajs/types"
|
||||
import { AbstractBaseRepository } from "./base"
|
||||
import { Context, DAL, ProductTypes } from "@medusajs/types"
|
||||
import { SqlEntityManager } from "@mikro-orm/postgresql"
|
||||
import { InjectTransactionManager, MedusaContext } from "@medusajs/utils"
|
||||
import {
|
||||
InjectTransactionManager,
|
||||
MedusaContext,
|
||||
MedusaError,
|
||||
} from "@medusajs/utils"
|
||||
|
||||
export class ProductCollectionRepository extends AbstractBaseRepository<ProductCollection> {
|
||||
import { BaseRepository } from "./base"
|
||||
|
||||
export class ProductCollectionRepository extends BaseRepository {
|
||||
protected readonly manager_: SqlEntityManager
|
||||
|
||||
constructor({ manager }: { manager: SqlEntityManager }) {
|
||||
@@ -62,23 +67,75 @@ export class ProductCollectionRepository extends AbstractBaseRepository<ProductC
|
||||
|
||||
@InjectTransactionManager()
|
||||
async delete(
|
||||
ids: string[],
|
||||
collectionIds: string[],
|
||||
@MedusaContext()
|
||||
{ transactionManager: manager }: Context = {}
|
||||
): Promise<void> {
|
||||
await (manager as SqlEntityManager).nativeDelete(
|
||||
Product,
|
||||
{ id: { $in: ids } },
|
||||
ProductCollection,
|
||||
{ id: { $in: collectionIds } },
|
||||
{}
|
||||
)
|
||||
}
|
||||
|
||||
@InjectTransactionManager()
|
||||
async create(
|
||||
data: unknown[],
|
||||
data: ProductTypes.CreateProductCollectionDTO[],
|
||||
@MedusaContext()
|
||||
{ transactionManager: manager }: Context = {}
|
||||
context: Context = {}
|
||||
): Promise<ProductCollection[]> {
|
||||
throw new Error("Method not implemented.")
|
||||
const manager = this.getActiveManager<SqlEntityManager>(context)
|
||||
|
||||
const productCollections = data.map((collectionData) => {
|
||||
return manager.create(ProductCollection, collectionData)
|
||||
})
|
||||
|
||||
await manager.persist(productCollections)
|
||||
|
||||
return productCollections
|
||||
}
|
||||
|
||||
@InjectTransactionManager()
|
||||
async update(
|
||||
data: ProductTypes.UpdateProductCollectionDTO[],
|
||||
@MedusaContext()
|
||||
context: Context = {}
|
||||
): Promise<ProductCollection[]> {
|
||||
const manager = this.getActiveManager<SqlEntityManager>(context)
|
||||
const collectionIds = data.map((collectionData) => collectionData.id)
|
||||
const existingCollections = await this.find(
|
||||
{
|
||||
where: {
|
||||
id: {
|
||||
$in: collectionIds,
|
||||
},
|
||||
},
|
||||
},
|
||||
context
|
||||
)
|
||||
|
||||
const existingCollectionsMap = new Map(
|
||||
existingCollections.map<[string, ProductCollection]>((collection) => [
|
||||
collection.id,
|
||||
collection,
|
||||
])
|
||||
)
|
||||
|
||||
const productCollections = data.map((collectionData) => {
|
||||
const existingCollection = existingCollectionsMap.get(collectionData.id)
|
||||
|
||||
if (!existingCollection) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_FOUND,
|
||||
`ProductCollection with id "${collectionData.id}" not found`
|
||||
)
|
||||
}
|
||||
|
||||
return manager.assign(existingCollection, collectionData)
|
||||
})
|
||||
|
||||
await manager.persist(productCollections)
|
||||
|
||||
return productCollections
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,8 +22,7 @@ export class ProductImageRepository extends AbstractBaseRepository<Image> {
|
||||
findOptions: DAL.FindOptions<Image> = { where: {} },
|
||||
context: Context = {}
|
||||
): Promise<Image[]> {
|
||||
const manager = (context.transactionManager ??
|
||||
this.manager_) as SqlEntityManager
|
||||
const manager = this.getActiveManager<SqlEntityManager>(context)
|
||||
|
||||
const findOptions_ = { ...findOptions }
|
||||
findOptions_.options ??= {}
|
||||
@@ -43,8 +42,7 @@ export class ProductImageRepository extends AbstractBaseRepository<Image> {
|
||||
findOptions: DAL.FindOptions<Image> = { where: {} },
|
||||
context: Context = {}
|
||||
): Promise<[Image[], number]> {
|
||||
const manager = (context.transactionManager ??
|
||||
this.manager_) as SqlEntityManager
|
||||
const manager = this.getActiveManager<SqlEntityManager>(context)
|
||||
|
||||
const findOptions_ = { ...findOptions }
|
||||
findOptions_.options ??= {}
|
||||
|
||||
@@ -7,7 +7,11 @@ import { Product, ProductOption } from "@models"
|
||||
import { Context, DAL, ProductTypes } from "@medusajs/types"
|
||||
import { AbstractBaseRepository } from "./base"
|
||||
import { SqlEntityManager } from "@mikro-orm/postgresql"
|
||||
import { InjectTransactionManager, MedusaContext } from "@medusajs/utils"
|
||||
import {
|
||||
InjectTransactionManager,
|
||||
MedusaContext,
|
||||
MedusaError,
|
||||
} from "@medusajs/utils"
|
||||
|
||||
export class ProductOptionRepository extends AbstractBaseRepository<ProductOption> {
|
||||
protected readonly manager_: SqlEntityManager
|
||||
@@ -22,8 +26,7 @@ export class ProductOptionRepository extends AbstractBaseRepository<ProductOptio
|
||||
findOptions: DAL.FindOptions<ProductOption> = { where: {} },
|
||||
context: Context = {}
|
||||
): Promise<ProductOption[]> {
|
||||
const manager = (context.transactionManager ??
|
||||
this.manager_) as SqlEntityManager
|
||||
const manager = this.getActiveManager<SqlEntityManager>(context)
|
||||
|
||||
const findOptions_ = { ...findOptions }
|
||||
findOptions_.options ??= {}
|
||||
@@ -43,8 +46,7 @@ export class ProductOptionRepository extends AbstractBaseRepository<ProductOptio
|
||||
findOptions: DAL.FindOptions<ProductOption> = { where: {} },
|
||||
context: Context = {}
|
||||
): Promise<[ProductOption[], number]> {
|
||||
const manager = (context.transactionManager ??
|
||||
this.manager_) as SqlEntityManager
|
||||
const manager = this.getActiveManager<SqlEntityManager>(context)
|
||||
|
||||
const findOptions_ = { ...findOptions }
|
||||
findOptions_.options ??= {}
|
||||
@@ -64,10 +66,12 @@ export class ProductOptionRepository extends AbstractBaseRepository<ProductOptio
|
||||
async delete(
|
||||
ids: string[],
|
||||
@MedusaContext()
|
||||
{ transactionManager: manager }: Context = {}
|
||||
context: Context = {}
|
||||
): Promise<void> {
|
||||
const manager = this.getActiveManager<SqlEntityManager>(context)
|
||||
|
||||
await (manager as SqlEntityManager).nativeDelete(
|
||||
Product,
|
||||
ProductOption,
|
||||
{ id: { $in: ids } },
|
||||
{}
|
||||
)
|
||||
@@ -75,16 +79,81 @@ export class ProductOptionRepository extends AbstractBaseRepository<ProductOptio
|
||||
|
||||
@InjectTransactionManager()
|
||||
async create(
|
||||
data: (ProductTypes.CreateProductOptionDTO & { product: { id: string } })[],
|
||||
data: ProductTypes.CreateProductOptionDTO[],
|
||||
@MedusaContext()
|
||||
{ transactionManager: manager }: Context = {}
|
||||
context: Context = {}
|
||||
): Promise<ProductOption[]> {
|
||||
const options = data.map((option) => {
|
||||
return (manager as SqlEntityManager).create(ProductOption, option)
|
||||
const manager = this.getActiveManager<SqlEntityManager>(context)
|
||||
const productIds: string[] = []
|
||||
|
||||
data.forEach((d) => d.product_id && productIds.push(d.product_id))
|
||||
|
||||
const existingProducts = await manager.find(
|
||||
Product,
|
||||
{ id: { $in: productIds } },
|
||||
)
|
||||
|
||||
const existingProductsMap = new Map(
|
||||
existingProducts.map<[string, Product]>((product) => [product.id, product])
|
||||
)
|
||||
|
||||
const productOptions = data.map((optionData) => {
|
||||
const productId = optionData.product_id
|
||||
|
||||
delete optionData.product_id
|
||||
|
||||
if (productId) {
|
||||
const product = existingProductsMap.get(productId)
|
||||
|
||||
optionData.product = product
|
||||
}
|
||||
|
||||
return manager.create(ProductOption, optionData)
|
||||
})
|
||||
|
||||
await (manager as SqlEntityManager).persist(options)
|
||||
await manager.persist(productOptions)
|
||||
|
||||
return options
|
||||
return productOptions
|
||||
}
|
||||
|
||||
@InjectTransactionManager()
|
||||
async update(
|
||||
data: ProductTypes.UpdateProductOptionDTO[],
|
||||
@MedusaContext()
|
||||
context: Context = {}
|
||||
): Promise<ProductOption[]> {
|
||||
const manager = this.getActiveManager<SqlEntityManager>(context)
|
||||
const optionIds = data.map((optionData) => optionData.id)
|
||||
const existingOptions = await this.find(
|
||||
{
|
||||
where: {
|
||||
id: {
|
||||
$in: optionIds,
|
||||
},
|
||||
},
|
||||
},
|
||||
context
|
||||
)
|
||||
|
||||
const existingOptionsMap = new Map(
|
||||
existingOptions.map<[string, ProductOption]>((option) => [option.id, option])
|
||||
)
|
||||
|
||||
const productOptions = data.map((optionData) => {
|
||||
const existingOption = existingOptionsMap.get(optionData.id)
|
||||
|
||||
if (!existingOption) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_FOUND,
|
||||
`ProductOption with id "${optionData.id}" not found`
|
||||
)
|
||||
}
|
||||
|
||||
return manager.assign(existingOption, optionData)
|
||||
})
|
||||
|
||||
await manager.persist(productOptions)
|
||||
|
||||
return productOptions
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,13 +4,23 @@ import {
|
||||
LoadStrategy,
|
||||
RequiredEntityData,
|
||||
} from "@mikro-orm/core"
|
||||
import { Product, ProductTag } from "@models"
|
||||
import { Context, CreateProductTagDTO, DAL } from "@medusajs/types"
|
||||
import { AbstractBaseRepository } from "./base"
|
||||
import { ProductTag } from "@models"
|
||||
import {
|
||||
Context,
|
||||
CreateProductTagDTO,
|
||||
DAL,
|
||||
UpdateProductTagDTO,
|
||||
UpsertProductTagDTO,
|
||||
} from "@medusajs/types"
|
||||
import { BaseRepository } from "./base"
|
||||
import { SqlEntityManager } from "@mikro-orm/postgresql"
|
||||
import { InjectTransactionManager, MedusaContext } from "@medusajs/utils"
|
||||
import {
|
||||
InjectTransactionManager,
|
||||
MedusaContext,
|
||||
MedusaError,
|
||||
} from "@medusajs/utils"
|
||||
|
||||
export class ProductTagRepository extends AbstractBaseRepository<ProductTag> {
|
||||
export class ProductTagRepository extends BaseRepository {
|
||||
protected readonly manager_: SqlEntityManager
|
||||
|
||||
constructor({ manager }: { manager: SqlEntityManager }) {
|
||||
@@ -23,10 +33,9 @@ export class ProductTagRepository extends AbstractBaseRepository<ProductTag> {
|
||||
findOptions: DAL.FindOptions<ProductTag> = { where: {} },
|
||||
context: Context = {}
|
||||
): Promise<ProductTag[]> {
|
||||
const manager = (context.transactionManager ??
|
||||
this.manager_) as SqlEntityManager
|
||||
|
||||
const manager = this.getActiveManager<SqlEntityManager>(context)
|
||||
const findOptions_ = { ...findOptions }
|
||||
|
||||
findOptions_.options ??= {}
|
||||
|
||||
Object.assign(findOptions_.options, {
|
||||
@@ -44,8 +53,7 @@ export class ProductTagRepository extends AbstractBaseRepository<ProductTag> {
|
||||
findOptions: DAL.FindOptions<ProductTag> = { where: {} },
|
||||
context: Context = {}
|
||||
): Promise<[ProductTag[], number]> {
|
||||
const manager = (context.transactionManager ??
|
||||
this.manager_) as SqlEntityManager
|
||||
const manager = this.getActiveManager<SqlEntityManager>(context)
|
||||
|
||||
const findOptions_ = { ...findOptions }
|
||||
findOptions_.options ??= {}
|
||||
@@ -61,14 +69,71 @@ export class ProductTagRepository extends AbstractBaseRepository<ProductTag> {
|
||||
)
|
||||
}
|
||||
|
||||
@InjectTransactionManager()
|
||||
async create(
|
||||
data: CreateProductTagDTO[],
|
||||
@MedusaContext()
|
||||
context: Context = {}
|
||||
): Promise<ProductTag[]> {
|
||||
const manager = this.getActiveManager<SqlEntityManager>(context)
|
||||
|
||||
const productTags = data.map((tagData) => {
|
||||
return manager.create(ProductTag, tagData)
|
||||
})
|
||||
|
||||
await manager.persist(productTags)
|
||||
|
||||
return productTags
|
||||
}
|
||||
|
||||
@InjectTransactionManager()
|
||||
async update(
|
||||
data: UpdateProductTagDTO[],
|
||||
@MedusaContext()
|
||||
context: Context = {}
|
||||
): Promise<ProductTag[]> {
|
||||
const manager = this.getActiveManager<SqlEntityManager>(context)
|
||||
const tagIds = data.map((tagData) => tagData.id)
|
||||
const existingTags = await this.find(
|
||||
{
|
||||
where: {
|
||||
id: {
|
||||
$in: tagIds,
|
||||
},
|
||||
},
|
||||
},
|
||||
context
|
||||
)
|
||||
|
||||
const existingTagsMap = new Map(
|
||||
existingTags.map<[string, ProductTag]>((tag) => [tag.id, tag])
|
||||
)
|
||||
|
||||
const productTags = data.map((tagData) => {
|
||||
const existingTag = existingTagsMap.get(tagData.id)
|
||||
|
||||
if (!existingTag) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_FOUND,
|
||||
`ProductTag with id "${tagData.id}" not found`
|
||||
)
|
||||
}
|
||||
|
||||
return manager.assign(existingTag, tagData)
|
||||
})
|
||||
|
||||
await manager.persist(productTags)
|
||||
|
||||
return productTags
|
||||
}
|
||||
|
||||
@InjectTransactionManager()
|
||||
async upsert(
|
||||
tags: CreateProductTagDTO[],
|
||||
tags: UpsertProductTagDTO[],
|
||||
@MedusaContext()
|
||||
context: Context = {}
|
||||
): Promise<ProductTag[]> {
|
||||
const { transactionManager: manager } = context
|
||||
|
||||
const tagsValues = tags.map((tag) => tag.value)
|
||||
const existingTags = await this.find(
|
||||
{
|
||||
@@ -115,21 +180,10 @@ export class ProductTagRepository extends AbstractBaseRepository<ProductTag> {
|
||||
async delete(
|
||||
ids: string[],
|
||||
@MedusaContext()
|
||||
{ transactionManager: manager }: Context = {}
|
||||
context: Context = {}
|
||||
): Promise<void> {
|
||||
await (manager as SqlEntityManager).nativeDelete(
|
||||
Product,
|
||||
{ id: { $in: ids } },
|
||||
{}
|
||||
)
|
||||
}
|
||||
const manager = this.getActiveManager<SqlEntityManager>(context)
|
||||
|
||||
@InjectTransactionManager()
|
||||
async create(
|
||||
data: unknown[],
|
||||
@MedusaContext()
|
||||
{ transactionManager: manager }: Context = {}
|
||||
): Promise<ProductTag[]> {
|
||||
throw new Error("Method not implemented.")
|
||||
await manager.nativeDelete(ProductTag, { id: { $in: ids } }, {})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,13 +4,23 @@ import {
|
||||
LoadStrategy,
|
||||
RequiredEntityData,
|
||||
} from "@mikro-orm/core"
|
||||
import { Product, ProductType } from "@models"
|
||||
import { Context, CreateProductTypeDTO, DAL } from "@medusajs/types"
|
||||
import { AbstractBaseRepository } from "./base"
|
||||
import { ProductType } from "@models"
|
||||
import {
|
||||
Context,
|
||||
CreateProductTypeDTO,
|
||||
DAL,
|
||||
UpdateProductTypeDTO,
|
||||
} from "@medusajs/types"
|
||||
import { SqlEntityManager } from "@mikro-orm/postgresql"
|
||||
import { InjectTransactionManager, MedusaContext } from "@medusajs/utils"
|
||||
import {
|
||||
InjectTransactionManager,
|
||||
MedusaContext,
|
||||
MedusaError,
|
||||
} from "@medusajs/utils"
|
||||
|
||||
export class ProductTypeRepository extends AbstractBaseRepository<ProductType> {
|
||||
import { BaseRepository } from "./base"
|
||||
|
||||
export class ProductTypeRepository extends BaseRepository {
|
||||
protected readonly manager_: SqlEntityManager
|
||||
|
||||
constructor({ manager }: { manager: SqlEntityManager }) {
|
||||
@@ -23,8 +33,7 @@ export class ProductTypeRepository extends AbstractBaseRepository<ProductType> {
|
||||
findOptions: DAL.FindOptions<ProductType> = { where: {} },
|
||||
context: Context = {}
|
||||
): Promise<ProductType[]> {
|
||||
const manager = (context.transactionManager ??
|
||||
this.manager_) as SqlEntityManager
|
||||
const manager = this.getActiveManager<SqlEntityManager>(context)
|
||||
|
||||
const findOptions_ = { ...findOptions }
|
||||
findOptions_.options ??= {}
|
||||
@@ -44,8 +53,7 @@ export class ProductTypeRepository extends AbstractBaseRepository<ProductType> {
|
||||
findOptions: DAL.FindOptions<ProductType> = { where: {} },
|
||||
context: Context = {}
|
||||
): Promise<[ProductType[], number]> {
|
||||
const manager = (context.transactionManager ??
|
||||
this.manager_) as SqlEntityManager
|
||||
const manager = this.getActiveManager<SqlEntityManager>(context)
|
||||
|
||||
const findOptions_ = { ...findOptions }
|
||||
findOptions_.options ??= {}
|
||||
@@ -118,7 +126,7 @@ export class ProductTypeRepository extends AbstractBaseRepository<ProductType> {
|
||||
{ transactionManager: manager }: Context = {}
|
||||
): Promise<void> {
|
||||
await (manager as SqlEntityManager).nativeDelete(
|
||||
Product,
|
||||
ProductType,
|
||||
{ id: { $in: ids } },
|
||||
{}
|
||||
)
|
||||
@@ -126,10 +134,59 @@ export class ProductTypeRepository extends AbstractBaseRepository<ProductType> {
|
||||
|
||||
@InjectTransactionManager()
|
||||
async create(
|
||||
data: unknown[],
|
||||
data: CreateProductTypeDTO[],
|
||||
@MedusaContext()
|
||||
{ transactionManager: manager }: Context = {}
|
||||
context: Context = {}
|
||||
): Promise<ProductType[]> {
|
||||
throw new Error("Method not implemented.")
|
||||
const manager = this.getActiveManager<SqlEntityManager>(context)
|
||||
|
||||
const productTypes = data.map((typeData) => {
|
||||
return manager.create(ProductType, typeData)
|
||||
})
|
||||
|
||||
await manager.persist(productTypes)
|
||||
|
||||
return productTypes
|
||||
}
|
||||
|
||||
@InjectTransactionManager()
|
||||
async update(
|
||||
data: UpdateProductTypeDTO[],
|
||||
@MedusaContext()
|
||||
context: Context = {}
|
||||
): Promise<ProductType[]> {
|
||||
const manager = this.getActiveManager<SqlEntityManager>(context)
|
||||
const typeIds = data.map((typeData) => typeData.id)
|
||||
const existingTypes = await this.find(
|
||||
{
|
||||
where: {
|
||||
id: {
|
||||
$in: typeIds,
|
||||
},
|
||||
},
|
||||
},
|
||||
context
|
||||
)
|
||||
|
||||
const existingTypesMap = new Map(
|
||||
existingTypes.map<[string, ProductType]>((type) => [type.id, type])
|
||||
)
|
||||
|
||||
const productTypes = data.map((typeData) => {
|
||||
const existingType = existingTypesMap.get(typeData.id)
|
||||
|
||||
if (!existingType) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_FOUND,
|
||||
`ProductType with id "${typeData.id}" not found`
|
||||
)
|
||||
}
|
||||
|
||||
return manager.assign(existingType, typeData)
|
||||
})
|
||||
|
||||
await manager.persist(productTypes)
|
||||
|
||||
return productTypes
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,8 +31,7 @@ export class ProductVariantRepository extends AbstractBaseRepository<ProductVari
|
||||
findOptions: DAL.FindOptions<ProductVariant> = { where: {} },
|
||||
context: Context = {}
|
||||
): Promise<ProductVariant[]> {
|
||||
const manager = (context.transactionManager ??
|
||||
this.manager_) as SqlEntityManager
|
||||
const manager = this.getActiveManager<SqlEntityManager>(context)
|
||||
|
||||
const findOptions_ = { ...findOptions }
|
||||
findOptions_.options ??= {}
|
||||
@@ -52,8 +51,7 @@ export class ProductVariantRepository extends AbstractBaseRepository<ProductVari
|
||||
findOptions: DAL.FindOptions<ProductVariant> = { where: {} },
|
||||
context: Context = {}
|
||||
): Promise<[ProductVariant[], number]> {
|
||||
const manager = (context.transactionManager ??
|
||||
this.manager_) as SqlEntityManager
|
||||
const manager = this.getActiveManager<SqlEntityManager>(context)
|
||||
|
||||
const findOptions_ = { ...findOptions }
|
||||
findOptions_.options ??= {}
|
||||
|
||||
@@ -38,9 +38,7 @@ export class ProductRepository extends AbstractBaseRepository<Product> {
|
||||
findOptions: DAL.FindOptions<Product> = { where: {} },
|
||||
context: Context = {}
|
||||
): Promise<Product[]> {
|
||||
// TODO: use the getter method (getActiveManager)
|
||||
const manager = (context.transactionManager ??
|
||||
this.manager_) as SqlEntityManager
|
||||
const manager = this.getActiveManager<SqlEntityManager>(context)
|
||||
|
||||
const findOptions_ = { ...findOptions }
|
||||
findOptions_.options ??= {}
|
||||
@@ -62,20 +60,18 @@ export class ProductRepository extends AbstractBaseRepository<Product> {
|
||||
findOptions: DAL.FindOptions<Product> = { where: {} },
|
||||
context: Context = {}
|
||||
): Promise<[Product[], number]> {
|
||||
const manager = this.getActiveManager<SqlEntityManager>(context)
|
||||
|
||||
const findOptions_ = { ...findOptions }
|
||||
findOptions_.options ??= {}
|
||||
|
||||
if (context.transactionManager) {
|
||||
Object.assign(findOptions_.options, { ctx: context.transactionManager })
|
||||
}
|
||||
|
||||
Object.assign(findOptions_.options, {
|
||||
strategy: LoadStrategy.SELECT_IN,
|
||||
})
|
||||
|
||||
await this.mutateNotInCategoriesConstraints(findOptions_)
|
||||
|
||||
return await this.manager_.findAndCount(
|
||||
return await manager.findAndCount(
|
||||
Product,
|
||||
findOptions_.where as MikroFilterQuery<Product>,
|
||||
findOptions_.options as MikroOptions<Product>
|
||||
@@ -90,9 +86,7 @@ export class ProductRepository extends AbstractBaseRepository<Product> {
|
||||
findOptions: DAL.FindOptions<Product> = { where: {} },
|
||||
context: Context = {}
|
||||
): Promise<void> {
|
||||
// TODO: use the getter method (getActiveManager)
|
||||
const manager = (context.transactionManager ??
|
||||
this.manager_) as SqlEntityManager
|
||||
const manager = this.getActiveManager<SqlEntityManager>(context)
|
||||
|
||||
if (findOptions.where.categories?.id?.["$nin"]) {
|
||||
const productsInCategories = await manager.find(
|
||||
|
||||
@@ -15,6 +15,7 @@ mockContainer.register({
|
||||
return [{}]
|
||||
}),
|
||||
findAndCount: jest.fn().mockResolvedValue([[], 0]),
|
||||
getFreshManager: jest.fn().mockResolvedValue({}),
|
||||
}),
|
||||
productService: asClass(ProductService),
|
||||
})
|
||||
|
||||
@@ -8,9 +8,10 @@ describe("Product service", function () {
|
||||
it("should retrieve a product", async function () {
|
||||
const productService = mockContainer.resolve("productService")
|
||||
const productRepository = mockContainer.resolve("productRepository")
|
||||
|
||||
const productId = "existing-product"
|
||||
|
||||
await productService.retrieve(productId)
|
||||
|
||||
expect(productRepository.find).toHaveBeenCalledWith(
|
||||
{
|
||||
where: {
|
||||
@@ -24,7 +25,7 @@ describe("Product service", function () {
|
||||
withDeleted: undefined,
|
||||
},
|
||||
},
|
||||
undefined
|
||||
expect.any(Object)
|
||||
)
|
||||
})
|
||||
|
||||
@@ -49,7 +50,7 @@ describe("Product service", function () {
|
||||
withDeleted: undefined,
|
||||
},
|
||||
},
|
||||
undefined
|
||||
expect.any(Object)
|
||||
)
|
||||
|
||||
expect(err.message).toBe(
|
||||
@@ -79,7 +80,7 @@ describe("Product service", function () {
|
||||
withDeleted: undefined,
|
||||
},
|
||||
},
|
||||
undefined
|
||||
expect.any(Object)
|
||||
)
|
||||
})
|
||||
|
||||
@@ -117,7 +118,7 @@ describe("Product service", function () {
|
||||
withDeleted: undefined,
|
||||
},
|
||||
},
|
||||
undefined
|
||||
expect.any(Object)
|
||||
)
|
||||
})
|
||||
|
||||
@@ -155,7 +156,7 @@ describe("Product service", function () {
|
||||
populate: ["tags"],
|
||||
},
|
||||
},
|
||||
undefined
|
||||
expect.any(Object)
|
||||
)
|
||||
})
|
||||
|
||||
@@ -193,7 +194,7 @@ describe("Product service", function () {
|
||||
populate: ["tags"],
|
||||
},
|
||||
},
|
||||
undefined
|
||||
expect.any(Object)
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,6 +1,17 @@
|
||||
import { ProductCategory } from "@models"
|
||||
import { ProductCategoryRepository } from "@repositories"
|
||||
import { Context, DAL, FindConfig, ProductTypes } from "@medusajs/types"
|
||||
import { ModulesSdkUtils, MedusaError, isDefined } from "@medusajs/utils"
|
||||
import {
|
||||
ModulesSdkUtils,
|
||||
MedusaError,
|
||||
isDefined,
|
||||
InjectTransactionManager,
|
||||
InjectManager,
|
||||
MedusaContext
|
||||
} from "@medusajs/utils"
|
||||
|
||||
import { shouldForceTransaction } from "../utils"
|
||||
import { ProductCategoryServiceTypes } from "../types"
|
||||
|
||||
type InjectedDependencies = {
|
||||
productCategoryRepository: DAL.TreeRepositoryService
|
||||
@@ -15,10 +26,11 @@ export default class ProductCategoryService<
|
||||
this.productCategoryRepository_ = productCategoryRepository
|
||||
}
|
||||
|
||||
@InjectManager("productCategoryRepository_")
|
||||
async retrieve(
|
||||
productCategoryId: string,
|
||||
config: FindConfig<ProductTypes.ProductCategoryDTO> = {},
|
||||
sharedContext?: Context
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<TEntity> {
|
||||
if (!isDefined(productCategoryId)) {
|
||||
throw new MedusaError(
|
||||
@@ -51,10 +63,11 @@ export default class ProductCategoryService<
|
||||
return productCategories[0] as TEntity
|
||||
}
|
||||
|
||||
@InjectManager("productCategoryRepository_")
|
||||
async list(
|
||||
filters: ProductTypes.FilterableProductCategoryProps = {},
|
||||
config: FindConfig<ProductTypes.ProductCategoryDTO> = {},
|
||||
sharedContext?: Context
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<TEntity[]> {
|
||||
const transformOptions = {
|
||||
includeDescendantsTree: filters?.include_descendants_tree || false,
|
||||
@@ -74,10 +87,11 @@ export default class ProductCategoryService<
|
||||
)) as TEntity[]
|
||||
}
|
||||
|
||||
@InjectManager("productCategoryRepository_")
|
||||
async listAndCount(
|
||||
filters: ProductTypes.FilterableProductCategoryProps = {},
|
||||
config: FindConfig<ProductTypes.ProductCategoryDTO> = {},
|
||||
sharedContext?: Context
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<[TEntity[], number]> {
|
||||
const transformOptions = {
|
||||
includeDescendantsTree: filters?.include_descendants_tree || false,
|
||||
@@ -96,4 +110,36 @@ export default class ProductCategoryService<
|
||||
sharedContext
|
||||
)) as [TEntity[], number]
|
||||
}
|
||||
|
||||
@InjectTransactionManager(shouldForceTransaction, "productCategoryRepository_")
|
||||
async create(
|
||||
data: ProductCategoryServiceTypes.CreateProductCategoryDTO,
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<TEntity> {
|
||||
return (await (this.productCategoryRepository_ as ProductCategoryRepository).create(
|
||||
data,
|
||||
sharedContext
|
||||
)) as TEntity
|
||||
}
|
||||
|
||||
@InjectTransactionManager(shouldForceTransaction, "productCategoryRepository_")
|
||||
async update(
|
||||
id: string,
|
||||
data: ProductCategoryServiceTypes.UpdateProductCategoryDTO,
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<TEntity> {
|
||||
return (await (this.productCategoryRepository_ as ProductCategoryRepository).update(
|
||||
id,
|
||||
data,
|
||||
sharedContext
|
||||
)) as TEntity
|
||||
}
|
||||
|
||||
@InjectTransactionManager(shouldForceTransaction, "productCategoryRepository_")
|
||||
async delete(
|
||||
id: string,
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<void> {
|
||||
await this.productCategoryRepository_.delete(id, sharedContext)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
import { Context, DAL, FindConfig, ProductTypes } from "@medusajs/types"
|
||||
import { ModulesSdkUtils, retrieveEntity } from "@medusajs/utils"
|
||||
import {
|
||||
ModulesSdkUtils,
|
||||
retrieveEntity,
|
||||
InjectTransactionManager,
|
||||
MedusaContext,
|
||||
InjectManager,
|
||||
} from "@medusajs/utils"
|
||||
|
||||
import { shouldForceTransaction } from "../utils"
|
||||
import { ProductCollectionRepository } from "../repositories"
|
||||
|
||||
import { ProductCollection } from "@models"
|
||||
|
||||
@@ -10,16 +19,17 @@ type InjectedDependencies = {
|
||||
export default class ProductCollectionService<
|
||||
TEntity extends ProductCollection = ProductCollection
|
||||
> {
|
||||
protected readonly productCollectionRepository_: DAL.TreeRepositoryService
|
||||
protected readonly productCollectionRepository_: DAL.RepositoryService
|
||||
|
||||
constructor({ productCollectionRepository }: InjectedDependencies) {
|
||||
this.productCollectionRepository_ = productCollectionRepository
|
||||
}
|
||||
|
||||
@InjectManager("productCollectionRepository_")
|
||||
async retrieve(
|
||||
productCollectionId: string,
|
||||
config: FindConfig<ProductTypes.ProductCollectionDTO> = {},
|
||||
sharedContext?: Context
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<TEntity> {
|
||||
return (await retrieveEntity<
|
||||
ProductCollection,
|
||||
@@ -33,10 +43,11 @@ export default class ProductCollectionService<
|
||||
})) as TEntity
|
||||
}
|
||||
|
||||
@InjectManager("productCollectionRepository_")
|
||||
async list(
|
||||
filters: ProductTypes.FilterableProductCollectionProps = {},
|
||||
config: FindConfig<ProductTypes.ProductCollectionDTO> = {},
|
||||
sharedContext?: Context
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<TEntity[]> {
|
||||
return (await this.productCollectionRepository_.find(
|
||||
this.buildListQueryOptions(filters, config),
|
||||
@@ -44,10 +55,11 @@ export default class ProductCollectionService<
|
||||
)) as TEntity[]
|
||||
}
|
||||
|
||||
@InjectManager("productCollectionRepository_")
|
||||
async listAndCount(
|
||||
filters: ProductTypes.FilterableProductCollectionProps = {},
|
||||
config: FindConfig<ProductTypes.ProductCollectionDTO> = {},
|
||||
sharedContext?: Context
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<[TEntity[], number]> {
|
||||
return (await this.productCollectionRepository_.findAndCount(
|
||||
this.buildListQueryOptions(filters, config),
|
||||
@@ -72,4 +84,34 @@ export default class ProductCollectionService<
|
||||
|
||||
return queryOptions
|
||||
}
|
||||
|
||||
@InjectTransactionManager(shouldForceTransaction, "productCollectionRepository_")
|
||||
async create(
|
||||
data: ProductTypes.CreateProductCollectionDTO[],
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<TEntity[]> {
|
||||
return (await (this.productCollectionRepository_ as ProductCollectionRepository).create(
|
||||
data,
|
||||
sharedContext
|
||||
)) as TEntity[]
|
||||
}
|
||||
|
||||
@InjectTransactionManager(shouldForceTransaction, "productCollectionRepository_")
|
||||
async update(
|
||||
data: ProductTypes.UpdateProductCollectionDTO[],
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<TEntity[]> {
|
||||
return (await (this.productCollectionRepository_ as ProductCollectionRepository).update(
|
||||
data,
|
||||
sharedContext
|
||||
)) as TEntity[]
|
||||
}
|
||||
|
||||
@InjectTransactionManager(shouldForceTransaction, "productCollectionRepository_")
|
||||
async delete(
|
||||
ids: string[],
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<void> {
|
||||
await this.productCollectionRepository_.delete(ids, sharedContext)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ import { serialize } from "@mikro-orm/core"
|
||||
import ProductImageService from "./product-image"
|
||||
import { ProductServiceTypes, ProductVariantServiceTypes } from "../types/services"
|
||||
import {
|
||||
InjectManager,
|
||||
InjectTransactionManager,
|
||||
isDefined,
|
||||
isString,
|
||||
@@ -38,8 +39,10 @@ import {
|
||||
MedusaContext,
|
||||
MedusaError,
|
||||
} from "@medusajs/utils"
|
||||
|
||||
import { shouldForceTransaction } from "../utils"
|
||||
import { joinerConfig } from "./../joiner-config"
|
||||
import { ProductCategoryServiceTypes } from "../types"
|
||||
|
||||
type InjectedDependencies = {
|
||||
baseRepository: DAL.RepositoryService
|
||||
@@ -106,10 +109,11 @@ export default class ProductModuleService<
|
||||
return joinerConfig
|
||||
}
|
||||
|
||||
@InjectManager("baseRepository_")
|
||||
async list(
|
||||
filters: ProductTypes.FilterableProductProps = {},
|
||||
config: FindConfig<ProductTypes.ProductDTO> = {},
|
||||
sharedContext?: Context
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<ProductTypes.ProductDTO[]> {
|
||||
const products = await this.productService_.list(
|
||||
filters,
|
||||
@@ -120,10 +124,11 @@ export default class ProductModuleService<
|
||||
return JSON.parse(JSON.stringify(products))
|
||||
}
|
||||
|
||||
@InjectManager("baseRepository_")
|
||||
async retrieve(
|
||||
productId: string,
|
||||
config: FindConfig<ProductTypes.ProductDTO> = {},
|
||||
sharedContext?: Context
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<ProductTypes.ProductDTO> {
|
||||
const product = await this.productService_.retrieve(
|
||||
productId,
|
||||
@@ -134,10 +139,11 @@ export default class ProductModuleService<
|
||||
return JSON.parse(JSON.stringify(product))
|
||||
}
|
||||
|
||||
@InjectManager("baseRepository_")
|
||||
async listAndCount(
|
||||
filters: ProductTypes.FilterableProductProps = {},
|
||||
config: FindConfig<ProductTypes.ProductDTO> = {},
|
||||
sharedContext?: Context
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<[ProductTypes.ProductDTO[], number]> {
|
||||
const [products, count] = await this.productService_.listAndCount(
|
||||
filters,
|
||||
@@ -148,10 +154,11 @@ export default class ProductModuleService<
|
||||
return [JSON.parse(JSON.stringify(products)), count]
|
||||
}
|
||||
|
||||
@InjectManager("baseRepository_")
|
||||
async retrieveVariant(
|
||||
productVariantId: string,
|
||||
config: FindConfig<ProductTypes.ProductVariantDTO> = {},
|
||||
sharedContext?: Context
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<ProductTypes.ProductVariantDTO> {
|
||||
const productVariant = await this.productVariantService_.retrieve(
|
||||
productVariantId,
|
||||
@@ -162,10 +169,11 @@ export default class ProductModuleService<
|
||||
return JSON.parse(JSON.stringify(productVariant))
|
||||
}
|
||||
|
||||
@InjectManager("baseRepository_")
|
||||
async listVariants(
|
||||
filters: ProductTypes.FilterableProductVariantProps = {},
|
||||
config: FindConfig<ProductTypes.ProductVariantDTO> = {},
|
||||
sharedContext?: Context
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<ProductTypes.ProductVariantDTO[]> {
|
||||
const variants = await this.productVariantService_.list(
|
||||
filters,
|
||||
@@ -176,10 +184,11 @@ export default class ProductModuleService<
|
||||
return JSON.parse(JSON.stringify(variants))
|
||||
}
|
||||
|
||||
@InjectManager("baseRepository_")
|
||||
async listAndCountVariants(
|
||||
filters: ProductTypes.FilterableProductVariantProps = {},
|
||||
config: FindConfig<ProductTypes.ProductVariantDTO> = {},
|
||||
sharedContext?: Context
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<[ProductTypes.ProductVariantDTO[], number]> {
|
||||
const [variants, count] = await this.productVariantService_.listAndCount(
|
||||
filters,
|
||||
@@ -190,10 +199,26 @@ export default class ProductModuleService<
|
||||
return [JSON.parse(JSON.stringify(variants)), count]
|
||||
}
|
||||
|
||||
@InjectManager("baseRepository_")
|
||||
async retrieveTag(
|
||||
tagId: string,
|
||||
config: FindConfig<ProductTypes.ProductTagDTO> = {},
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<ProductTypes.ProductTagDTO> {
|
||||
const productTag = await this.productTagService_.retrieve(
|
||||
tagId,
|
||||
config,
|
||||
sharedContext
|
||||
)
|
||||
|
||||
return JSON.parse(JSON.stringify(productTag))
|
||||
}
|
||||
|
||||
@InjectManager("baseRepository_")
|
||||
async listTags(
|
||||
filters: ProductTypes.FilterableProductTagProps = {},
|
||||
config: FindConfig<ProductTypes.ProductTagDTO> = {},
|
||||
sharedContext?: Context
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<ProductTypes.ProductTagDTO[]> {
|
||||
const tags = await this.productTagService_.list(
|
||||
filters,
|
||||
@@ -204,10 +229,218 @@ export default class ProductModuleService<
|
||||
return JSON.parse(JSON.stringify(tags))
|
||||
}
|
||||
|
||||
@InjectManager("baseRepository_")
|
||||
async listAndCountTags(
|
||||
filters: ProductTypes.FilterableProductTagProps = {},
|
||||
config: FindConfig<ProductTypes.ProductTagDTO> = {},
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<[ProductTypes.ProductTagDTO[], number]> {
|
||||
const [tags, count] = await this.productTagService_.listAndCount(
|
||||
filters,
|
||||
config,
|
||||
sharedContext
|
||||
)
|
||||
|
||||
return [JSON.parse(JSON.stringify(tags)), count]
|
||||
}
|
||||
|
||||
@InjectTransactionManager(shouldForceTransaction, "baseRepository_")
|
||||
async createTags(
|
||||
data: ProductTypes.CreateProductTagDTO[],
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
) {
|
||||
const productTags = await this.productTagService_.create(
|
||||
data,
|
||||
sharedContext
|
||||
)
|
||||
|
||||
return JSON.parse(JSON.stringify(productTags))
|
||||
}
|
||||
|
||||
@InjectTransactionManager(shouldForceTransaction, "baseRepository_")
|
||||
async updateTags(
|
||||
data: ProductTypes.UpdateProductTagDTO[],
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
) {
|
||||
const productTags = await this.productTagService_.update(
|
||||
data,
|
||||
sharedContext
|
||||
)
|
||||
|
||||
return JSON.parse(JSON.stringify(productTags))
|
||||
}
|
||||
|
||||
@InjectTransactionManager(shouldForceTransaction, "baseRepository_")
|
||||
async deleteTags(
|
||||
productTagIds: string[],
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<void> {
|
||||
await this.productTagService_.delete(productTagIds, sharedContext)
|
||||
}
|
||||
|
||||
@InjectManager("baseRepository_")
|
||||
async retrieveType(
|
||||
typeId: string,
|
||||
config: FindConfig<ProductTypes.ProductTypeDTO> = {},
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<ProductTypes.ProductTypeDTO> {
|
||||
const productType = await this.productTypeService_.retrieve(
|
||||
typeId,
|
||||
config,
|
||||
sharedContext
|
||||
)
|
||||
|
||||
return JSON.parse(JSON.stringify(productType))
|
||||
}
|
||||
|
||||
@InjectManager("baseRepository_")
|
||||
async listTypes(
|
||||
filters: ProductTypes.FilterableProductTypeProps = {},
|
||||
config: FindConfig<ProductTypes.ProductTypeDTO> = {},
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<ProductTypes.ProductTypeDTO[]> {
|
||||
const types = await this.productTypeService_.list(
|
||||
filters,
|
||||
config,
|
||||
sharedContext
|
||||
)
|
||||
|
||||
return JSON.parse(JSON.stringify(types))
|
||||
}
|
||||
|
||||
@InjectManager("baseRepository_")
|
||||
async listAndCountTypes(
|
||||
filters: ProductTypes.FilterableProductTypeProps = {},
|
||||
config: FindConfig<ProductTypes.ProductTypeDTO> = {},
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<[ProductTypes.ProductTypeDTO[], number]> {
|
||||
const [types, count] = await this.productTypeService_.listAndCount(
|
||||
filters,
|
||||
config,
|
||||
sharedContext
|
||||
)
|
||||
|
||||
return [JSON.parse(JSON.stringify(types)), count]
|
||||
}
|
||||
|
||||
@InjectTransactionManager(shouldForceTransaction, "baseRepository_")
|
||||
async createTypes(
|
||||
data: ProductTypes.CreateProductTypeDTO[],
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
) {
|
||||
const productTypes = await this.productTypeService_.create(
|
||||
data,
|
||||
sharedContext
|
||||
)
|
||||
|
||||
return JSON.parse(JSON.stringify(productTypes))
|
||||
}
|
||||
|
||||
@InjectTransactionManager(shouldForceTransaction, "baseRepository_")
|
||||
async updateTypes(
|
||||
data: ProductTypes.UpdateProductTypeDTO[],
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
) {
|
||||
const productTypes = await this.productTypeService_.update(
|
||||
data,
|
||||
sharedContext
|
||||
)
|
||||
|
||||
return JSON.parse(JSON.stringify(productTypes))
|
||||
}
|
||||
|
||||
@InjectTransactionManager(shouldForceTransaction, "baseRepository_")
|
||||
async deleteTypes(
|
||||
productTypeIds: string[],
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<void> {
|
||||
await this.productTypeService_.delete(productTypeIds, sharedContext)
|
||||
}
|
||||
|
||||
@InjectManager("baseRepository_")
|
||||
async retrieveOption(
|
||||
optionId: string,
|
||||
config: FindConfig<ProductTypes.ProductOptionDTO> = {},
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<ProductTypes.ProductOptionDTO> {
|
||||
const productOptions = await this.productOptionService_.retrieve(
|
||||
optionId,
|
||||
config,
|
||||
sharedContext
|
||||
)
|
||||
|
||||
return JSON.parse(JSON.stringify(productOptions))
|
||||
}
|
||||
|
||||
@InjectManager("baseRepository_")
|
||||
async listOptions(
|
||||
filters: ProductTypes.FilterableProductTypeProps = {},
|
||||
config: FindConfig<ProductTypes.ProductOptionDTO> = {},
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<ProductTypes.ProductOptionDTO[]> {
|
||||
const productOptions = await this.productOptionService_.list(
|
||||
filters,
|
||||
config,
|
||||
sharedContext
|
||||
)
|
||||
|
||||
return JSON.parse(JSON.stringify(productOptions))
|
||||
}
|
||||
|
||||
@InjectManager("baseRepository_")
|
||||
async listAndCountOptions(
|
||||
filters: ProductTypes.FilterableProductTypeProps = {},
|
||||
config: FindConfig<ProductTypes.ProductOptionDTO> = {},
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<[ProductTypes.ProductOptionDTO[], number]> {
|
||||
const [productOptions, count] = await this.productOptionService_.listAndCount(
|
||||
filters,
|
||||
config,
|
||||
sharedContext
|
||||
)
|
||||
|
||||
return [JSON.parse(JSON.stringify(productOptions)), count]
|
||||
}
|
||||
|
||||
@InjectTransactionManager(shouldForceTransaction, "baseRepository_")
|
||||
async createOptions(
|
||||
data: ProductTypes.CreateProductOptionDTO[],
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
) {
|
||||
const productOptions = await this.productOptionService_.create(
|
||||
data,
|
||||
sharedContext
|
||||
)
|
||||
|
||||
return JSON.parse(JSON.stringify(productOptions))
|
||||
}
|
||||
|
||||
@InjectTransactionManager(shouldForceTransaction, "baseRepository_")
|
||||
async updateOptions(
|
||||
data: ProductTypes.UpdateProductOptionDTO[],
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
) {
|
||||
const productOptions = await this.productOptionService_.update(
|
||||
data,
|
||||
sharedContext
|
||||
)
|
||||
|
||||
return JSON.parse(JSON.stringify(productOptions))
|
||||
}
|
||||
|
||||
@InjectTransactionManager(shouldForceTransaction, "baseRepository_")
|
||||
async deleteOptions(
|
||||
productOptionIds: string[],
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<void> {
|
||||
await this.productOptionService_.delete(productOptionIds, sharedContext)
|
||||
}
|
||||
|
||||
@InjectManager("baseRepository_")
|
||||
async retrieveCollection(
|
||||
productCollectionId: string,
|
||||
config: FindConfig<ProductTypes.ProductCollectionDTO> = {},
|
||||
sharedContext?: Context
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<ProductTypes.ProductCollectionDTO> {
|
||||
const productCollection = await this.productCollectionService_.retrieve(
|
||||
productCollectionId,
|
||||
@@ -218,10 +451,11 @@ export default class ProductModuleService<
|
||||
return JSON.parse(JSON.stringify(productCollection))
|
||||
}
|
||||
|
||||
@InjectManager("baseRepository_")
|
||||
async listCollections(
|
||||
filters: ProductTypes.FilterableProductCollectionProps = {},
|
||||
config: FindConfig<ProductTypes.ProductCollectionDTO> = {},
|
||||
sharedContext?: Context
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<ProductTypes.ProductCollectionDTO[]> {
|
||||
const collections = await this.productCollectionService_.list(
|
||||
filters,
|
||||
@@ -232,10 +466,11 @@ export default class ProductModuleService<
|
||||
return JSON.parse(JSON.stringify(collections))
|
||||
}
|
||||
|
||||
@InjectManager("baseRepository_")
|
||||
async listAndCountCollections(
|
||||
filters: ProductTypes.FilterableProductCollectionProps = {},
|
||||
config: FindConfig<ProductTypes.ProductCollectionDTO> = {},
|
||||
sharedContext?: Context
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<[ProductTypes.ProductCollectionDTO[], number]> {
|
||||
const collections = await this.productCollectionService_.listAndCount(
|
||||
filters,
|
||||
@@ -246,10 +481,48 @@ export default class ProductModuleService<
|
||||
return JSON.parse(JSON.stringify(collections))
|
||||
}
|
||||
|
||||
@InjectTransactionManager(shouldForceTransaction, "baseRepository_")
|
||||
async createCollections(
|
||||
data: ProductTypes.CreateProductCollectionDTO[],
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
) {
|
||||
const productCollections = await this.productCollectionService_.create(
|
||||
data,
|
||||
sharedContext
|
||||
)
|
||||
|
||||
return JSON.parse(JSON.stringify(productCollections))
|
||||
}
|
||||
|
||||
@InjectTransactionManager(shouldForceTransaction, "baseRepository_")
|
||||
async updateCollections(
|
||||
data: ProductTypes.UpdateProductCollectionDTO[],
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
) {
|
||||
const productCollections = await this.productCollectionService_.update(
|
||||
data,
|
||||
sharedContext
|
||||
)
|
||||
|
||||
return JSON.parse(JSON.stringify(productCollections))
|
||||
}
|
||||
|
||||
@InjectTransactionManager(shouldForceTransaction, "baseRepository_")
|
||||
async deleteCollections(
|
||||
productCollectionIds: string[],
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<void> {
|
||||
await this.productCollectionService_.delete(
|
||||
productCollectionIds,
|
||||
sharedContext
|
||||
)
|
||||
}
|
||||
|
||||
@InjectManager("baseRepository_")
|
||||
async retrieveCategory(
|
||||
productCategoryId: string,
|
||||
config: FindConfig<ProductTypes.ProductCategoryDTO> = {},
|
||||
sharedContext?: Context
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<ProductTypes.ProductCategoryDTO> {
|
||||
const productCategory = await this.productCategoryService_.retrieve(
|
||||
productCategoryId,
|
||||
@@ -260,10 +533,11 @@ export default class ProductModuleService<
|
||||
return JSON.parse(JSON.stringify(productCategory))
|
||||
}
|
||||
|
||||
@InjectManager("baseRepository_")
|
||||
async listCategories(
|
||||
filters: ProductTypes.FilterableProductCategoryProps = {},
|
||||
config: FindConfig<ProductTypes.ProductCategoryDTO> = {},
|
||||
sharedContext?: Context
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<ProductTypes.ProductCategoryDTO[]> {
|
||||
const categories = await this.productCategoryService_.list(
|
||||
filters,
|
||||
@@ -274,10 +548,47 @@ export default class ProductModuleService<
|
||||
return JSON.parse(JSON.stringify(categories))
|
||||
}
|
||||
|
||||
@InjectTransactionManager(shouldForceTransaction, "baseRepository_")
|
||||
async createCategory(
|
||||
data: ProductCategoryServiceTypes.CreateProductCategoryDTO,
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
) {
|
||||
const productCategory = await this.productCategoryService_.create(
|
||||
data,
|
||||
sharedContext
|
||||
)
|
||||
|
||||
return JSON.parse(JSON.stringify(productCategory))
|
||||
}
|
||||
|
||||
@InjectTransactionManager(shouldForceTransaction, "baseRepository_")
|
||||
async updateCategory(
|
||||
categoryId: string,
|
||||
data: ProductCategoryServiceTypes.UpdateProductCategoryDTO,
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
) {
|
||||
const productCategory = await this.productCategoryService_.update(
|
||||
categoryId,
|
||||
data,
|
||||
sharedContext
|
||||
)
|
||||
|
||||
return JSON.parse(JSON.stringify(productCategory))
|
||||
}
|
||||
|
||||
@InjectTransactionManager(shouldForceTransaction, "baseRepository_")
|
||||
async deleteCategory(
|
||||
categoryId: string,
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<void> {
|
||||
await this.productCategoryService_.delete(categoryId, sharedContext)
|
||||
}
|
||||
|
||||
@InjectManager("baseRepository_")
|
||||
async listAndCountCategories(
|
||||
filters: ProductTypes.FilterableProductCategoryProps = {},
|
||||
config: FindConfig<ProductTypes.ProductCategoryDTO> = {},
|
||||
sharedContext?: Context
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<[ProductTypes.ProductCategoryDTO[], number]> {
|
||||
const categories = await this.productCategoryService_.listAndCount(
|
||||
filters,
|
||||
@@ -298,7 +609,7 @@ export default class ProductModuleService<
|
||||
|
||||
async update(
|
||||
data: ProductTypes.UpdateProductDTO[],
|
||||
sharedContext?: Context
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<ProductTypes.ProductDTO[]> {
|
||||
const products = await this.update_(data, sharedContext)
|
||||
|
||||
@@ -368,10 +679,17 @@ export default class ProductModuleService<
|
||||
const productOptionsData = [...productOptionsMap]
|
||||
.map(([handle, options]) => {
|
||||
return options.map((option) => {
|
||||
return {
|
||||
...option,
|
||||
product: productByHandleMap.get(handle)!,
|
||||
const productOptionsData: ProductTypes.CreateProductOptionOnlyDTO = { ...option }
|
||||
const product = productByHandleMap.get(handle)
|
||||
const productId = product?.id!
|
||||
|
||||
if (productId) {
|
||||
productOptionsData.product_id = productId
|
||||
} else if (product) {
|
||||
productOptionsData.product = product
|
||||
}
|
||||
|
||||
return productOptionsData
|
||||
})
|
||||
})
|
||||
.flat()
|
||||
@@ -614,12 +932,12 @@ export default class ProductModuleService<
|
||||
if (isDefined(productData.type)) {
|
||||
const productType = (
|
||||
await this.productTypeService_.upsert(
|
||||
[productData.type as ProductTypes.CreateProductTypeDTO],
|
||||
[productData.type],
|
||||
sharedContext
|
||||
)
|
||||
)
|
||||
|
||||
productData.type = productType?.[0]
|
||||
productData.type_id = productType?.[0]?.id
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,20 @@
|
||||
import { ProductOption } from "@models"
|
||||
import { Context, DAL, ProductTypes } from "@medusajs/types"
|
||||
import {
|
||||
Context,
|
||||
DAL,
|
||||
FindConfig,
|
||||
ProductTypes,
|
||||
} from "@medusajs/types"
|
||||
import { ProductOptionRepository } from "@repositories"
|
||||
import { InjectTransactionManager, MedusaContext } from "@medusajs/utils"
|
||||
import { doNotForceTransaction } from "../utils"
|
||||
import {
|
||||
InjectTransactionManager,
|
||||
InjectManager,
|
||||
MedusaContext,
|
||||
ModulesSdkUtils,
|
||||
retrieveEntity,
|
||||
} from "@medusajs/utils"
|
||||
|
||||
import { doNotForceTransaction, shouldForceTransaction } from "../utils"
|
||||
|
||||
type InjectedDependencies = {
|
||||
productOptionRepository: DAL.RepositoryService
|
||||
@@ -18,7 +30,62 @@ export default class ProductOptionService<
|
||||
productOptionRepository as ProductOptionRepository
|
||||
}
|
||||
|
||||
@InjectTransactionManager(doNotForceTransaction, "productOptionRepository_")
|
||||
@InjectManager("productOptionRepository_")
|
||||
async retrieve(
|
||||
productOptionId: string,
|
||||
config: FindConfig<ProductTypes.ProductOptionDTO> = {},
|
||||
@MedusaContext() sharedContext?: Context
|
||||
): Promise<TEntity> {
|
||||
return (await retrieveEntity<
|
||||
ProductOption,
|
||||
ProductTypes.ProductOptionDTO
|
||||
>({
|
||||
id: productOptionId,
|
||||
entityName: ProductOption.name,
|
||||
repository: this.productOptionRepository_,
|
||||
config,
|
||||
sharedContext,
|
||||
})) as TEntity
|
||||
}
|
||||
|
||||
@InjectManager("productOptionRepository_")
|
||||
async list(
|
||||
filters: ProductTypes.FilterableProductOptionProps = {},
|
||||
config: FindConfig<ProductTypes.ProductOptionDTO> = {},
|
||||
@MedusaContext() sharedContext?: Context
|
||||
): Promise<TEntity[]> {
|
||||
return (await this.productOptionRepository_.find(
|
||||
this.buildQueryForList(filters, config),
|
||||
sharedContext
|
||||
)) as TEntity[]
|
||||
}
|
||||
|
||||
@InjectManager("productOptionRepository_")
|
||||
async listAndCount(
|
||||
filters: ProductTypes.FilterableProductOptionProps = {},
|
||||
config: FindConfig<ProductTypes.ProductOptionDTO> = {},
|
||||
@MedusaContext() sharedContext?: Context
|
||||
): Promise<[TEntity[], number]> {
|
||||
return (await this.productOptionRepository_.findAndCount(
|
||||
this.buildQueryForList(filters, config),
|
||||
sharedContext
|
||||
)) as [TEntity[], number]
|
||||
}
|
||||
|
||||
private buildQueryForList(
|
||||
filters: ProductTypes.FilterableProductOptionProps = {},
|
||||
config: FindConfig<ProductTypes.ProductOptionDTO> = {},
|
||||
) {
|
||||
const queryOptions = ModulesSdkUtils.buildQuery<ProductOption>(filters, config)
|
||||
|
||||
if (filters.title) {
|
||||
queryOptions.where["title"] = { $ilike: filters.title }
|
||||
}
|
||||
|
||||
return queryOptions
|
||||
}
|
||||
|
||||
@InjectTransactionManager(shouldForceTransaction, "productOptionRepository_")
|
||||
async create(
|
||||
data: ProductTypes.CreateProductOptionOnlyDTO[],
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
@@ -29,4 +96,23 @@ export default class ProductOptionService<
|
||||
transactionManager: sharedContext.transactionManager,
|
||||
})) as TEntity[]
|
||||
}
|
||||
|
||||
@InjectTransactionManager(shouldForceTransaction, "productOptionRepository_")
|
||||
async update(
|
||||
data: ProductTypes.UpdateProductOptionDTO[],
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<TEntity[]> {
|
||||
return (await (this.productOptionRepository_ as ProductOptionRepository).update(
|
||||
data,
|
||||
sharedContext
|
||||
)) as TEntity[]
|
||||
}
|
||||
|
||||
@InjectTransactionManager(doNotForceTransaction, "productOptionRepository_")
|
||||
async delete(
|
||||
ids: string[],
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<void> {
|
||||
return await this.productOptionRepository_.delete(ids, sharedContext)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,18 +2,23 @@ import { ProductTag } from "@models"
|
||||
import {
|
||||
Context,
|
||||
CreateProductTagDTO,
|
||||
UpdateProductTagDTO,
|
||||
UpsertProductTagDTO,
|
||||
DAL,
|
||||
FindConfig,
|
||||
ProductTypes,
|
||||
} from "@medusajs/types"
|
||||
import {
|
||||
InjectTransactionManager,
|
||||
InjectManager,
|
||||
MedusaContext,
|
||||
ModulesSdkUtils,
|
||||
retrieveEntity,
|
||||
} from "@medusajs/utils"
|
||||
import { doNotForceTransaction } from "../utils"
|
||||
import { ProductTagRepository } from "@repositories"
|
||||
|
||||
import { doNotForceTransaction, shouldForceTransaction } from "../utils"
|
||||
|
||||
type InjectedDependencies = {
|
||||
productTagRepository: DAL.RepositoryService
|
||||
}
|
||||
@@ -27,30 +32,98 @@ export default class ProductTagService<
|
||||
this.productTagRepository_ = productTagRepository
|
||||
}
|
||||
|
||||
@InjectManager("productTagRepository_")
|
||||
async retrieve(
|
||||
productTagId: string,
|
||||
config: FindConfig<ProductTypes.ProductTagDTO> = {},
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<TEntity> {
|
||||
return (await retrieveEntity<
|
||||
ProductTag,
|
||||
ProductTypes.ProductTagDTO
|
||||
>({
|
||||
id: productTagId,
|
||||
entityName: ProductTag.name,
|
||||
repository: this.productTagRepository_,
|
||||
config,
|
||||
sharedContext,
|
||||
})) as TEntity
|
||||
}
|
||||
|
||||
@InjectManager("productTagRepository_")
|
||||
async list(
|
||||
filters: ProductTypes.FilterableProductTagProps = {},
|
||||
config: FindConfig<ProductTypes.ProductTagDTO> = {},
|
||||
sharedContext?: Context
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<TEntity[]> {
|
||||
return (await this.productTagRepository_.find(
|
||||
this.buildQueryForList(filters, config),
|
||||
sharedContext
|
||||
)) as TEntity[]
|
||||
}
|
||||
|
||||
@InjectManager("productTagRepository_")
|
||||
async listAndCount(
|
||||
filters: ProductTypes.FilterableProductTagProps = {},
|
||||
config: FindConfig<ProductTypes.ProductTagDTO> = {},
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<[TEntity[], number]> {
|
||||
return (await this.productTagRepository_.findAndCount(
|
||||
this.buildQueryForList(filters, config),
|
||||
sharedContext
|
||||
)) as [TEntity[], number]
|
||||
}
|
||||
|
||||
private buildQueryForList(
|
||||
filters: ProductTypes.FilterableProductTagProps = {},
|
||||
config: FindConfig<ProductTypes.ProductTagDTO> = {},
|
||||
) {
|
||||
const queryOptions = ModulesSdkUtils.buildQuery<ProductTag>(filters, config)
|
||||
|
||||
if (filters.value) {
|
||||
queryOptions.where["value"] = { $ilike: filters.value }
|
||||
}
|
||||
|
||||
return (await this.productTagRepository_.find(
|
||||
queryOptions,
|
||||
return queryOptions
|
||||
}
|
||||
|
||||
@InjectTransactionManager(shouldForceTransaction, "productTagRepository_")
|
||||
async create(
|
||||
data: CreateProductTagDTO[],
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<TEntity[]> {
|
||||
return (await (this.productTagRepository_ as ProductTagRepository).create(
|
||||
data,
|
||||
sharedContext
|
||||
)) as TEntity[]
|
||||
}
|
||||
|
||||
@InjectTransactionManager(shouldForceTransaction, "productTagRepository_")
|
||||
async update(
|
||||
data: UpdateProductTagDTO[],
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<TEntity[]> {
|
||||
return (await (this.productTagRepository_ as ProductTagRepository).update(
|
||||
data,
|
||||
sharedContext
|
||||
)) as TEntity[]
|
||||
}
|
||||
|
||||
@InjectTransactionManager(doNotForceTransaction, "productTagRepository_")
|
||||
async delete(
|
||||
ids: string[],
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<void> {
|
||||
await this.productTagRepository_.delete(ids, sharedContext)
|
||||
}
|
||||
|
||||
@InjectTransactionManager(doNotForceTransaction, "productTagRepository_")
|
||||
async upsert(
|
||||
tags: CreateProductTagDTO[],
|
||||
data: UpsertProductTagDTO[],
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<TEntity[]> {
|
||||
return (await (this.productTagRepository_ as ProductTagRepository).upsert!(
|
||||
tags,
|
||||
data,
|
||||
sharedContext
|
||||
)) as TEntity[]
|
||||
}
|
||||
|
||||
@@ -1,8 +1,23 @@
|
||||
import { ProductType } from "@models"
|
||||
import { Context, CreateProductTypeDTO, DAL } from "@medusajs/types"
|
||||
import { InjectTransactionManager, MedusaContext } from "@medusajs/utils"
|
||||
import { doNotForceTransaction } from "../utils"
|
||||
import {
|
||||
Context,
|
||||
CreateProductTypeDTO,
|
||||
UpsertProductTypeDTO,
|
||||
UpdateProductTypeDTO,
|
||||
DAL,
|
||||
FindConfig,
|
||||
ProductTypes
|
||||
} from "@medusajs/types"
|
||||
import { ProductTypeRepository } from "@repositories"
|
||||
import {
|
||||
InjectManager,
|
||||
InjectTransactionManager,
|
||||
MedusaContext,
|
||||
ModulesSdkUtils,
|
||||
retrieveEntity,
|
||||
} from "@medusajs/utils"
|
||||
|
||||
import { doNotForceTransaction, shouldForceTransaction } from "../utils"
|
||||
|
||||
type InjectedDependencies = {
|
||||
productTypeRepository: DAL.RepositoryService
|
||||
@@ -17,12 +32,97 @@ export default class ProductTypeService<
|
||||
this.productTypeRepository_ = productTypeRepository
|
||||
}
|
||||
|
||||
@InjectManager("productTypeRepository_")
|
||||
async retrieve(
|
||||
productTypeId: string,
|
||||
config: FindConfig<ProductTypes.ProductTypeDTO> = {},
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<TEntity> {
|
||||
return (await retrieveEntity<
|
||||
ProductType,
|
||||
ProductTypes.ProductTypeDTO
|
||||
>({
|
||||
id: productTypeId,
|
||||
entityName: ProductType.name,
|
||||
repository: this.productTypeRepository_,
|
||||
config,
|
||||
sharedContext,
|
||||
})) as TEntity
|
||||
}
|
||||
|
||||
@InjectManager("productTypeRepository_")
|
||||
async list(
|
||||
filters: ProductTypes.FilterableProductTypeProps = {},
|
||||
config: FindConfig<ProductTypes.ProductTypeDTO> = {},
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<TEntity[]> {
|
||||
return (await this.productTypeRepository_.find(
|
||||
this.buildQueryForList(filters, config),
|
||||
sharedContext
|
||||
)) as TEntity[]
|
||||
}
|
||||
|
||||
@InjectManager("productTypeRepository_")
|
||||
async listAndCount(
|
||||
filters: ProductTypes.FilterableProductTypeProps = {},
|
||||
config: FindConfig<ProductTypes.ProductTypeDTO> = {},
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<[TEntity[], number]> {
|
||||
return (await this.productTypeRepository_.findAndCount(
|
||||
this.buildQueryForList(filters, config),
|
||||
sharedContext
|
||||
)) as [TEntity[], number]
|
||||
}
|
||||
|
||||
private buildQueryForList(
|
||||
filters: ProductTypes.FilterableProductTypeProps = {},
|
||||
config: FindConfig<ProductTypes.ProductTypeDTO> = {},
|
||||
) {
|
||||
const queryOptions = ModulesSdkUtils.buildQuery<ProductType>(filters, config)
|
||||
|
||||
if (filters.value) {
|
||||
queryOptions.where["value"] = { $ilike: filters.value }
|
||||
}
|
||||
|
||||
return queryOptions
|
||||
}
|
||||
|
||||
@InjectTransactionManager(doNotForceTransaction, "productTypeRepository_")
|
||||
async upsert(
|
||||
types: CreateProductTypeDTO[],
|
||||
types: UpsertProductTypeDTO[],
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<TEntity[]> {
|
||||
return (await (this.productTypeRepository_ as ProductTypeRepository)
|
||||
.upsert!(types, sharedContext)) as TEntity[]
|
||||
}
|
||||
|
||||
@InjectTransactionManager(shouldForceTransaction, "productTypeRepository_")
|
||||
async create(
|
||||
data: CreateProductTypeDTO[],
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<TEntity[]> {
|
||||
return (await (this.productTypeRepository_ as ProductTypeRepository).create(
|
||||
data,
|
||||
sharedContext
|
||||
)) as TEntity[]
|
||||
}
|
||||
|
||||
@InjectTransactionManager(shouldForceTransaction, "productTypeRepository_")
|
||||
async update(
|
||||
data: UpdateProductTypeDTO[],
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<TEntity[]> {
|
||||
return (await (this.productTypeRepository_ as ProductTypeRepository).update(
|
||||
data,
|
||||
sharedContext
|
||||
)) as TEntity[]
|
||||
}
|
||||
|
||||
@InjectTransactionManager(doNotForceTransaction, "productTypeRepository_")
|
||||
async delete(
|
||||
ids: string[],
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<void> {
|
||||
await this.productTypeRepository_.delete(ids, sharedContext)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Product, ProductVariant } from "@models"
|
||||
import { Context, DAL, FindConfig, ProductTypes } from "@medusajs/types"
|
||||
import { ProductVariantRepository } from "@repositories"
|
||||
import {
|
||||
InjectManager,
|
||||
InjectTransactionManager,
|
||||
isString,
|
||||
MedusaContext,
|
||||
@@ -33,10 +34,11 @@ export default class ProductVariantService<
|
||||
this.productService_ = productService
|
||||
}
|
||||
|
||||
@InjectManager("productVariantRepository_")
|
||||
async retrieve(
|
||||
productVariantId: string,
|
||||
config: FindConfig<ProductTypes.ProductVariantDTO> = {},
|
||||
sharedContext?: Context
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<TEntity> {
|
||||
return (await retrieveEntity<
|
||||
ProductVariant,
|
||||
@@ -50,10 +52,11 @@ export default class ProductVariantService<
|
||||
})) as TEntity
|
||||
}
|
||||
|
||||
@InjectManager("productVariantRepository_")
|
||||
async list(
|
||||
filters: ProductTypes.FilterableProductVariantProps = {},
|
||||
config: FindConfig<ProductTypes.ProductVariantDTO> = {},
|
||||
sharedContext?: Context
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<TEntity[]> {
|
||||
const queryOptions = ModulesSdkUtils.buildQuery<ProductVariant>(
|
||||
filters,
|
||||
@@ -66,10 +69,11 @@ export default class ProductVariantService<
|
||||
)) as TEntity[]
|
||||
}
|
||||
|
||||
@InjectManager("productVariantRepository_")
|
||||
async listAndCount(
|
||||
filters: ProductTypes.FilterableProductVariantProps = {},
|
||||
config: FindConfig<ProductTypes.ProductVariantDTO> = {},
|
||||
sharedContext?: Context
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<[TEntity[], number]> {
|
||||
const queryOptions = ModulesSdkUtils.buildQuery<ProductVariant>(
|
||||
filters,
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
WithRequiredProperty,
|
||||
} from "@medusajs/types"
|
||||
import {
|
||||
InjectManager,
|
||||
InjectTransactionManager,
|
||||
MedusaContext,
|
||||
MedusaError,
|
||||
@@ -30,10 +31,11 @@ export default class ProductService<TEntity extends Product = Product> {
|
||||
this.productRepository_ = productRepository
|
||||
}
|
||||
|
||||
@InjectManager("productRepository_")
|
||||
async retrieve(
|
||||
productId: string,
|
||||
config: FindConfig<ProductTypes.ProductDTO> = {},
|
||||
sharedContext?: Context
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<TEntity> {
|
||||
if (!isDefined(productId)) {
|
||||
throw new MedusaError(
|
||||
@@ -61,10 +63,11 @@ export default class ProductService<TEntity extends Product = Product> {
|
||||
return product[0] as TEntity
|
||||
}
|
||||
|
||||
@InjectManager("productRepository_")
|
||||
async list(
|
||||
filters: ProductTypes.FilterableProductProps = {},
|
||||
config: FindConfig<ProductTypes.ProductDTO> = {},
|
||||
sharedContext?: Context
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<TEntity[]> {
|
||||
if (filters.category_ids) {
|
||||
if (Array.isArray(filters.category_ids)) {
|
||||
@@ -86,10 +89,11 @@ export default class ProductService<TEntity extends Product = Product> {
|
||||
)) as TEntity[]
|
||||
}
|
||||
|
||||
@InjectManager("productRepository_")
|
||||
async listAndCount(
|
||||
filters: ProductTypes.FilterableProductProps = {},
|
||||
config: FindConfig<ProductTypes.ProductDTO> = {},
|
||||
sharedContext?: Context
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<[TEntity[], number]> {
|
||||
if (filters.category_ids) {
|
||||
if (Array.isArray(filters.category_ids)) {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
export * from "./services"
|
||||
|
||||
import { IEventBusService } from "@medusajs/types"
|
||||
|
||||
export type InitializeModuleInjectableDependencies = {
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
export * as ProductCategoryServiceTypes from "./product-category"
|
||||
export * as ProductServiceTypes from "./product"
|
||||
export * as ProductVariantServiceTypes from "./product-variant"
|
||||
|
||||
19
packages/product/src/types/services/product-category.ts
Normal file
19
packages/product/src/types/services/product-category.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
export interface CreateProductCategoryDTO {
|
||||
name: string
|
||||
handle?: string
|
||||
is_active?: boolean
|
||||
is_internal?: boolean
|
||||
rank?: number
|
||||
parent_category_id: string | null
|
||||
metadata?: Record<string, unknown>
|
||||
}
|
||||
|
||||
export interface UpdateProductCategoryDTO {
|
||||
name?: string
|
||||
handle?: string
|
||||
is_active?: boolean
|
||||
is_internal?: boolean
|
||||
rank?: number
|
||||
parent_category_id?: string | null
|
||||
metadata?: Record<string, unknown>
|
||||
}
|
||||
@@ -7,21 +7,27 @@ import { Context } from "../shared-context"
|
||||
* This layer helps to separate the business logic (service layer) from accessing the
|
||||
* ORM directly and allows to switch to another ORM without changing the business logic.
|
||||
*/
|
||||
export interface RepositoryService<T = any> {
|
||||
transaction(
|
||||
task: (transactionManager: unknown) => Promise<any>,
|
||||
interface BaseRepositoryService<T = any> {
|
||||
transaction<TManager = unknown>(
|
||||
task: (transactionManager: TManager) => Promise<any>,
|
||||
context?: {
|
||||
isolationLevel?: string
|
||||
transaction?: unknown
|
||||
transaction?: TManager
|
||||
enableNestedTransactions?: boolean
|
||||
}
|
||||
): Promise<any>
|
||||
|
||||
getFreshManager<TManager = unknown>(): TManager
|
||||
|
||||
getActiveManager<TManager = unknown>(): TManager
|
||||
|
||||
serialize<TOutput extends object | object[]>(
|
||||
data: any,
|
||||
options?: any
|
||||
): Promise<TOutput>
|
||||
}
|
||||
|
||||
export interface RepositoryService<T = any> extends BaseRepositoryService<T> {
|
||||
find(options?: FindOptions<T>, context?: Context): Promise<T[]>
|
||||
|
||||
findAndCount(
|
||||
@@ -44,7 +50,7 @@ export interface RepositoryService<T = any> {
|
||||
restore(ids: string[], context?: Context): Promise<T[]>
|
||||
}
|
||||
|
||||
export interface TreeRepositoryService<T = any> extends RepositoryService<T> {
|
||||
export interface TreeRepositoryService<T = any> extends BaseRepositoryService<T> {
|
||||
find(
|
||||
options?: FindOptions<T>,
|
||||
transformOptions?: RepositoryTransformOptions,
|
||||
@@ -56,4 +62,8 @@ export interface TreeRepositoryService<T = any> extends RepositoryService<T> {
|
||||
transformOptions?: RepositoryTransformOptions,
|
||||
context?: Context
|
||||
): Promise<[T[], number]>
|
||||
|
||||
create(data: unknown, context?: Context): Promise<T>
|
||||
|
||||
delete(id: string, context?: Context): Promise<void>
|
||||
}
|
||||
|
||||
@@ -85,6 +85,26 @@ export interface ProductCategoryDTO {
|
||||
updated_at: string | Date
|
||||
}
|
||||
|
||||
export interface CreateProductCategoryDTO {
|
||||
name: string
|
||||
handle?: string
|
||||
is_active?: boolean
|
||||
is_internal?: boolean
|
||||
rank?: number
|
||||
parent_category_id: string | null
|
||||
metadata?: Record<string, unknown>
|
||||
}
|
||||
|
||||
export interface UpdateProductCategoryDTO {
|
||||
name?: string
|
||||
handle?: string
|
||||
is_active?: boolean
|
||||
is_internal?: boolean
|
||||
rank?: number
|
||||
parent_category_id?: string | null
|
||||
metadata?: Record<string, unknown>
|
||||
}
|
||||
|
||||
export interface ProductTagDTO {
|
||||
id: string
|
||||
value: string
|
||||
@@ -153,6 +173,19 @@ export interface FilterableProductTagProps
|
||||
value?: string
|
||||
}
|
||||
|
||||
export interface FilterableProductTypeProps
|
||||
extends BaseFilterable<FilterableProductTypeProps> {
|
||||
id?: string | string[]
|
||||
value?: string
|
||||
}
|
||||
|
||||
export interface FilterableProductOptionProps
|
||||
extends BaseFilterable<FilterableProductOptionProps> {
|
||||
id?: string | string[]
|
||||
title?: string
|
||||
product_id?: string | string[]
|
||||
}
|
||||
|
||||
export interface FilterableProductCollectionProps
|
||||
extends BaseFilterable<FilterableProductCollectionProps> {
|
||||
id?: string | string[]
|
||||
@@ -170,6 +203,7 @@ export interface FilterableProductVariantProps
|
||||
export interface FilterableProductCategoryProps
|
||||
extends BaseFilterable<FilterableProductCategoryProps> {
|
||||
id?: string | string[]
|
||||
name?: string | string[]
|
||||
parent_category_id?: string | string[] | null
|
||||
handle?: string | string[]
|
||||
is_active?: boolean
|
||||
@@ -181,18 +215,63 @@ export interface FilterableProductCategoryProps
|
||||
* Write DTO (module API input)
|
||||
*/
|
||||
|
||||
export interface CreateProductCollectionDTO {
|
||||
title: string
|
||||
handle?: string
|
||||
products?: ProductDTO[]
|
||||
metadata?: Record<string, unknown>
|
||||
}
|
||||
|
||||
export interface UpdateProductCollectionDTO {
|
||||
id: string
|
||||
value?: string
|
||||
title?: string
|
||||
handle?: string
|
||||
products?: ProductDTO[]
|
||||
metadata?: Record<string, unknown>
|
||||
}
|
||||
|
||||
export interface CreateProductTypeDTO {
|
||||
id?: string
|
||||
value: string
|
||||
metadata?: Record<string, unknown>
|
||||
}
|
||||
|
||||
export interface UpsertProductTypeDTO {
|
||||
id?: string
|
||||
value: string
|
||||
}
|
||||
|
||||
export interface UpdateProductTypeDTO {
|
||||
id: string
|
||||
value?: string
|
||||
metadata?: Record<string, unknown>
|
||||
}
|
||||
|
||||
export interface CreateProductTagDTO {
|
||||
value: string
|
||||
}
|
||||
|
||||
export interface UpsertProductTagDTO {
|
||||
id?: string
|
||||
value: string
|
||||
}
|
||||
|
||||
export interface UpdateProductTagDTO {
|
||||
id: string
|
||||
value?: string
|
||||
}
|
||||
|
||||
export interface CreateProductOptionDTO {
|
||||
title: string
|
||||
product_id?: string
|
||||
product?: Record<any, any>
|
||||
}
|
||||
|
||||
export interface UpdateProductOptionDTO {
|
||||
id: string
|
||||
title?: string
|
||||
product_id?: string
|
||||
}
|
||||
|
||||
export interface CreateProductVariantOptionDTO {
|
||||
@@ -368,6 +447,7 @@ export interface UpdateProductVariantOnlyDTO {
|
||||
}
|
||||
|
||||
export interface CreateProductOptionOnlyDTO {
|
||||
product: { id: string }
|
||||
product_id?: string
|
||||
product?: Record<any, any>
|
||||
title: string
|
||||
}
|
||||
|
||||
@@ -1,16 +1,30 @@
|
||||
import {
|
||||
CreateProductDTO,
|
||||
CreateProductTagDTO,
|
||||
CreateProductTypeDTO,
|
||||
CreateProductOptionDTO,
|
||||
CreateProductCategoryDTO,
|
||||
UpdateProductTagDTO,
|
||||
UpdateProductTypeDTO,
|
||||
UpdateProductOptionDTO,
|
||||
UpdateProductCategoryDTO,
|
||||
UpdateProductDTO,
|
||||
FilterableProductCategoryProps,
|
||||
FilterableProductCollectionProps,
|
||||
FilterableProductProps,
|
||||
FilterableProductTagProps,
|
||||
FilterableProductTypeProps,
|
||||
FilterableProductOptionProps,
|
||||
FilterableProductVariantProps,
|
||||
ProductCategoryDTO,
|
||||
ProductCollectionDTO,
|
||||
ProductDTO,
|
||||
ProductTagDTO,
|
||||
ProductTypeDTO,
|
||||
ProductOptionDTO,
|
||||
ProductVariantDTO,
|
||||
CreateProductCollectionDTO,
|
||||
UpdateProductCollectionDTO,
|
||||
} from "./common"
|
||||
|
||||
import { Context } from "../shared-context"
|
||||
@@ -38,12 +52,105 @@ export interface IProductModuleService {
|
||||
sharedContext?: Context
|
||||
): Promise<[ProductDTO[], number]>
|
||||
|
||||
retrieveTag(
|
||||
tagId: string,
|
||||
config?: FindConfig<ProductTagDTO>,
|
||||
sharedContext?: Context
|
||||
): Promise<ProductTagDTO>
|
||||
|
||||
listTags(
|
||||
filters?: FilterableProductTagProps,
|
||||
config?: FindConfig<ProductTagDTO>,
|
||||
sharedContext?: Context
|
||||
): Promise<ProductTagDTO[]>
|
||||
|
||||
listAndCountTags(
|
||||
filters?: FilterableProductTagProps,
|
||||
config?: FindConfig<ProductTagDTO>,
|
||||
sharedContext?: Context
|
||||
): Promise<[ProductTagDTO[], number]>
|
||||
|
||||
createTags(
|
||||
data: CreateProductTagDTO[],
|
||||
sharedContext?: Context,
|
||||
): Promise<ProductTagDTO[]>
|
||||
|
||||
updateTags(
|
||||
data: UpdateProductTagDTO[],
|
||||
sharedContext?: Context,
|
||||
): Promise<ProductTagDTO[]>
|
||||
|
||||
deleteTags(
|
||||
productTagIds: string[],
|
||||
sharedContext?: Context,
|
||||
): Promise<void>
|
||||
|
||||
retrieveType(
|
||||
typeId: string,
|
||||
config?: FindConfig<ProductTypeDTO>,
|
||||
sharedContext?: Context
|
||||
): Promise<ProductTypeDTO>
|
||||
|
||||
listTypes(
|
||||
filters?: FilterableProductTypeProps,
|
||||
config?: FindConfig<ProductTypeDTO>,
|
||||
sharedContext?: Context
|
||||
): Promise<ProductTypeDTO[]>
|
||||
|
||||
listAndCountTypes(
|
||||
filters?: FilterableProductTypeProps,
|
||||
config?: FindConfig<ProductTypeDTO>,
|
||||
sharedContext?: Context
|
||||
): Promise<[ProductTypeDTO[], number]>
|
||||
|
||||
createTypes(
|
||||
data: CreateProductTypeDTO[],
|
||||
sharedContext?: Context,
|
||||
): Promise<ProductTypeDTO[]>
|
||||
|
||||
updateTypes(
|
||||
data: UpdateProductTypeDTO[],
|
||||
sharedContext?: Context,
|
||||
): Promise<ProductTypeDTO[]>
|
||||
|
||||
deleteTypes(
|
||||
productTypeIds: string[],
|
||||
sharedContext?: Context,
|
||||
): Promise<void>
|
||||
|
||||
retrieveOption(
|
||||
optionId: string,
|
||||
config?: FindConfig<ProductOptionDTO>,
|
||||
sharedContext?: Context
|
||||
): Promise<ProductOptionDTO>
|
||||
|
||||
listOptions(
|
||||
filters?: FilterableProductOptionProps,
|
||||
config?: FindConfig<ProductOptionDTO>,
|
||||
sharedContext?: Context
|
||||
): Promise<ProductOptionDTO[]>
|
||||
|
||||
listAndCountOptions(
|
||||
filters?: FilterableProductOptionProps,
|
||||
config?: FindConfig<ProductOptionDTO>,
|
||||
sharedContext?: Context
|
||||
): Promise<[ProductOptionDTO[], number]>
|
||||
|
||||
createOptions(
|
||||
data: CreateProductOptionDTO[],
|
||||
sharedContext?: Context,
|
||||
): Promise<ProductOptionDTO[]>
|
||||
|
||||
updateOptions(
|
||||
data: UpdateProductOptionDTO[],
|
||||
sharedContext?: Context,
|
||||
): Promise<ProductOptionDTO[]>
|
||||
|
||||
deleteOptions(
|
||||
productOptionIds: string[],
|
||||
sharedContext?: Context,
|
||||
): Promise<void>
|
||||
|
||||
retrieveVariant(
|
||||
productVariantId: string,
|
||||
config?: FindConfig<ProductVariantDTO>,
|
||||
@@ -80,6 +187,21 @@ export interface IProductModuleService {
|
||||
sharedContext?: Context
|
||||
): Promise<[ProductCollectionDTO[], number]>
|
||||
|
||||
createCollections(
|
||||
data: CreateProductCollectionDTO[],
|
||||
sharedContext?: Context,
|
||||
): Promise<ProductCollectionDTO[]>
|
||||
|
||||
updateCollections(
|
||||
data: UpdateProductCollectionDTO[],
|
||||
sharedContext?: Context,
|
||||
): Promise<ProductCollectionDTO[]>
|
||||
|
||||
deleteCollections(
|
||||
productCollectionIds: string[],
|
||||
sharedContext?: Context,
|
||||
): Promise<void>
|
||||
|
||||
retrieveCategory(
|
||||
productCategoryId: string,
|
||||
config?: FindConfig<ProductCategoryDTO>,
|
||||
@@ -98,6 +220,22 @@ export interface IProductModuleService {
|
||||
sharedContext?: Context
|
||||
): Promise<[ProductCategoryDTO[], number]>
|
||||
|
||||
createCategory(
|
||||
data: CreateProductCategoryDTO,
|
||||
sharedContext?: Context,
|
||||
): Promise<ProductCategoryDTO>
|
||||
|
||||
updateCategory(
|
||||
categoryId: string,
|
||||
data: UpdateProductCategoryDTO,
|
||||
sharedContext?: Context,
|
||||
): Promise<ProductCategoryDTO>
|
||||
|
||||
deleteCategory(
|
||||
categoryId: string,
|
||||
sharedContext?: Context,
|
||||
): Promise<void>
|
||||
|
||||
create(
|
||||
data: CreateProductDTO[],
|
||||
sharedContext?: Context
|
||||
|
||||
@@ -2,10 +2,12 @@ import { EntityManager } from "typeorm"
|
||||
|
||||
export type SharedContext = {
|
||||
transactionManager?: EntityManager
|
||||
manager?: EntityManager
|
||||
}
|
||||
|
||||
export type Context<TManager = unknown> = {
|
||||
transactionManager?: TManager
|
||||
manager?: TManager
|
||||
isolationLevel?: string
|
||||
enableNestedTransactions?: boolean
|
||||
transactionId?: string
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
export * from "./inject-transaction-manager"
|
||||
export * from "./inject-manager"
|
||||
|
||||
31
packages/utils/src/modules-sdk/decorators/inject-manager.ts
Normal file
31
packages/utils/src/modules-sdk/decorators/inject-manager.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { Context, SharedContext } from "@medusajs/types"
|
||||
|
||||
export function InjectManager(managerProperty?: string): MethodDecorator {
|
||||
return function (
|
||||
target: any,
|
||||
propertyKey: string | symbol,
|
||||
descriptor: any
|
||||
): void {
|
||||
if (!target.MedusaContextIndex_) {
|
||||
throw new Error(
|
||||
`To apply @InjectManager you have to flag a parameter using @MedusaContext`
|
||||
)
|
||||
}
|
||||
|
||||
const originalMethod = descriptor.value
|
||||
const argIndex = target.MedusaContextIndex_[propertyKey]
|
||||
|
||||
descriptor.value = function (...args: any[]) {
|
||||
const context: SharedContext | Context = args[argIndex] ?? {}
|
||||
const resourceWithManager = (!managerProperty
|
||||
? this
|
||||
: this[managerProperty])
|
||||
|
||||
context.manager =
|
||||
context.manager ?? resourceWithManager.getFreshManager()
|
||||
args[argIndex] = context
|
||||
|
||||
return originalMethod.apply(this, args)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import { buildQuery } from "./build-query"
|
||||
type RetrieveEntityParams<TDTO> = {
|
||||
id: string,
|
||||
entityName: string,
|
||||
repository: DAL.TreeRepositoryService
|
||||
repository: DAL.TreeRepositoryService | DAL.RepositoryService
|
||||
config: FindConfig<TDTO>
|
||||
sharedContext?: Context
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user