chore(medusa): Typeorm upgrade to 0.3.11 (#3041)

This commit is contained in:
Riqwan Thamir authored and GitHub committed 2023-02-15 16:25:30 +01:00
1 parent 8137061908
commit 121b42acfe
275 files changed
+4493 -4780

No files matched your search

+2 -4
View File
@@ -21,6 +21,7 @@
"cross-env": "^5.2.1",
"jest": "^25.5.4",
"ts-jest": "^25.5.1",
"typeorm": "^0.3.11",
"typescript": "^4.4.4"
},
"scripts": {
@@ -32,9 +33,6 @@
},
"peerDependencies": {
"@medusajs/medusa": "1.7.7",
"medusa-interfaces": "1.3.6"
},
"dependencies": {
"typeorm": "^0.2.31"
"typeorm": "^0.3.11"
}
}
@@ -1,11 +1,11 @@
import { DeepPartial, EntityManager } from "typeorm"
import { DeepPartial, EntityManager, FindManyOptions } from "typeorm"
import { isDefined, MedusaError } from "medusa-core-utils"
import {
FindConfig,
buildQuery,
IEventBusService,
FilterableInventoryItemProps,
CreateInventoryItemInput,
FilterableInventoryItemProps,
FindConfig,
IEventBusService,
InventoryItemDTO,
TransactionBaseService,
} from "@medusajs/medusa"
@@ -87,7 +87,7 @@ export default class InventoryItemService extends TransactionBaseService {
const manager = this.getManager()
const itemRepository = manager.getRepository(InventoryItem)
const query = buildQuery({ id: inventoryItemId }, config)
const query = buildQuery({ id: inventoryItemId }, config) as FindManyOptions
const [inventoryItem] = await itemRepository.find(query)
if (!inventoryItem) {
@@ -1,10 +1,10 @@
import { DeepPartial, EntityManager } from "typeorm"
import { DeepPartial, EntityManager, FindManyOptions } from "typeorm"
import { isDefined, MedusaError } from "medusa-core-utils"
import {
FindConfig,
buildQuery,
FilterableInventoryLevelProps,
CreateInventoryLevelInput,
FilterableInventoryLevelProps,
FindConfig,
IEventBusService,
TransactionBaseService,
} from "@medusajs/medusa"
@@ -52,7 +52,7 @@ export default class InventoryLevelService extends TransactionBaseService {
const manager = this.getManager()
const levelRepository = manager.getRepository(InventoryLevel)
const query = buildQuery(selector, config)
const query = buildQuery(selector, config) as FindManyOptions
return await levelRepository.find(query)
}
@@ -69,7 +69,7 @@ export default class InventoryLevelService extends TransactionBaseService {
const manager = this.getManager()
const levelRepository = manager.getRepository(InventoryLevel)
const query = buildQuery(selector, config)
const query = buildQuery(selector, config) as FindManyOptions
return await levelRepository.findAndCount(query)
}
@@ -94,7 +94,7 @@ export default class InventoryLevelService extends TransactionBaseService {
const manager = this.getManager()
const levelRepository = manager.getRepository(InventoryLevel)
const query = buildQuery({ id: inventoryLevelId }, config)
const query = buildQuery({ id: inventoryLevelId }, config) as FindManyOptions
const [inventoryLevel] = await levelRepository.find(query)
if (!inventoryLevel) {
@@ -1,4 +1,4 @@
import { EntityManager } from "typeorm"
import { EntityManager, FindManyOptions } from "typeorm"
import { isDefined, MedusaError } from "medusa-core-utils"
import {
FindConfig,
@@ -62,7 +62,7 @@ export default class ReservationItemService extends TransactionBaseService {
const manager = this.getManager()
const itemRepository = manager.getRepository(ReservationItem)
const query = buildQuery(selector, config)
const query = buildQuery(selector, config) as FindManyOptions
return await itemRepository.find(query)
}
@@ -79,7 +79,7 @@ export default class ReservationItemService extends TransactionBaseService {
const manager = this.getManager()
const itemRepository = manager.getRepository(ReservationItem)
const query = buildQuery(selector, config)
const query = buildQuery(selector, config) as FindManyOptions
return await itemRepository.findAndCount(query)
}
@@ -104,7 +104,7 @@ export default class ReservationItemService extends TransactionBaseService {
const manager = this.getManager()
const reservationItemRepository = manager.getRepository(ReservationItem)
const query = buildQuery({ id: reservationItemId }, config)
const query = buildQuery({ id: reservationItemId }, config) as FindManyOptions
const [reservationItem] = await reservationItemRepository.find(query)
if (!reservationItem) {
+15 -7
View File
@@ -1,6 +1,8 @@
import { EntityManager, ILike } from "typeorm"
import { EntityManager, FindOptionsWhere, ILike } from "typeorm"
import {
buildLegacyFieldsListFrom,
buildQuery,
ExtendedFindConfig,
FilterableInventoryItemProps,
FindConfig,
} from "@medusajs/medusa"
@@ -12,14 +14,19 @@ export function getListQuery(
config: FindConfig<InventoryItem> = { relations: [], skip: 0, take: 10 }
) {
const inventoryItemRepository = manager.getRepository(InventoryItem)
const query = buildQuery(selector, config)
const { q, ...selectorRest } = selector
const query = buildQuery(selectorRest, config) as ExtendedFindConfig<InventoryItem> & {
where: FindOptionsWhere<InventoryItem & {
location_id?: string
}>
}
const queryBuilder = inventoryItemRepository.createQueryBuilder("inv_item")
if (query.where.q) {
query.where.sku = ILike(`%${query.where.q as string}%`)
delete query.where.q
if (q) {
query.where.sku = ILike(`%${q}%`)
}
if ("location_id" in query.where) {
@@ -50,7 +57,8 @@ export function getListQuery(
}
if (query.select) {
queryBuilder.select(query.select.map((s) => "inv_item." + s))
const legacySelect = buildLegacyFieldsListFrom(query.select)
queryBuilder.select(legacySelect.map((s) => "inv_item." + s))
}
if (query.order) {
@@ -25,7 +25,7 @@ type ComputePropertyNames<
* ```
*/
export const transformIdableFields = <
T = Record<string, unknown>,
T extends object = Record<string, unknown>,
TFields extends (keyof T | string)[] = (keyof T | string)[],
TOutput = {
[P in ComputePropertyNames<T, keyof T & string, TFields>]: P extends keyof T
+3 -3
View File
@@ -26,10 +26,10 @@
"medusa-interfaces": "^1.3.6"
},
"scripts": {
"prepare": "cross-env NODE_ENV=production yarn run build",
"test": "jest --passWithNoTests src",
"build": "babel src --out-dir dist/ --ignore '**/__tests__','**/__mocks__'",
"watch": "babel -w src --out-dir dist/ --ignore '**/__tests__','**/__mocks__'"
"prepare": "cross-env NODE_ENV=production yarn run build",
"watch": "babel -w src --out-dir dist/ --ignore '**/__tests__','**/__mocks__'",
"test": "jest --passWithNoTests src"
},
"peerDependencies": {
"medusa-interfaces": "1.3.6"
-1
View File
@@ -33,7 +33,6 @@
"jest": "^25.5.4",
"medusa-core-utils": "^1.1.39",
"medusa-test-utils": "^1.1.37",
"typeorm": "^0.2.29",
"typescript": "^4.4.4"
},
"peerDependencies": {
@@ -1,33 +1,6 @@
import BaseService from "../base-service"
import { In, Not } from "typeorm"
describe("BaseService", () => {
describe("buildQuery_", () => {
const baseService = new BaseService()
it("successfully creates query", () => {
const q = baseService.buildQuery_(
{
id: "1234",
test1: ["123", "12", "1"],
test2: Not("this"),
},
{
relations: ["1234"],
}
)
expect(q).toEqual({
where: {
id: "1234",
test1: In(["123", "12", "1"]),
test2: Not("this"),
},
relations: ["1234"],
})
})
})
describe("addDecorator", () => {
const baseService = new BaseService()
+7 -135
View File
@@ -1,11 +1,12 @@
import { MedusaError } from "medusa-core-utils"
import { FindOperator, In, Raw } from "typeorm"
// Import from dist to avoid circular deps which result in the base service to be undefined
import { buildQuery, setMetadata, validateId } from "@medusajs/medusa/dist/utils"
/**
* Common functionality for Services
* @interface
* @deprecated use TransactionBaseService from @medusajs/medusa instead
*/
class BaseService {
export default class BaseService {
constructor() {
this.decorators_ = []
}
@@ -19,93 +20,7 @@ class BaseService {
* Used to build TypeORM queries.
*/
buildQuery_(selector, config = {}) {
const build = (obj) => {
const where = Object.entries(obj).reduce((acc, [key, value]) => {
// Undefined values indicate that they have no significance to the query.
// If the query is looking for rows where a column is not set it should use null instead of undefined
if (typeof value === "undefined") {
return acc
}
switch (true) {
case value instanceof FindOperator:
acc[key] = value
break
case Array.isArray(value):
acc[key] = In([...value])
break
case value !== null && typeof value === "object":
const subquery = []
Object.entries(value).map(([modifier, val]) => {
switch (modifier) {
case "lt":
subquery.push({ operator: "<", value: val })
break
case "gt":
subquery.push({ operator: ">", value: val })
break
case "lte":
subquery.push({ operator: "<=", value: val })
break
case "gte":
subquery.push({ operator: ">=", value: val })
break
default:
acc[key] = value
break
}
})
if (subquery.length) {
acc[key] = Raw(
(a) =>
subquery
.map((s, index) => `${a} ${s.operator} :${index}`)
.join(" AND "),
subquery.map((s) => s.value)
)
}
break
default:
acc[key] = value
break
}
return acc
}, {})
return where
}
const query = {
where: build(selector),
}
if ("deleted_at" in selector) {
query.withDeleted = true
}
if ("skip" in config) {
query.skip = config.skip
}
if ("take" in config) {
query.take = config.take
}
if ("relations" in config) {
query.relations = config.relations
}
if ("select" in config) {
query.select = config.select
}
if ("order" in config) {
query.order = config.order
}
return query
return buildQuery(selector, config)
}
/**
@@ -117,32 +32,7 @@ class BaseService {
* @returns {string} the rawId given that nothing failed
*/
validateId_(rawId, config = {}) {
const { prefix, length } = config
if (!rawId) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Failed to validate id: ${rawId}`
)
}
if (prefix || length) {
const [pre, rand] = rawId.split("_")
if (prefix && pre !== prefix) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`The provided id: ${rawId} does not adhere to prefix constraint: ${prefix}`
)
}
if (length && length !== rand.length) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`The provided id: ${rawId} does not adhere to length constraint: ${length}`
)
}
}
return rawId
return validateId(rawId, config)
}
shouldRetryTransaction(err) {
@@ -249,24 +139,7 @@ class BaseService {
* @return {Promise} resolves to the updated result.
*/
setMetadata_(obj, metadata) {
const existing = obj.metadata || {}
const newData = {}
for (const [key, value] of Object.entries(metadata)) {
if (typeof key !== "string") {
throw new MedusaError(
MedusaError.Types.INVALID_ARGUMENT,
"Key type is invalid. Metadata keys must be strings"
)
}
newData[key] = value
}
const updated = {
...existing,
...newData,
}
return updated
return setMetadata(obj, metadata)
}
/**
@@ -295,4 +168,3 @@ class BaseService {
}, Promise.resolve(obj))
}
}
export default BaseService
@@ -1,6 +1,7 @@
import SendGrid from "@sendgrid/mail"
import { humanizeAmount, zeroDecimalCurrencies } from "medusa-core-utils"
import { NotificationService } from "medusa-interfaces"
import { IsNull, Not } from "typeorm"
class SendGridService extends NotificationService {
static identifier = "sendgrid"
@@ -621,7 +622,7 @@ class SendGridService extends NotificationService {
{
id: returnRequest.items.map(({ item_id }) => item_id),
},
{ relations: ["tax_lines"] }
{ relations: ["tax_lines", "variant", "variant.product"] }
)
// Fetch the order
@@ -629,6 +630,7 @@ class SendGridService extends NotificationService {
select: ["total"],
relations: [
"items",
"items.variant",
"items.tax_lines",
"discounts",
"discounts.rule",
@@ -712,20 +714,22 @@ class SendGridService extends NotificationService {
async swapReceivedData({ id }) {
const store = await this.storeService_.retrieve()
const swap = await this.swapService_.retrieve(id, {
relations: [
"additional_items",
"additional_items.tax_lines",
"additional_items.variant",
"return_order",
"return_order.items",
"return_order.items.item",
"return_order.items.item.variant",
"return_order.shipping_method",
"return_order.shipping_method.shipping_option",
],
})
const returnRequest = swap.return_order
const items = await this.lineItemService_.list(
{
id: returnRequest.items.map(({ item_id }) => item_id),
@@ -752,16 +756,19 @@ class SendGridService extends NotificationService {
select: ["total"],
relations: [
"items",
"items.variant",
"discounts",
"discounts.rule",
"shipping_address",
"swaps",
"swaps.additional_items",
"swaps.additional_items.tax_lines",
"swaps.additional_items.variant",
],
})
const cart = await this.cartService_.retrieve(swap.cart_id, {
relations: ["items", "items.variant", "items.variant.product"],
select: [
"total",
"tax_total",
@@ -770,8 +777,8 @@ class SendGridService extends NotificationService {
"subtotal",
],
})
const currencyCode = order.currency_code.toUpperCase()
const currencyCode = order.currency_code.toUpperCase()
const decoratedItems = await Promise.all(
cart.items.map(async (i) => {
const totals = await this.totalsService_.getLineItemTotals(i, cart, {
@@ -837,10 +844,11 @@ class SendGridService extends NotificationService {
}
async swapCreatedData({ id }) {
const store = await this.storeService_.retrieve()
const store = await this.storeService_.retrieve({ where: { id: Not(IsNull()) } })
const swap = await this.swapService_.retrieve(id, {
relations: [
"additional_items",
"additional_items.variant.product",
"additional_items.tax_lines",
"return_order",
"return_order.items",
@@ -857,7 +865,7 @@ class SendGridService extends NotificationService {
id: returnRequest.items.map(({ item_id }) => item_id),
},
{
relations: ["tax_lines"],
relations: ["tax_lines", "variant", "variant.product"],
}
)
@@ -878,6 +886,8 @@ class SendGridService extends NotificationService {
select: ["total"],
relations: [
"items",
"items.variant",
"items.variant.product",
"items.tax_lines",
"discounts",
"discounts.rule",
@@ -885,6 +895,7 @@ class SendGridService extends NotificationService {
"swaps",
"swaps.additional_items",
"swaps.additional_items.tax_lines",
"swaps.additional_items.variant",
],
})
@@ -896,6 +907,7 @@ class SendGridService extends NotificationService {
"shipping_total",
"subtotal",
],
relations: ["items", "items.variant", "items.variant.product"]
})
const currencyCode = order.currency_code.toUpperCase()
@@ -977,6 +989,8 @@ class SendGridService extends NotificationService {
"shipping_methods",
"shipping_methods.tax_lines",
"additional_items",
"additional_items.variant",
"additional_items.variant.product",
"additional_items.tax_lines",
"return_order",
"return_order.items",
@@ -988,10 +1002,14 @@ class SendGridService extends NotificationService {
"region",
"items",
"items.tax_lines",
"items.variant",
"items.variant.product",
"discounts",
"discounts.rule",
"swaps",
"swaps.additional_items",
"swaps.additional_items.variant",
"swaps.additional_items.variant.product",
"swaps.additional_items.tax_lines",
],
})
@@ -1004,6 +1022,11 @@ class SendGridService extends NotificationService {
"shipping_total",
"subtotal",
],
relations: [
"items",
"items.variant",
"items.variant.product",
]
})
const returnRequest = swap.return_order
@@ -1012,7 +1035,7 @@ class SendGridService extends NotificationService {
id: returnRequest.items.map(({ item_id }) => item_id),
},
{
relations: ["tax_lines"],
relations: ["tax_lines", "variant", "variant.product"],
}
)
@@ -1118,7 +1141,13 @@ class SendGridService extends NotificationService {
async claimShipmentCreatedData({ id, fulfillment_id }) {
const claim = await this.claimService_.retrieve(id, {
relations: ["order", "order.items", "order.shipping_address"],
relations: [
"order",
"order.items",
"order.items.variant",
"order.items.variant.product",
"order.shipping_address"
],
})
const shipment = await this.fulfillmentService_.retrieve(fulfillment_id, {
@@ -1,17 +1,17 @@
export default {
getRepository: function (repo) {
return repo;
return repo
},
getCustomRepository: function (repo) {
return repo;
withRepository: function (repo) {
return repo
},
transaction: function (isolationOrCb, cb) {
if (typeof isolationOrCb === "string") {
return cb(this);
return cb(this)
} else {
return isolationOrCb(this);
return isolationOrCb(this)
}
},
};
}
@@ -12,6 +12,7 @@ class MockRepo {
save,
findAndCount,
del,
count,
}) {
this.create_ = create;
this.update_ = update;
@@ -28,45 +29,45 @@ class MockRepo {
}
setFindOne(fn) {
this.findOne_ = fn;
this.findOne_ = fn
}
create = jest.fn().mockImplementation((...args) => {
if (this.create_) {
return this.create_(...args);
return this.create_(...args)
}
return {};
});
return {}
})
softRemove = jest.fn().mockImplementation((...args) => {
if (this.softRemove_) {
return this.softRemove_(...args);
return this.softRemove_(...args)
}
return {};
});
return {}
})
remove = jest.fn().mockImplementation((...args) => {
if (this.remove_) {
return this.remove_(...args);
return this.remove_(...args)
}
return {};
});
return {}
})
update = jest.fn().mockImplementation((...args) => {
if (this.update_) {
return this.update_(...args);
return this.update_(...args)
}
});
})
findOneOrFail = jest.fn().mockImplementation((...args) => {
if (this.findOneOrFail_) {
return this.findOneOrFail_(...args);
return this.findOneOrFail_(...args)
}
});
})
findOneWithRelations = jest.fn().mockImplementation((...args) => {
if (this.findOneWithRelations_) {
return this.findOneWithRelations_(...args);
return this.findOneWithRelations_(...args)
}
});
})
findOne = jest.fn().mockImplementation((...args) => {
if (this.findOne_) {
return this.findOne_(...args);
return this.findOne_(...args)
}
});
findDescendantsTree = jest.fn().mockImplementation((...args) => {
@@ -76,40 +77,46 @@ class MockRepo {
});
findOneOrFail = jest.fn().mockImplementation((...args) => {
if (this.findOneOrFail_) {
return this.findOneOrFail_(...args);
return this.findOneOrFail_(...args)
}
});
})
find = jest.fn().mockImplementation((...args) => {
if (this.find_) {
return this.find_(...args);
return this.find_(...args)
}
});
})
softRemove = jest.fn().mockImplementation((...args) => {
if (this.softRemove_) {
return this.softRemove_(...args);
return this.softRemove_(...args)
}
});
})
save = jest.fn().mockImplementation((...args) => {
if (this.save_) {
return this.save_(...args);
return this.save_(...args)
}
return Promise.resolve(...args);
});
return Promise.resolve(...args)
})
findAndCount = jest.fn().mockImplementation((...args) => {
if (this.findAndCount_) {
return this.findAndCount_(...args);
return this.findAndCount_(...args)
}
return {};
});
return {}
})
count = jest.fn().mockImplementation((...args) => {
if (this.count_) {
return this.count(...args)
}
return {}
})
delete = jest.fn().mockImplementation((...args) => {
if (this.delete_) {
return this.delete_(...args);
return this.delete_(...args)
}
return {};
});
return {}
})
}
export default (methods = {}) => {
return new MockRepo(methods);
};
return new MockRepo(methods)
}
+1 -1
View File
@@ -45,7 +45,7 @@
},
"peerDependencies": {
"medusa-interfaces": "1.3.6",
"typeorm": "0.2.x"
"typeorm": "^0.3.11"
},
"dependencies": {
"@medusajs/medusa-cli": "^1.3.8",
@@ -18,9 +18,14 @@ export const getVariantsByInventoryItemId = async (
inventoryItems.map((item) => item.id)
)
const variants = await productVariantService.list({
id: variantInventory.map((varInventory) => varInventory.variant_id),
})
const variants = await productVariantService.list(
{
id: variantInventory.map((varInventory) => varInventory.variant_id),
},
{
relations: ["product"],
}
)
const variantMap = new Map(variants.map((variant) => [variant.id, variant]))
return variantInventory.reduce((acc, cur) => {
@@ -142,9 +142,11 @@ export default async (req, res) => {
relations: [
"cart",
"items",
"items.variant",
"items.tax_lines",
"swaps",
"swaps.additional_items",
"swaps.additional_items.variant",
"swaps.additional_items.tax_lines",
],
})
@@ -319,6 +319,7 @@ export const defaultAdminOrdersRelations = [
"claims.shipping_methods",
"claims.shipping_address",
"claims.additional_items",
"claims.additional_items.variant",
"claims.fulfillments",
"claims.fulfillments.tracking_links",
"claims.claim_items",
@@ -334,6 +335,7 @@ export const defaultAdminOrdersRelations = [
"swaps.shipping_methods.tax_lines",
"swaps.shipping_address",
"swaps.additional_items",
"swaps.additional_items.variant",
"swaps.fulfillments",
"swaps.fulfillments.tracking_links",
]
@@ -33,7 +33,7 @@ export default (app, featureFlagRouter: FlagRouter) => {
return app
}
export const defaultFields = [
export const defaultFields: (keyof ShippingOption)[] = [
"id",
"name",
"region_id",
@@ -14,6 +14,8 @@ import { EntityManager } from "typeorm"
import TaxInclusivePricingFeatureFlag from "../../../../loaders/feature-flags/tax-inclusive-pricing"
import { FeatureFlagDecorators } from "../../../../utils/feature-flag-decorators"
import { validator } from "../../../../utils/validator"
import { ShippingOptionService } from "../../../../services"
import { UpdateShippingOptionInput } from "../../../../types/shipping-options"
/**
* @oas [post] /shipping-options/{id}
@@ -92,9 +94,14 @@ import { validator } from "../../../../utils/validator"
export default async (req, res) => {
const { option_id } = req.params
const validated = await validator(AdminPostShippingOptionsOptionReq, req.body)
const validated = (await validator(
AdminPostShippingOptionsOptionReq,
req.body
)) as UpdateShippingOptionInput
const optionService = req.scope.resolve("shippingOptionService")
const optionService: ShippingOptionService = req.scope.resolve(
"shippingOptionService"
)
const manager: EntityManager = req.scope.resolve("manager")
await manager.transaction(async (transactionManager) => {
@@ -13,6 +13,7 @@ const defaultRelations = [
"shipping_methods",
"cart",
"cart.items",
"cart.items.variant",
"cart.items.adjustments",
]
@@ -32,6 +32,7 @@ export const defaultAdminSwapRelations = [
"shipping_methods",
"cart",
"cart.items",
"cart.items.variant",
"cart.items.adjustments",
]
@@ -57,6 +57,8 @@ export const defaultAdminVariantFields: (keyof ProductVariant)[] = [
"created_at",
"updated_at",
"metadata",
"deleted_at",
"manage_inventory",
]
/**
@@ -143,6 +143,7 @@ export const defaultStoreCartRelations = [
"gift_cards",
"region",
"items",
"items.variant",
"items.adjustments",
"payment",
"shipping_address",
@@ -78,7 +78,7 @@ export default async (req, res) => {
} else {
const cart = await cartService
.withTransaction(m)
.retrieve(id, { relations: ["items"] })
.retrieve(id, { relations: ["items", "items.variant"] })
const existing = cart.items.find((i) => i.id === line_id)
if (!existing) {
@@ -75,7 +75,6 @@ export const defaultStoreOrdersRelations = [
"fulfillments.tracking_links",
"items",
"items.variant",
"items.variant.product",
"shipping_methods",
"discounts",
"discounts.rule",
@@ -140,9 +140,11 @@ export default async (req, res) => {
select: ["refunded_total", "total"],
relations: [
"items",
"items.variant",
"items.tax_lines",
"swaps",
"swaps.additional_items",
"swaps.additional_items.variant",
"swaps.additional_items.tax_lines",
],
})
@@ -21,6 +21,7 @@ export default (app) => {
export const defaultStoreSwapRelations = [
"order",
"additional_items",
"additional_items.variant",
"return_order",
"return_order.shipping_method",
"fulfillments",
@@ -15,7 +15,7 @@ describe("Get variant by id", () => {
it("calls get variant from variantSerice", () => {
expect(ProductVariantServiceMock.retrieve).toHaveBeenCalledTimes(1)
expect(ProductVariantServiceMock.retrieve).toHaveBeenCalledWith("1", {
relations: ["prices", "options"],
relations: ["prices", "options", "product"],
})
})
@@ -27,7 +27,7 @@ export default (app) => {
return app
}
export const defaultStoreVariantRelations = ["prices", "options"]
export const defaultStoreVariantRelations = ["prices", "options", "product"]
/**
* @schema StoreVariantsRes
+21 -37
View File
@@ -1,71 +1,55 @@
import { createConnection } from "typeorm"
import { asValue, createContainer } from "awilix"
import featureFlagLoader from "../loaders/feature-flags"
import configModuleLoader from "../loaders/config"
import Logger from "../loaders/logger"
import databaseLoader from "../loaders/database"
import configModuleLoader from "../loaders/config"
import getMigrations, { getModuleSharedResources } from "./utils/get-migrations"
const getDataSource = async (directory) => {
const configModule = configModuleLoader(directory)
const featureFlagRouter = featureFlagLoader(configModule)
const { coreMigrations } = getMigrations(directory, featureFlagRouter)
const { migrations: moduleMigrations } = getModuleSharedResources(
configModule,
featureFlagRouter
)
return await createConnection({
type: configModule.projectConfig.database_type,
url: configModule.projectConfig.database_url,
extra: configModule.projectConfig.database_extra || {},
schema: configModule.projectConfig.database_schema,
migrations: coreMigrations.concat(moduleMigrations),
logging: true,
const container = createContainer()
container.register("db_entities", asValue([]))
return await databaseLoader({
container,
configModule,
customOptions: {
migrations: coreMigrations.concat(moduleMigrations),
logging: "all",
},
})
}
const main = async function ({ directory }) {
const args = process.argv
args.shift()
args.shift()
args.shift()
const dataSource = await getDataSource(directory)
if (args[0] === "run") {
const dataSource = await getDataSource(directory)
await dataSource.runMigrations()
await dataSource.close()
await dataSource.destroy()
Logger.info("Migrations completed.")
process.exit()
} else if (args[0] === "revert") {
const dataSource = await getDataSource(directory)
await dataSource.undoLastMigration({ transaction: "all" })
await dataSource.close()
await dataSource.destroy()
Logger.info("Migrations reverted.")
process.exit()
} else if (args[0] === "show") {
const configModule = configModuleLoader(directory)
const featureFlagRouter = featureFlagLoader(configModule)
const { coreMigrations } = getMigrations(directory, featureFlagRouter)
const connection = await createConnection({
type: configModule.projectConfig.database_type,
url: configModule.projectConfig.database_url,
extra: configModule.projectConfig.database_extra || {},
schema: configModule.projectConfig.database_schema,
migrations: coreMigrations,
logging: true,
})
const unapplied = await connection.showMigrations()
await connection.close()
const unapplied = await dataSource.showMigrations()
Logger.info(unapplied)
await dataSource.destroy()
process.exit(unapplied ? 1 : 0)
}
}
@@ -1,29 +1,36 @@
import { asValue, createContainer } from "awilix";
import { MockRepository, MockManager } from "medusa-test-utils"
import { StoreServiceMock } from "../../services/__mocks__/store";
import { ShippingProfileServiceMock } from "../../services/__mocks__/shipping-profile";
import Logger from "../logger";
import featureFlagsLoader from "../feature-flags";
import { asValue, createContainer } from "awilix"
import { MockManager, MockRepository } from "medusa-test-utils"
import { StoreServiceMock } from "../../services/__mocks__/store"
import { ShippingProfileServiceMock } from "../../services/__mocks__/shipping-profile"
import Logger from "../logger"
import featureFlagsLoader from "../feature-flags"
import { default as defaultLoader } from "../defaults"
import { SalesChannelServiceMock } from "../../services/__mocks__/sales-channel";
import { PaymentProviderServiceMock } from "../../services/__mocks__/payment-provider";
import { SalesChannelServiceMock } from "../../services/__mocks__/sales-channel"
import { PaymentProviderServiceMock } from "../../services/__mocks__/payment-provider"
describe('default', () => {
describe('sales channel default', () => {
describe("default", () => {
describe("sales channel default", () => {
let featureFlagRouter
const container = createContainer()
beforeAll(async () => {
featureFlagRouter = await featureFlagsLoader({
featureFlags: {
sales_channels: true,
featureFlagRouter = await featureFlagsLoader(
{
featureFlags: {
sales_channels: true,
},
},
}, Logger)
Logger
)
container.register({
storeService: asValue(StoreServiceMock),
currencyRepository: asValue(MockRepository()),
countryRepository: asValue(MockRepository()),
countryRepository: asValue(
MockRepository({
count: jest.fn().mockImplementation(() => 1),
})
),
shippingProfileService: asValue(ShippingProfileServiceMock),
salesChannelService: asValue(SalesChannelServiceMock),
logger: asValue(Logger),
+38 -20
View File
@@ -1,48 +1,66 @@
import {
Connection,
createConnection,
getConnectionManager,
getConnection,
DataSource,
DataSourceOptions,
Repository,
TreeRepository,
} from "typeorm"
import { ShortenedNamingStrategy } from "../utils/naming-strategy"
import { AwilixContainer } from "awilix"
import { ConnectionOptions } from "typeorm/connection/ConnectionOptions"
import { ConfigModule } from "../types/global"
import "../utils/naming-strategy"
type Options = {
configModule: ConfigModule
container: AwilixContainer
customOptions?: {
migrations: DataSourceOptions["migrations"]
logging: DataSourceOptions["logging"]
}
}
export let dataSource: DataSource
// TODO: With the latest version of typeorm, the datasource is expected to be
// available globally. During the integration test, the medusa
// files are imported from @medusajs/medusa, therefore, the repositories are
// evaluated at the same time, unfortunately, the integration tests have their
// own way to load and at the moment, the datasource does not exists. This is
// why we are mocking them here
if (process.env.NODE_ENV === "test") {
dataSource = {
getRepository: (target) => new Repository(target, {} as any) as any,
getTreeRepository: (target) => new TreeRepository(target, {} as any) as any,
} as unknown as DataSource
}
export default async ({
container,
configModule,
}: Options): Promise<Connection> => {
customOptions,
}: Options): Promise<DataSource> => {
const entities = container.resolve("db_entities")
const isSqlite = configModule.projectConfig.database_type === "sqlite"
const cnnManager = getConnectionManager()
if (cnnManager.has("default") && getConnection().isConnected) {
await getConnection().close()
}
const connection = await createConnection({
dataSource = new DataSource({
type: configModule.projectConfig.database_type,
url: configModule.projectConfig.database_url,
database: configModule.projectConfig.database_database,
extra: configModule.projectConfig.database_extra || {},
schema: configModule.projectConfig.database_schema,
entities,
namingStrategy: new ShortenedNamingStrategy(),
logging: configModule.projectConfig.database_logging || false,
} as ConnectionOptions)
migrations: customOptions?.migrations,
logging:
customOptions?.logging ??
(configModule.projectConfig.database_logging || false),
} as DataSourceOptions)
await dataSource.initialize()
if (isSqlite) {
await connection.query(`PRAGMA foreign_keys = OFF`)
await connection.synchronize()
await connection.query(`PRAGMA foreign_keys = ON`)
await dataSource.query(`PRAGMA foreign_keys = OFF`)
await dataSource.synchronize()
await dataSource.query(`PRAGMA foreign_keys = ON`)
}
return connection
return dataSource
}
+4 -4
View File
@@ -78,8 +78,8 @@ export default async ({
const entityManager = container.resolve<EntityManager>("manager")
await entityManager.transaction(async (manager: EntityManager) => {
const countryRepo = manager.getCustomRepository(countryRepository)
const hasCountries = await countryRepo.findOne()
const countryRepo = manager.withRepository(countryRepository)
const hasCountries = !!(await countryRepo.count())
if (!hasCountries) {
for (const c of countries) {
const query = `INSERT INTO "country" ("iso_2", "iso_3", "num_code", "name", "display_name")
@@ -103,8 +103,8 @@ export default async ({
})
await entityManager.transaction(async (manager: EntityManager) => {
const currencyRepo = manager.getCustomRepository(currencyRepository)
const hasCurrencies = await currencyRepo.findOne()
const currencyRepo = manager.withRepository(currencyRepository)
const hasCurrencies = !!(await currencyRepo.count())
if (!hasCurrencies) {
for (const [, c] of Object.entries(currencies)) {
const query = `INSERT INTO "currency" ("code", "symbol", "symbol_native", "name")
+10 -10
View File
@@ -11,11 +11,11 @@ import { track } from "medusa-telemetry"
import { EOL } from "os"
import "reflect-metadata"
import requestIp from "request-ip"
import { Connection, getManager } from "typeorm"
import { Connection } from "typeorm"
import { MedusaContainer } from "../types/global"
import apiLoader from "./api"
import loadConfig from "./config"
import databaseLoader from "./database"
import databaseLoader, { dataSource } from "./database"
import defaultsLoader from "./defaults"
import expressLoader from "./express"
import featureFlagsLoader from "./feature-flags"
@@ -109,12 +109,6 @@ export default async ({
const pmAct = Logger.success(pmActivity, "Plugin models initialized") || {}
track("PLUGIN_MODELS_INIT_COMPLETED", { duration: pmAct.duration })
const repoActivity = Logger.activity(`Initializing repositories${EOL}`)
track("REPOSITORIES_INIT_STARTED")
repositoriesLoader({ container })
const rAct = Logger.success(repoActivity, "Repositories initialized") || {}
track("REPOSITORIES_INIT_COMPLETED", { duration: rAct.duration })
const stratActivity = Logger.activity(`Initializing strategies${EOL}`)
track("STRATEGIES_INIT_STARTED")
strategiesLoader({ container, configModule, isTest })
@@ -136,7 +130,13 @@ export default async ({
const dbAct = Logger.success(dbActivity, "Database initialized") || {}
track("DATABASE_INIT_COMPLETED", { duration: dbAct.duration })
container.register({ manager: asValue(dbConnection.manager) })
const repoActivity = Logger.activity(`Initializing repositories${EOL}`)
track("REPOSITORIES_INIT_STARTED")
repositoriesLoader({ container })
const rAct = Logger.success(repoActivity, "Repositories initialized") || {}
track("REPOSITORIES_INIT_COMPLETED", { duration: rAct.duration })
container.register({ manager: asValue(dataSource.manager) })
const servicesActivity = Logger.activity(`Initializing services${EOL}`)
track("SERVICES_INIT_STARTED")
@@ -153,7 +153,7 @@ export default async ({
// Add the registered services to the request scope
expressApp.use((req: Request, res: Response, next: NextFunction) => {
container.register({ manager: asValue(getManager()) })
container.register({ manager: asValue(dataSource.manager) })
;(req as any).scope = container.createScope()
next()
})
+9 -13
View File
@@ -1,9 +1,9 @@
import glob from "glob"
import path from "path"
import { asClass } from "awilix"
import formatRegistrationName from "../utils/format-registration-name"
import { ClassConstructor, MedusaContainer } from "../types/global"
import { MedusaContainer } from "../types/global"
import { asValue } from "awilix"
/**
* Registers all models in the model directory
@@ -14,17 +14,13 @@ export default ({ container }: { container: MedusaContainer }): void => {
const core = glob.sync(coreFull, { cwd: __dirname })
core.forEach((fn) => {
const loaded = require(fn) as ClassConstructor<unknown>
const loaded = require(fn).default
Object.entries(loaded).map(
([, val]: [string, ClassConstructor<unknown>]) => {
if (typeof val === "function") {
const name = formatRegistrationName(fn)
container.register({
[name]: asClass(val),
})
}
}
)
if (typeof loaded === "object") {
const name = formatRegistrationName(fn)
container.register({
[name]: asValue(loaded),
})
}
})
}
@@ -7,6 +7,7 @@ import {
JoinTable,
ManyToMany,
ManyToOne,
OneToMany,
Unique,
} from "typeorm"
@@ -19,6 +20,7 @@ import { ProductTag } from "./product-tag"
import { ProductType } from "./product-type"
import { SoftDeletableEntity } from "../interfaces/models/soft-deletable-entity"
import { generateEntityId } from "../utils/generate-entity-id"
import { Discount } from "./discount"
export enum DiscountConditionType {
PRODUCTS = "products",
+1 -1
View File
@@ -118,7 +118,7 @@ export class LineItem extends BaseEntity {
@Column({ nullable: true, type: "text" })
variant_id: string | null
@ManyToOne(() => ProductVariant, { eager: true })
@ManyToOne(() => ProductVariant)
@JoinColumn({ name: "variant_id" })
variant: ProductVariant
+2 -9
View File
@@ -11,15 +11,8 @@ import {
OneToMany,
OneToOne,
} from "typeorm"
import {
DbAwareColumn,
resolveDbGenerationStrategy,
resolveDbType,
} from "../utils/db-aware-column"
import {
FeatureFlagColumn,
FeatureFlagDecorators,
} from "../utils/feature-flag-decorators"
import { DbAwareColumn, resolveDbGenerationStrategy, resolveDbType, } from "../utils/db-aware-column"
import { FeatureFlagColumn, FeatureFlagDecorators, } from "../utils/feature-flag-decorators"
import { BaseEntity } from "../interfaces/models/base-entity"
import { generateEntityId } from "../utils/generate-entity-id"
@@ -1,3 +1,8 @@
import { DbAwareColumn, generateEntityId } from "../utils"
import { MoneyAmount } from "./money-amount"
import { Product } from "./product"
import { ProductOptionValue } from "./product-option-value"
import { SoftDeletableEntity } from "../interfaces"
import {
BeforeInsert,
Column,
@@ -5,15 +10,9 @@ import {
Index,
JoinColumn,
ManyToOne,
OneToMany,
OneToMany
} from "typeorm"
import { DbAwareColumn } from "../utils/db-aware-column"
import { MoneyAmount } from "./money-amount"
import { Product } from "./product"
import { ProductOptionValue } from "./product-option-value"
import { SoftDeletableEntity } from "../interfaces/models/soft-deletable-entity"
import { generateEntityId } from "../utils/generate-entity-id"
import { ProductVariantInventoryItem } from "./product-variant-inventory-item"
@Entity()
@@ -25,7 +24,7 @@ export class ProductVariant extends SoftDeletableEntity {
@Column()
product_id: string
@ManyToOne(() => Product, (product) => product.variants, { eager: true })
@ManyToOne(() => Product, (product) => product.variants)
@JoinColumn({ name: "product_id" })
product: Product
@@ -51,7 +50,7 @@ export class ProductVariant extends SoftDeletableEntity {
@Index({ unique: true, where: "deleted_at IS NULL" })
upc: string
@Column({ nullable: true, default: 0, select: false })
@Column({ nullable: true, default: 0 })
variant_rank: number
@Column({ type: "int" })
+4 -4
View File
@@ -1,5 +1,5 @@
import { EntityRepository, Repository } from "typeorm"
import { Address } from "../models/address"
import { Address } from "../models"
import { dataSource } from "../loaders/database"
@EntityRepository(Address)
export class AddressRepository extends Repository<Address> {}
export const AddressRepository = dataSource.getRepository(Address)
export default AddressRepository
@@ -1,5 +1,5 @@
import { EntityRepository, Repository } from "typeorm"
import { AnalyticsConfig } from "../models/analytics-config"
import { dataSource } from "../loaders/database"
@EntityRepository(AnalyticsConfig)
export class AnalyticsConfigRepository extends Repository<AnalyticsConfig> {}
export const AnalyticsConfigRepository = dataSource.getRepository(AnalyticsConfig)
export default AnalyticsConfigRepository
@@ -1,5 +1,5 @@
import { EntityRepository, Repository } from "typeorm"
import { BatchJob } from "../models"
import { dataSource } from "../loaders/database"
@EntityRepository(BatchJob)
export class BatchJobRepository extends Repository<BatchJob> {}
export const BatchJobRepository = dataSource.getRepository(BatchJob)
export default BatchJobRepository
+4 -52
View File
@@ -1,53 +1,5 @@
import { flatten, groupBy, map, merge } from "lodash"
import { EntityRepository, FindManyOptions, Repository } from "typeorm"
import { Cart } from "../models/cart"
import { Cart } from "../models"
import { dataSource } from "../loaders/database"
@EntityRepository(Cart)
export class CartRepository extends Repository<Cart> {
public async findWithRelations(
relations: string[] = [],
optionsWithoutRelations: Omit<FindManyOptions<Cart>, "relations"> = {}
): Promise<Cart[]> {
const entities = await this.find(optionsWithoutRelations)
const entitiesIds = entities.map(({ id }) => id)
const groupedRelations = {}
for (const rel of relations) {
const [topLevel] = rel.split(".")
if (groupedRelations[topLevel]) {
groupedRelations[topLevel].push(rel)
} else {
groupedRelations[topLevel] = [rel]
}
}
const entitiesIdsWithRelations = await Promise.all(
Object.entries(groupedRelations).map(async ([_, rels]) => {
return this.findByIds(entitiesIds, {
select: ["id"],
relations: rels as string[],
})
})
).then(flatten)
const entitiesAndRelations = entitiesIdsWithRelations.concat(entities)
const entitiesAndRelationsById = groupBy(entitiesAndRelations, "id")
return map(entitiesAndRelationsById, (entityAndRelations) =>
merge({}, ...entityAndRelations)
)
}
public async findOneWithRelations(
relations: string[] = [],
optionsWithoutRelations: Omit<FindManyOptions<Cart>, "relations"> = {}
): Promise<Cart> {
// Limit 1
optionsWithoutRelations.take = 1
const result = await this.findWithRelations(
relations,
optionsWithoutRelations
)
return result[0]
}
}
export const CartRepository = dataSource.getRepository(Cart)
export default CartRepository
@@ -1,5 +1,5 @@
import { EntityRepository, Repository } from "typeorm"
import { ClaimImage } from "../models/claim-image"
import { dataSource } from "../loaders/database"
@EntityRepository(ClaimImage)
export class ClaimImageRepository extends Repository<ClaimImage> {}
export const ClaimImageRepository = dataSource.getRepository(ClaimImage)
export default ClaimImageRepository
@@ -1,5 +1,5 @@
import { EntityRepository, Repository } from "typeorm"
import { ClaimItem } from "../models/claim-item"
import { dataSource } from "../loaders/database"
@EntityRepository(ClaimItem)
export class ClaimItemRepository extends Repository<ClaimItem> {}
export const ClaimItemRepository = dataSource.getRepository(ClaimItem)
export default ClaimItemRepository
@@ -1,5 +1,5 @@
import { EntityRepository, Repository } from "typeorm"
import { ClaimTag } from "../models/claim-tag"
import { dataSource } from "../loaders/database"
@EntityRepository(ClaimTag)
export class ClaimTagRepository extends Repository<ClaimTag> {}
export const ClaimTagRepository = dataSource.getRepository(ClaimTag)
export default ClaimTagRepository
+3 -3
View File
@@ -1,5 +1,5 @@
import { EntityRepository, Repository } from "typeorm"
import { ClaimOrder } from "../models/claim-order"
import { dataSource } from "../loaders/database"
@EntityRepository(ClaimOrder)
export class ClaimRepository extends Repository<ClaimOrder> {}
export const ClaimRepository = dataSource.getRepository(ClaimOrder)
export default ClaimRepository
+3 -3
View File
@@ -1,5 +1,5 @@
import { EntityRepository, Repository } from "typeorm"
import { Country } from "../models/country"
import { dataSource } from "../loaders/database"
@EntityRepository(Country)
export class CountryRepository extends Repository<Country> {}
export const CountryRepository = dataSource.getRepository(Country)
export default CountryRepository
+3 -3
View File
@@ -1,5 +1,5 @@
import { EntityRepository, Repository } from "typeorm"
import { Currency } from "../models"
import { dataSource } from "../loaders/database"
@EntityRepository(Currency)
export class CurrencyRepository extends Repository<Currency> {}
export const CurrencyRepository = dataSource.getRepository(Currency)
export default CurrencyRepository
@@ -1,6 +1,6 @@
import { EntityRepository, Repository } from "typeorm"
import { CustomShippingOption } from "./../models/custom-shipping-option"
import { CustomShippingOption } from "../models"
import { dataSource } from "../loaders/database"
@EntityRepository(CustomShippingOption)
// eslint-disable-next-line max-len
export class CustomShippingOptionRepository extends Repository<CustomShippingOption> {}
export const CustomShippingOptionRepository =
dataSource.getRepository(CustomShippingOption)
export default CustomShippingOptionRepository
+126 -109
View File
@@ -1,22 +1,23 @@
import {
DeleteResult,
EntityRepository,
FindOperator,
FindOptionsRelations,
In,
Repository,
SelectQueryBuilder,
} from "typeorm"
import { CustomerGroup } from "../models"
import { ExtendedFindConfig, Selector } from "../types/common"
import { ExtendedFindConfig } from "../types/common"
import {
getGroupedRelations,
mergeEntitiesWithRelations,
queryEntityWithIds,
queryEntityWithoutRelations,
} from "../utils/repository"
import { buildLegacyFieldsListFrom } from "../utils"
import { dataSource } from "../loaders/database"
export type DefaultWithoutRelations = Omit<
ExtendedFindConfig<CustomerGroup, Selector<CustomerGroup>>,
ExtendedFindConfig<CustomerGroup>,
"relations"
>
@@ -26,118 +27,134 @@ export type FindWithoutRelationsOptions = DefaultWithoutRelations & {
}
}
@EntityRepository(CustomerGroup)
export class CustomerGroupRepository extends Repository<CustomerGroup> {
async addCustomers(
groupId: string,
customerIds: string[]
): Promise<CustomerGroup> {
const customerGroup = await this.findOne(groupId)
await this.createQueryBuilder()
.insert()
.into("customer_group_customers")
.values(
customerIds.map((id) => ({
customer_id: id,
customer_group_id: groupId,
}))
)
.orIgnore()
.execute()
return customerGroup as CustomerGroup
}
async removeCustomers(
groupId: string,
customerIds: string[]
): Promise<DeleteResult> {
return await this.createQueryBuilder()
.delete()
.from("customer_group_customers")
.where({
customer_group_id: groupId,
customer_id: In(customerIds),
export const CustomerGroupRepository = dataSource
.getRepository(CustomerGroup)
.extend({
async addCustomers(
groupId: string,
customerIds: string[]
): Promise<CustomerGroup> {
const customerGroup = await this.findOne({
where: {
id: groupId,
},
})
.execute()
}
public async findWithRelationsAndCount(
relations: string[] = [],
idsOrOptionsWithoutRelations: FindWithoutRelationsOptions = { where: {} }
): Promise<[CustomerGroup[], number]> {
let count: number
let entities: CustomerGroup[]
if (Array.isArray(idsOrOptionsWithoutRelations)) {
entities = await this.findByIds(idsOrOptionsWithoutRelations, {
withDeleted: idsOrOptionsWithoutRelations.withDeleted ?? false,
})
count = entities.length
} else {
const customJoinsBuilders: ((
qb: SelectQueryBuilder<CustomerGroup>,
alias: string
) => void)[] = []
if (idsOrOptionsWithoutRelations?.where?.discount_condition_id) {
const discountConditionId =
idsOrOptionsWithoutRelations?.where?.discount_condition_id
delete idsOrOptionsWithoutRelations?.where?.discount_condition_id
customJoinsBuilders.push(
(qb: SelectQueryBuilder<CustomerGroup>, alias: string) => {
qb.innerJoin(
"discount_condition_customer_group",
"dc_cg",
`dc_cg.customer_group_id = ${alias}.id AND dc_cg.condition_id = :dcId`,
{ dcId: discountConditionId }
)
}
await this.createQueryBuilder()
.insert()
.into("customer_group_customers")
.values(
customerIds.map((id) => ({
customer_id: id,
customer_group_id: groupId,
}))
)
.orIgnore()
.execute()
return customerGroup as CustomerGroup
},
async removeCustomers(
groupId: string,
customerIds: string[]
): Promise<DeleteResult> {
return await this.createQueryBuilder()
.delete()
.from("customer_group_customers")
.where({
customer_group_id: groupId,
customer_id: In(customerIds),
})
.execute()
},
async findWithRelationsAndCount(
relations: FindOptionsRelations<CustomerGroup> = {},
idsOrOptionsWithoutRelations: FindWithoutRelationsOptions = { where: {} }
): Promise<[CustomerGroup[], number]> {
let count: number
let entities: CustomerGroup[]
if (Array.isArray(idsOrOptionsWithoutRelations)) {
entities = await this.find({
where: { id: In(idsOrOptionsWithoutRelations) },
withDeleted: idsOrOptionsWithoutRelations.withDeleted ?? false,
})
count = entities.length
} else {
const customJoinsBuilders: ((
qb: SelectQueryBuilder<CustomerGroup>,
alias: string
) => void)[] = []
if (idsOrOptionsWithoutRelations?.where?.discount_condition_id) {
const discountConditionId =
idsOrOptionsWithoutRelations?.where?.discount_condition_id
delete idsOrOptionsWithoutRelations?.where?.discount_condition_id
customJoinsBuilders.push(
(qb: SelectQueryBuilder<CustomerGroup>, alias: string) => {
qb.innerJoin(
"discount_condition_customer_group",
"dc_cg",
`dc_cg.customer_group_id = ${alias}.id AND dc_cg.condition_id = :dcId`,
{ dcId: discountConditionId }
)
}
)
}
const result = await queryEntityWithoutRelations(
this,
idsOrOptionsWithoutRelations,
true,
customJoinsBuilders
)
entities = result[0]
count = result[1]
}
const entitiesIds = entities.map(({ id }) => id)
if (entitiesIds.length === 0) {
// no need to continue
return [[], count]
}
const result = await queryEntityWithoutRelations(
if (Object.keys(relations).length === 0) {
const options = { ...idsOrOptionsWithoutRelations }
// Since we are finding by the ids that have been retrieved above and those ids are already
// applying skip/take. Remove those options to avoid getting no results
delete options.skip
delete options.take
const toReturn = await this.find({
...options,
where: { id: In(entitiesIds) },
})
return [toReturn, toReturn.length]
}
const legacyRelations = buildLegacyFieldsListFrom(relations)
const groupedRelations = getGroupedRelations(legacyRelations)
const legacySelect = buildLegacyFieldsListFrom(
idsOrOptionsWithoutRelations.select
)
const entitiesIdsWithRelations = await queryEntityWithIds(
this,
idsOrOptionsWithoutRelations,
true,
customJoinsBuilders
entitiesIds,
groupedRelations,
idsOrOptionsWithoutRelations.withDeleted,
legacySelect
)
entities = result[0]
count = result[1]
}
const entitiesIds = entities.map(({ id }) => id)
if (entitiesIds.length === 0) {
// no need to continue
return [[], count]
}
const entitiesAndRelations = entitiesIdsWithRelations.concat(entities)
const entitiesToReturn =
mergeEntitiesWithRelations<CustomerGroup>(entitiesAndRelations)
if (relations.length === 0) {
const options = { ...idsOrOptionsWithoutRelations }
return [entitiesToReturn, count]
},
})
// Since we are finding by the ids that have been retrieved above and those ids are already
// applying skip/take. Remove those options to avoid getting no results
delete options.skip
delete options.take
const toReturn = await this.findByIds(entitiesIds, options)
return [toReturn, toReturn.length]
}
const groupedRelations = getGroupedRelations(relations)
const entitiesIdsWithRelations = await queryEntityWithIds(
this,
entitiesIds,
groupedRelations,
idsOrOptionsWithoutRelations.withDeleted,
idsOrOptionsWithoutRelations.select
)
const entitiesAndRelations = entitiesIdsWithRelations.concat(entities)
const entitiesToReturn =
mergeEntitiesWithRelations<CustomerGroup>(entitiesAndRelations)
return [entitiesToReturn, count]
}
}
export default CustomerGroupRepository
+39 -39
View File
@@ -1,49 +1,49 @@
import { Brackets, EntityRepository, ILike, Repository } from "typeorm"
import { Customer } from "../models/customer"
import { ExtendedFindConfig, Selector } from "../types/common"
import { FindOperator, FindOptionsWhere, ILike, In } from "typeorm"
import { Customer } from "../models"
import { dataSource } from "../loaders/database"
import { ExtendedFindConfig } from "../types/common"
@EntityRepository(Customer)
export class CustomerRepository extends Repository<Customer> {
export const CustomerRepository = dataSource.getRepository(Customer).extend({
async listAndCount(
query: ExtendedFindConfig<Customer, Selector<Customer>>,
query: ExtendedFindConfig<Customer> & {
where: FindOptionsWhere<Customer & { groups?: FindOperator<string[]> }>
},
q: string | undefined = undefined
): Promise<[Customer[], number]> {
const groups = query.where.groups as { value: string[] }
delete query.where.groups
const query_ = { ...query }
const qb = this.createQueryBuilder("customer")
.skip(query.skip)
.take(query.take)
if (query_.where.groups) {
query_.relations = query_.relations ?? {}
query_.relations.groups = query_.relations.groups ?? true
query.where.groups = {
id: In((query_.where.groups as FindOperator<string[]>).value),
}
}
if (q) {
delete query.where.email
delete query.where.first_name
delete query.where.last_name
query_.where = query_.where as FindOptionsWhere<Customer>
delete query_.where.email
delete query_.where.first_name
delete query_.where.last_name
qb.where(
new Brackets((qb) => {
qb.where({ email: ILike(`%${q}%`) })
.orWhere({ first_name: ILike(`%${q}%`) })
.orWhere({ last_name: ILike(`%${q}%`) })
})
)
query_.where = [
{
...query_.where,
email: ILike(`%${q}%`),
},
{
...query_.where,
first_name: ILike(`%${q}%`),
},
{
...query_.where,
last_name: ILike(`%${q}%`),
},
]
}
qb.andWhere(query.where)
if (groups) {
qb.leftJoinAndSelect("customer.groups", "group").andWhere(
`group.id IN (:...ids)`,
{ ids: groups.value }
)
}
if (query.relations?.length) {
query.relations.forEach((rel) => {
qb.leftJoinAndSelect(`customer.${rel}`, rel)
})
}
return await qb.getManyAndCount()
}
}
return await this.findAndCount(query_)
},
})
export default CustomerRepository
@@ -1,11 +1,4 @@
import {
DeleteResult,
EntityRepository,
EntityTarget,
In,
Not,
Repository,
} from "typeorm"
import { DeleteResult, EntityTarget, In, Not } from "typeorm"
import {
Discount,
DiscountCondition,
@@ -18,6 +11,7 @@ import {
DiscountConditionType,
} from "../models"
import { isString } from "../utils"
import { dataSource } from "../loaders/database"
export enum DiscountConditionJoinTableForeignKey {
PRODUCT_ID = "product_id",
@@ -35,301 +29,305 @@ type DiscountConditionResourceType = EntityTarget<
| DiscountConditionCustomerGroup
>
@EntityRepository(DiscountCondition)
export class DiscountConditionRepository extends Repository<DiscountCondition> {
async findOneWithDiscount(
conditionId: string,
discountId: string
): Promise<(DiscountCondition & { discount: Discount }) | undefined> {
return (await this.createQueryBuilder("condition")
.leftJoinAndMapOne(
"condition.discount",
Discount,
"discount",
`condition.discount_rule_id = discount.rule_id and discount.id = :discId and condition.id = :dcId`,
{ discId: discountId, dcId: conditionId }
)
.getOne()) as (DiscountCondition & { discount: Discount }) | undefined
}
export const DiscountConditionRepository = dataSource
.getRepository(DiscountCondition)
.extend({
async findOneWithDiscount(
conditionId: string,
discountId: string
): Promise<(DiscountCondition & { discount: Discount }) | undefined> {
return (await this.createQueryBuilder("condition")
.leftJoinAndMapOne(
"condition.discount",
Discount,
"discount",
`condition.discount_rule_id = discount.rule_id and discount.id = :discId and condition.id = :dcId`,
{ discId: discountId, dcId: conditionId }
)
.getOne()) as (DiscountCondition & { discount: Discount }) | undefined
},
getJoinTableResourceIdentifiers(type: string): {
joinTable: string
resourceKey: string
joinTableForeignKey: DiscountConditionJoinTableForeignKey
conditionTable: DiscountConditionResourceType
joinTableKey: string
} {
let conditionTable: DiscountConditionResourceType = DiscountConditionProduct
getJoinTableResourceIdentifiers(type: string): {
joinTable: string
resourceKey: string
joinTableForeignKey: DiscountConditionJoinTableForeignKey
conditionTable: DiscountConditionResourceType
joinTableKey: string
} {
let conditionTable: DiscountConditionResourceType =
DiscountConditionProduct
let joinTable = "product"
let joinTableForeignKey: DiscountConditionJoinTableForeignKey =
DiscountConditionJoinTableForeignKey.PRODUCT_ID
let joinTableKey = "id"
let joinTable = "product"
let joinTableForeignKey: DiscountConditionJoinTableForeignKey =
DiscountConditionJoinTableForeignKey.PRODUCT_ID
let joinTableKey = "id"
// On the joined table (e.g. `product`), what key should be match on
// (e.g `type_id` for product types and `id` for products)
let resourceKey
// On the joined table (e.g. `product`), what key should be match on
// (e.g `type_id` for product types and `id` for products)
let resourceKey
switch (type) {
case DiscountConditionType.PRODUCTS: {
resourceKey = "id"
joinTableForeignKey = DiscountConditionJoinTableForeignKey.PRODUCT_ID
joinTable = "product"
switch (type) {
case DiscountConditionType.PRODUCTS: {
resourceKey = "id"
joinTableForeignKey = DiscountConditionJoinTableForeignKey.PRODUCT_ID
joinTable = "product"
conditionTable = DiscountConditionProduct
break
conditionTable = DiscountConditionProduct
break
}
case DiscountConditionType.PRODUCT_TYPES: {
resourceKey = "type_id"
joinTableForeignKey =
DiscountConditionJoinTableForeignKey.PRODUCT_TYPE_ID
joinTable = "product"
conditionTable = DiscountConditionProductType
break
}
case DiscountConditionType.PRODUCT_COLLECTIONS: {
resourceKey = "collection_id"
joinTableForeignKey =
DiscountConditionJoinTableForeignKey.PRODUCT_COLLECTION_ID
joinTable = "product"
conditionTable = DiscountConditionProductCollection
break
}
case DiscountConditionType.PRODUCT_TAGS: {
joinTableKey = "product_id"
resourceKey = "product_tag_id"
joinTableForeignKey =
DiscountConditionJoinTableForeignKey.PRODUCT_TAG_ID
joinTable = "product_tags"
conditionTable = DiscountConditionProductTag
break
}
case DiscountConditionType.CUSTOMER_GROUPS: {
joinTableKey = "customer_id"
resourceKey = "customer_group_id"
joinTable = "customer_group_customers"
joinTableForeignKey =
DiscountConditionJoinTableForeignKey.CUSTOMER_GROUP_ID
conditionTable = DiscountConditionCustomerGroup
break
}
default:
break
}
case DiscountConditionType.PRODUCT_TYPES: {
resourceKey = "type_id"
joinTableForeignKey =
DiscountConditionJoinTableForeignKey.PRODUCT_TYPE_ID
joinTable = "product"
conditionTable = DiscountConditionProductType
break
return {
joinTable,
joinTableKey,
resourceKey,
joinTableForeignKey,
conditionTable,
}
case DiscountConditionType.PRODUCT_COLLECTIONS: {
resourceKey = "collection_id"
joinTableForeignKey =
DiscountConditionJoinTableForeignKey.PRODUCT_COLLECTION_ID
joinTable = "product"
},
conditionTable = DiscountConditionProductCollection
break
async removeConditionResources(
id: string,
type: DiscountConditionType,
resourceIds: (string | { id: string })[]
): Promise<DeleteResult | void> {
const { conditionTable, joinTableForeignKey } =
this.getJoinTableResourceIdentifiers(type)
if (!conditionTable || !joinTableForeignKey) {
return Promise.resolve()
}
case DiscountConditionType.PRODUCT_TAGS: {
joinTableKey = "product_id"
resourceKey = "product_tag_id"
joinTableForeignKey =
DiscountConditionJoinTableForeignKey.PRODUCT_TAG_ID
joinTable = "product_tags"
conditionTable = DiscountConditionProductTag
break
}
case DiscountConditionType.CUSTOMER_GROUPS: {
joinTableKey = "customer_id"
resourceKey = "customer_group_id"
joinTable = "customer_group_customers"
joinTableForeignKey =
DiscountConditionJoinTableForeignKey.CUSTOMER_GROUP_ID
conditionTable = DiscountConditionCustomerGroup
break
}
default:
break
}
return {
joinTable,
joinTableKey,
resourceKey,
joinTableForeignKey,
conditionTable,
}
}
async removeConditionResources(
id: string,
type: DiscountConditionType,
resourceIds: (string | { id: string })[]
): Promise<DeleteResult | void> {
const { conditionTable, joinTableForeignKey } =
this.getJoinTableResourceIdentifiers(type)
if (!conditionTable || !joinTableForeignKey) {
return Promise.resolve()
}
const idsToDelete = resourceIds.map((rId): string => {
return isString(rId) ? rId : rId.id
})
return await this.createQueryBuilder()
.delete()
.from(conditionTable)
.where({ condition_id: id, [joinTableForeignKey]: In(idsToDelete) })
.execute()
}
async addConditionResources(
conditionId: string,
resourceIds: (string | { id: string })[],
type: DiscountConditionType,
overrideExisting = false
): Promise<
(
| DiscountConditionProduct
| DiscountConditionProductType
| DiscountConditionProductCollection
| DiscountConditionProductTag
| DiscountConditionCustomerGroup
)[]
> {
let toInsert: { condition_id: string; [x: string]: string }[] | [] = []
const { conditionTable, joinTableForeignKey } =
this.getJoinTableResourceIdentifiers(type)
if (!conditionTable || !joinTableForeignKey) {
return Promise.resolve([])
}
const idsToInsert = resourceIds.map((rId): string => {
return isString(rId) ? rId : rId.id
})
toInsert = idsToInsert.map((rId) => ({
condition_id: conditionId,
[joinTableForeignKey]: rId,
}))
const insertResult = await this.createQueryBuilder()
.insert()
.orIgnore(true)
.into(conditionTable)
.values(toInsert)
.execute()
if (overrideExisting) {
await this.createQueryBuilder()
const idsToDelete = resourceIds.map((rId): string => {
return isString(rId) ? rId : rId.id
})
return await this.createQueryBuilder()
.delete()
.from(conditionTable)
.where({
condition_id: conditionId,
[joinTableForeignKey]: Not(In(idsToInsert)),
})
.where({ condition_id: id, [joinTableForeignKey]: In(idsToDelete) })
.execute()
}
},
return await this.manager
.createQueryBuilder(conditionTable, "discon")
.select()
.where(insertResult.identifiers)
.getMany()
}
async addConditionResources(
conditionId: string,
resourceIds: (string | { id: string })[],
type: DiscountConditionType,
overrideExisting = false
): Promise<
(
| DiscountConditionProduct
| DiscountConditionProductType
| DiscountConditionProductCollection
| DiscountConditionProductTag
| DiscountConditionCustomerGroup
)[]
> {
let toInsert: { condition_id: string; [x: string]: string }[] | [] = []
async queryConditionTable({ type, condId, resourceId }): Promise<number> {
const {
conditionTable,
joinTable,
joinTableForeignKey,
resourceKey,
joinTableKey,
} = this.getJoinTableResourceIdentifiers(type)
const { conditionTable, joinTableForeignKey } =
this.getJoinTableResourceIdentifiers(type)
return await this.manager
.createQueryBuilder(conditionTable, "dc")
.innerJoin(
if (!conditionTable || !joinTableForeignKey) {
return Promise.resolve([])
}
const idsToInsert = resourceIds.map((rId): string => {
return isString(rId) ? rId : rId.id
})
toInsert = idsToInsert.map((rId) => ({
condition_id: conditionId,
[joinTableForeignKey]: rId,
}))
const insertResult = await this.createQueryBuilder()
.insert()
.orIgnore(true)
.into(conditionTable)
.values(toInsert)
.execute()
if (overrideExisting) {
await this.createQueryBuilder()
.delete()
.from(conditionTable)
.where({
condition_id: conditionId,
[joinTableForeignKey]: Not(In(idsToInsert)),
})
.execute()
}
return await this.manager
.createQueryBuilder(conditionTable, "discon")
.select()
.where(insertResult.identifiers)
.getMany()
},
async queryConditionTable({ type, condId, resourceId }): Promise<number> {
const {
conditionTable,
joinTable,
"resource",
`dc.${joinTableForeignKey} = resource.${resourceKey} and resource.${joinTableKey} = :resourceId `,
{
resourceId,
joinTableForeignKey,
resourceKey,
joinTableKey,
} = this.getJoinTableResourceIdentifiers(type)
return await this.manager
.createQueryBuilder(conditionTable, "dc")
.innerJoin(
joinTable,
"resource",
`dc.${joinTableForeignKey} = resource.${resourceKey} and resource.${joinTableKey} = :resourceId `,
{
resourceId,
}
)
.where(`dc.condition_id = :conditionId`, {
conditionId: condId,
})
.getCount()
},
async isValidForProduct(
discountRuleId: string,
productId: string
): Promise<boolean> {
const discountConditions = await this.createQueryBuilder("discon")
.select(["discon.id", "discon.type", "discon.operator"])
.where("discon.discount_rule_id = :discountRuleId", {
discountRuleId,
})
.getMany()
// in case of no discount conditions, we assume that the discount
// is valid for all
if (!discountConditions.length) {
return true
}
// retrieve all conditions for each type where condition type id is in jointable (products, product_types, product_collections, product_tags)
// "E.g. for a given product condition, give me all products affected by it"
// for each of these types, we check:
// if condition operation is `in` and the query for conditions defined for the given type is empty, the discount is invalid
// if condition operation is `not_in` and the query for conditions defined for the given type is not empty, the discount is invalid
for (const condition of discountConditions) {
if (condition.type === DiscountConditionType.CUSTOMER_GROUPS) {
continue
}
)
.where(`dc.condition_id = :conditionId`, {
conditionId: condId,
})
.getCount()
}
async isValidForProduct(
discountRuleId: string,
productId: string
): Promise<boolean> {
const discountConditions = await this.createQueryBuilder("discon")
.select(["discon.id", "discon.type", "discon.operator"])
.where("discon.discount_rule_id = :discountRuleId", {
discountRuleId,
})
.getMany()
const numConditions = await this.queryConditionTable({
type: condition.type,
condId: condition.id,
resourceId: productId,
})
if (
condition.operator === DiscountConditionOperator.IN &&
numConditions === 0
) {
return false
}
if (
condition.operator === DiscountConditionOperator.NOT_IN &&
numConditions > 0
) {
return false
}
}
// in case of no discount conditions, we assume that the discount
// is valid for all
if (!discountConditions.length) {
return true
}
},
// retrieve all conditions for each type where condition type id is in jointable (products, product_types, product_collections, product_tags)
// "E.g. for a given product condition, give me all products affected by it"
// for each of these types, we check:
// if condition operation is `in` and the query for conditions defined for the given type is empty, the discount is invalid
// if condition operation is `not_in` and the query for conditions defined for the given type is not empty, the discount is invalid
for (const condition of discountConditions) {
if (condition.type === DiscountConditionType.CUSTOMER_GROUPS) {
continue
async canApplyForCustomer(
discountRuleId: string,
customerId: string
): Promise<boolean> {
const discountConditions = await this.createQueryBuilder("discon")
.select(["discon.id", "discon.type", "discon.operator"])
.where("discon.discount_rule_id = :discountRuleId", {
discountRuleId,
})
.andWhere("discon.type = :type", {
type: DiscountConditionType.CUSTOMER_GROUPS,
})
.getMany()
// in case of no discount conditions, we assume that the discount
// is valid for all
if (!discountConditions.length) {
return true
}
const numConditions = await this.queryConditionTable({
type: condition.type,
condId: condition.id,
resourceId: productId,
})
// retrieve conditions for customer groups
// for each customer group
// if condition operation is `in` and the query for customer group conditions is empty, the discount is invalid
// if condition operation is `not_in` and the query for customer group conditions is not empty, the discount is invalid
for (const condition of discountConditions) {
const numConditions = await this.queryConditionTable({
type: "customer_groups",
condId: condition.id,
resourceId: customerId,
})
if (
condition.operator === DiscountConditionOperator.IN &&
numConditions === 0
) {
return false
if (
condition.operator === DiscountConditionOperator.IN &&
numConditions === 0
) {
return false
}
if (
condition.operator === DiscountConditionOperator.NOT_IN &&
numConditions > 0
) {
return false
}
}
if (
condition.operator === DiscountConditionOperator.NOT_IN &&
numConditions > 0
) {
return false
}
}
return true
}
async canApplyForCustomer(
discountRuleId: string,
customerId: string
): Promise<boolean> {
const discountConditions = await this.createQueryBuilder("discon")
.select(["discon.id", "discon.type", "discon.operator"])
.where("discon.discount_rule_id = :discountRuleId", {
discountRuleId,
})
.andWhere("discon.type = :type", {
type: DiscountConditionType.CUSTOMER_GROUPS,
})
.getMany()
// in case of no discount conditions, we assume that the discount
// is valid for all
if (!discountConditions.length) {
return true
}
},
})
// retrieve conditions for customer groups
// for each customer group
// if condition operation is `in` and the query for customer group conditions is empty, the discount is invalid
// if condition operation is `not_in` and the query for customer group conditions is not empty, the discount is invalid
for (const condition of discountConditions) {
const numConditions = await this.queryConditionTable({
type: "customer_groups",
condId: condition.id,
resourceId: customerId,
})
if (
condition.operator === DiscountConditionOperator.IN &&
numConditions === 0
) {
return false
}
if (
condition.operator === DiscountConditionOperator.NOT_IN &&
numConditions > 0
) {
return false
}
}
return true
}
}
export default DiscountConditionRepository
@@ -1,5 +1,5 @@
import { EntityRepository, Repository } from "typeorm"
import { DiscountRule } from "../models/discount-rule"
import { DiscountRule } from "../models"
import { dataSource } from "../loaders/database"
@EntityRepository(DiscountRule)
export class DiscountRuleRepository extends Repository<DiscountRule> {}
export const DiscountRuleRepository = dataSource.getRepository(DiscountRule)
export default DiscountRuleRepository
+4 -4
View File
@@ -1,5 +1,5 @@
import { EntityRepository, Repository } from "typeorm"
import { Discount } from "../models/discount"
import { Discount } from "../models"
import { dataSource } from "../loaders/database"
@EntityRepository(Discount)
export class DiscountRepository extends Repository<Discount> {}
export const DiscountRepository = dataSource.getRepository(Discount)
export default DiscountRepository
@@ -1,5 +1,5 @@
import { EntityRepository, Repository } from "typeorm"
import { DraftOrder } from "../models/draft-order"
import { DraftOrder } from "../models"
import { dataSource } from "../loaders/database"
@EntityRepository(DraftOrder)
export class DraftOrderRepository extends Repository<DraftOrder> {}
export const DraftOrderRepository = dataSource.getRepository(DraftOrder)
export default DraftOrderRepository
@@ -1,6 +1,6 @@
import { EntityRepository, Repository } from "typeorm"
import { FulfillmentProvider } from "../models/fulfillment-provider"
import { FulfillmentProvider } from "../models"
import { dataSource } from "../loaders/database"
@EntityRepository(FulfillmentProvider)
// eslint-disable-next-line max-len
export class FulfillmentProviderRepository extends Repository<FulfillmentProvider> {}
export const FulfillmentProviderRepository =
dataSource.getRepository(FulfillmentProvider)
export default FulfillmentProviderRepository
@@ -1,5 +1,5 @@
import { EntityRepository, Repository } from "typeorm"
import { Fulfillment } from "../models/fulfillment"
import { Fulfillment } from "../models"
import { dataSource } from "../loaders/database"
@EntityRepository(Fulfillment)
export class FulfillmentRepository extends Repository<Fulfillment> {}
export const FulfillmentRepository = dataSource.getRepository(Fulfillment)
export default FulfillmentRepository
@@ -1,6 +1,6 @@
import { EntityRepository, Repository } from "typeorm"
import { GiftCardTransaction } from "../models/gift-card-transaction"
import { GiftCardTransaction } from "../models"
import { dataSource } from "../loaders/database"
@EntityRepository(GiftCardTransaction)
// eslint-disable-next-line max-len
export class GiftCardTransactionRepository extends Repository<GiftCardTransaction> {}
export const GiftCardTransactionRepository =
dataSource.getRepository(GiftCardTransaction)
export default GiftCardTransactionRepository
+31 -133
View File
@@ -1,142 +1,40 @@
import { flatten, groupBy, merge } from "lodash"
import {
Brackets,
EntityRepository,
FindManyOptions,
Repository,
} from "typeorm"
import { GiftCard } from "../models/gift-card"
import { ExtendedFindConfig, QuerySelector, Writable } from "../types/common"
import { FindOptionsWhere, ILike, Raw } from "typeorm"
import { GiftCard } from "../models"
import { ExtendedFindConfig } from "../types/common"
import { dataSource } from "../loaders/database"
@EntityRepository(GiftCard)
export class GiftCardRepository extends Repository<GiftCard> {
public async findWithRelations(
relations: (keyof GiftCard | string)[] = [],
idsOrOptionsWithoutRelations:
| Omit<FindManyOptions<GiftCard>, "relations">
| string[] = {}
): Promise<[GiftCard[], number]> {
let entities: GiftCard[] = []
let count = 0
if (Array.isArray(idsOrOptionsWithoutRelations)) {
entities = await this.findByIds(idsOrOptionsWithoutRelations)
count = idsOrOptionsWithoutRelations.length
} else {
const [results, resultCount] = await this.findAndCount(
idsOrOptionsWithoutRelations
)
entities = results
count = resultCount
}
const entitiesIds = entities.map(({ id }) => id)
const groupedRelations = {}
for (const rel of relations) {
const [topLevel] = rel.split(".")
if (groupedRelations[topLevel]) {
groupedRelations[topLevel].push(rel)
} else {
groupedRelations[topLevel] = [rel]
}
}
const entitiesIdsWithRelations = await Promise.all(
Object.entries(groupedRelations).map(async ([_, rels]) => {
return this.findByIds(entitiesIds, {
select: ["id"],
relations: rels as string[],
})
})
).then(flatten)
const entitiesAndRelations = entitiesIdsWithRelations.concat(entities)
const entitiesAndRelationsById = groupBy(entitiesAndRelations, "id")
return [
Object.values(entitiesAndRelationsById).map((v) => merge({}, ...v)),
count,
]
}
protected async queryGiftCards(
q: string,
where: Partial<Writable<QuerySelector<GiftCard>>>,
rels: (keyof GiftCard | string)[],
shouldCount = false
): Promise<[GiftCard[], number]> {
const qb = this.createQueryBuilder("gift_card")
.leftJoinAndSelect("gift_card.order", "order")
.select(["gift_card.id"])
.where(where)
.andWhere(
new Brackets((qb) => {
return qb
.where(`gift_card.code ILIKE :q`, { q: `%${q}%` })
.orWhere(`display_id::varchar(255) ILIKE :dId`, { dId: `${q}` })
})
)
let raw: GiftCard[] = []
let count = 0
if (shouldCount) {
const [results, resultCount] = await qb.getManyAndCount()
raw = results
count = resultCount
} else {
raw = await qb.getMany()
}
const [results] = await this.findWithRelations(
rels,
raw.map((i) => i.id)
)
return [results, count]
}
public async listGiftCardsAndCount(
inputQuery: ExtendedFindConfig<GiftCard, QuerySelector<GiftCard>>,
rels: (keyof GiftCard | string)[] = [],
export const GiftCardRepository = dataSource.getRepository(GiftCard).extend({
async listGiftCardsAndCount(
query: ExtendedFindConfig<GiftCard>,
q?: string
): Promise<[GiftCard[], number]> {
const query = { ...inputQuery }
const query_ = { ...query }
query_.where = query_.where as FindOptionsWhere<GiftCard>
if (q) {
const where = query.where
delete where.id
delete query_.where.id
return await this.queryGiftCards(q, where, rels, true)
}
return await this.findWithRelations(rels, query)
}
query_.relations = query_.relations ?? {}
query_.relations.order = query_.relations.order ?? true
public async listGiftCards(
query: ExtendedFindConfig<GiftCard, QuerySelector<GiftCard>>,
rels: (keyof GiftCard | string)[] = [],
q?: string
): Promise<GiftCard[]> {
if (q) {
const where = query.where
delete where.id
const [result] = await this.queryGiftCards(q, where, rels)
return result
query_.where = query_.where as FindOptionsWhere<GiftCard>[]
query_.where = [
{
...query_.where,
code: ILike(`%${q}%`),
},
{
...query_.where,
order: {
display_id: Raw((alias) => `CAST(${alias} as varchar) ILike :q`, {
q: `%${q}%`,
}),
},
},
]
}
const [results] = await this.findWithRelations(rels, query)
return results
}
public async findOneWithRelations(
relations: Array<keyof GiftCard> = [],
optionsWithoutRelations: Omit<FindManyOptions<GiftCard>, "relations"> = {}
): Promise<GiftCard> {
// Limit 1
optionsWithoutRelations.take = 1
const [result] = await this.findWithRelations(
relations,
optionsWithoutRelations
)
return result[0]
}
}
return await this.findAndCount(query_)
},
})
export default GiftCardRepository
@@ -1,5 +1,5 @@
import { EntityRepository, Repository } from "typeorm"
import { IdempotencyKey } from "../models/idempotency-key"
import { IdempotencyKey } from "../models"
import { dataSource } from "../loaders/database"
@EntityRepository(IdempotencyKey)
export class IdempotencyKeyRepository extends Repository<IdempotencyKey> {}
export const IdempotencyKeyRepository = dataSource.getRepository(IdempotencyKey)
export default IdempotencyKeyRepository
+8 -7
View File
@@ -1,9 +1,9 @@
import { EntityRepository, In, Repository } from "typeorm"
import { Image } from "../models/image"
import { Image } from "../models"
import { dataSource } from "../loaders/database"
import { In } from "typeorm"
@EntityRepository(Image)
export class ImageRepository extends Repository<Image> {
public async upsertImages(imageUrls: string[]) {
export const ImageRepository = dataSource.getRepository(Image).extend({
async upsertImages(imageUrls: string[]) {
const existingImages = await this.find({
where: {
url: In(imageUrls),
@@ -27,5 +27,6 @@ export class ImageRepository extends Repository<Image> {
}
return upsertedImgs
}
}
},
})
export default ImageRepository
+4 -4
View File
@@ -1,5 +1,5 @@
import { EntityRepository, Repository } from "typeorm"
import { Invite } from "../models/invite"
import { Invite } from "../models"
import { dataSource } from "../loaders/database"
@EntityRepository(Invite)
export class InviteRepository extends Repository<Invite> {}
export const InviteRepository = dataSource.getRepository(Invite)
export default InviteRepository
@@ -1,6 +1,6 @@
import { EntityRepository, Repository } from "typeorm"
import { LineItemAdjustment } from "../models/line-item-adjustment"
import { LineItemAdjustment } from "../models"
import { dataSource } from "../loaders/database"
@EntityRepository(LineItemAdjustment)
// eslint-disable-next-line max-len
export class LineItemAdjustmentRepository extends Repository<LineItemAdjustment> {}
export const LineItemAdjustmentRepository =
dataSource.getRepository(LineItemAdjustment)
export default LineItemAdjustmentRepository
@@ -1,38 +1,37 @@
import { EntityRepository, Repository } from "typeorm"
import { LineItemTaxLine } from "../models/line-item-tax-line"
import { LineItemTaxLine } from "../models"
import { QueryDeepPartialEntity } from "typeorm/query-builder/QueryPartialEntity"
import { dataSource } from "../loaders/database"
@EntityRepository(LineItemTaxLine)
export class LineItemTaxLineRepository extends Repository<LineItemTaxLine> {
async upsertLines(lines: LineItemTaxLine[]): Promise<LineItemTaxLine[]> {
const insertResult = await this.createQueryBuilder()
.insert()
.values(lines as QueryDeepPartialEntity<LineItemTaxLine>[])
.orUpdate({
conflict_target: ["item_id", "code"],
overwrite: ["rate", "name", "updated_at"],
})
.execute()
export const LineItemTaxLineRepository = dataSource
.getRepository(LineItemTaxLine)
.extend({
async upsertLines(lines: LineItemTaxLine[]): Promise<LineItemTaxLine[]> {
const insertResult = await this.createQueryBuilder()
.insert()
.values(lines as QueryDeepPartialEntity<LineItemTaxLine>[])
.orUpdate(["rate", "name", "updated_at"], ["item_id", "code"])
.execute()
return insertResult.identifiers as LineItemTaxLine[]
}
return insertResult.identifiers as LineItemTaxLine[]
},
async deleteForCart(cartId: string): Promise<void> {
const qb = this.createQueryBuilder("line")
.select(["line.id"])
.innerJoin("line_item", "i", "i.id = line.item_id")
.innerJoin(
"cart",
"c",
"i.cart_id = :cartId AND c.completed_at is NULL",
{ cartId }
)
async deleteForCart(cartId: string): Promise<void> {
const qb = this.createQueryBuilder("line")
.select(["line.id"])
.innerJoin("line_item", "i", "i.id = line.item_id")
.innerJoin(
"cart",
"c",
"i.cart_id = :cartId AND c.completed_at is NULL",
{ cartId }
)
const toDelete = await qb.getMany()
const toDelete = await qb.getMany()
await this.createQueryBuilder()
.delete()
.whereInIds(toDelete.map((d) => d.id))
.execute()
}
}
await this.createQueryBuilder()
.delete()
.whereInIds(toDelete.map((d) => d.id))
.execute()
},
})
export default LineItemTaxLineRepository
@@ -1,9 +1,8 @@
import { EntityRepository, Repository } from "typeorm"
import { LineItem } from "../models/line-item"
import { ReturnItem } from "../models/return-item"
import { dataSource } from "../loaders/database"
@EntityRepository(LineItem)
export class LineItemRepository extends Repository<LineItem> {
export const LineItemRepository = dataSource.getRepository(LineItem).extend({
/**
* Finds line items that are to be returned as part of the Return represented
* by `returnId`. The function joins the associated ReturnItem and the
@@ -27,5 +26,7 @@ export class LineItemRepository extends Repository<LineItem> {
.where(`ri.return_id = :returnId`, { returnId })
return (await qb.getMany()) as (LineItem & { return_item: ReturnItem })[]
}
}
},
})
export default LineItemRepository
+227 -227
View File
@@ -1,20 +1,19 @@
import partition from "lodash/partition"
import {
Brackets,
EntityRepository,
In,
IsNull,
Not,
ObjectLiteral,
Repository,
WhereExpressionBuilder,
} from "typeorm"
import { QueryDeepPartialEntity } from "typeorm/query-builder/QueryPartialEntity"
import { MoneyAmount } from "../models/money-amount"
import { MoneyAmount } from "../models"
import {
PriceListPriceCreateInput,
PriceListPriceUpdateInput,
} from "../types/price-list"
import { QueryDeepPartialEntity } from "typeorm/query-builder/QueryPartialEntity"
import { dataSource } from "../loaders/database"
type Price = Partial<
Omit<MoneyAmount, "created_at" | "updated_at" | "deleted_at">
@@ -22,252 +21,253 @@ type Price = Partial<
amount: number
}
@EntityRepository(MoneyAmount)
export class MoneyAmountRepository extends Repository<MoneyAmount> {
/**
* Will be removed in a future release.
* Use `deleteVariantPricesNotIn` instead.
* @deprecated
*/
public async findVariantPricesNotIn(
variantId: string,
prices: Price[]
): Promise<MoneyAmount[]> {
const pricesNotInPricesPayload = await this.createQueryBuilder()
.where({
export const MoneyAmountRepository = dataSource
.getRepository(MoneyAmount)
.extend({
async findVariantPricesNotIn(
variantId: string,
prices: Price[]
): Promise<MoneyAmount[]> {
const pricesNotInPricesPayload = await this.createQueryBuilder()
.where({
variant_id: variantId,
price_list_id: IsNull(),
})
.andWhere(
new Brackets((qb) => {
qb.where({
currency_code: Not(In(prices.map((p) => p.currency_code))),
}).orWhere({ region_id: Not(In(prices.map((p) => p.region_id))) })
})
)
.getMany()
return pricesNotInPricesPayload
},
async upsertVariantCurrencyPrice(
variantId: string,
price: Price
): Promise<MoneyAmount> {
let moneyAmount = await this.findOne({
where: {
currency_code: price.currency_code,
variant_id: variantId,
region_id: IsNull(),
price_list_id: IsNull(),
},
})
if (!moneyAmount) {
moneyAmount = this.create({
...price,
currency_code: price.currency_code?.toLowerCase(),
variant_id: variantId,
})
} else {
moneyAmount.amount = price.amount
}
return await this.save(moneyAmount)
},
async deleteVariantPricesNotIn(
variantId: string,
prices: Price[]
): Promise<void> {
const where = {
variant_id: variantId,
price_list_id: IsNull(),
})
.andWhere(
new Brackets((qb) => {
qb.where({
currency_code: Not(In(prices.map((p) => p.currency_code))),
}).orWhere({ region_id: Not(In(prices.map((p) => p.region_id))) })
}
const orWhere: ObjectLiteral[] = []
for (const price of prices) {
if (price.currency_code) {
orWhere.push(
{
currency_code: Not(price.currency_code),
},
{
region_id: price.region_id ? Not(price.region_id) : Not(IsNull()),
currency_code: price.currency_code,
}
)
}
if (price.region_id) {
orWhere.push({
region_id: Not(price.region_id),
})
}
}
await this.createQueryBuilder()
.delete()
.where(where)
.andWhere(orWhere)
.execute()
},
async addPriceListPrices(
priceListId: string,
prices: PriceListPriceCreateInput[],
overrideExisting = false
): Promise<MoneyAmount[]> {
const toInsert = prices.map((price) =>
this.create({
...price,
price_list_id: priceListId,
})
)
.getMany()
return pricesNotInPricesPayload
}
public async deleteVariantPricesNotIn(
variantId: string,
prices: Price[]
): Promise<void> {
const where = {
variant_id: variantId,
price_list_id: IsNull(),
}
const insertResult = await this.createQueryBuilder()
.insert()
.orIgnore(true)
.into(MoneyAmount)
.values(toInsert as QueryDeepPartialEntity<MoneyAmount>[])
.execute()
const orWhere: ObjectLiteral[] = []
for (const price of prices) {
if (price.currency_code) {
orWhere.push(
{
currency_code: Not(price.currency_code),
},
{
region_id: price.region_id ? Not(price.region_id) : Not(IsNull()),
currency_code: price.currency_code,
}
)
if (overrideExisting) {
await this.createQueryBuilder()
.delete()
.from(MoneyAmount)
.where({
price_list_id: priceListId,
id: Not(In(insertResult.identifiers.map((ma) => ma.id))),
})
.execute()
}
if (price.region_id) {
orWhere.push({
region_id: Not(price.region_id),
})
}
}
return await this.manager
.createQueryBuilder(MoneyAmount, "ma")
.select()
.where(insertResult.identifiers)
.getMany()
},
await this.createQueryBuilder()
.delete()
.where(where)
.andWhere(orWhere)
.execute()
}
public async upsertVariantCurrencyPrice(
variantId: string,
price: Price
): Promise<MoneyAmount> {
let moneyAmount = await this.findOne({
where: {
currency_code: price.currency_code,
variant_id: variantId,
region_id: IsNull(),
price_list_id: IsNull(),
},
})
if (!moneyAmount) {
moneyAmount = this.create({
...price,
currency_code: price.currency_code?.toLowerCase(),
variant_id: variantId,
})
} else {
moneyAmount.amount = price.amount
}
return await this.save(moneyAmount)
}
public async addPriceListPrices(
priceListId: string,
prices: PriceListPriceCreateInput[],
overrideExisting = false
): Promise<MoneyAmount[]> {
const toInsert = prices.map((price) =>
this.create({
...price,
price_list_id: priceListId,
})
)
const insertResult = await this.createQueryBuilder()
.insert()
.orIgnore(true)
.into(MoneyAmount)
.values(toInsert as QueryDeepPartialEntity<MoneyAmount>[])
.execute()
if (overrideExisting) {
async deletePriceListPrices(
priceListId: string,
moneyAmountIds: string[]
): Promise<void> {
await this.createQueryBuilder()
.delete()
.from(MoneyAmount)
.where({
price_list_id: priceListId,
id: Not(In(insertResult.identifiers.map((ma) => ma.id))),
})
.where({ price_list_id: priceListId, id: In(moneyAmountIds) })
.execute()
}
},
return await this.manager
.createQueryBuilder(MoneyAmount, "ma")
.select()
.where(insertResult.identifiers)
.getMany()
}
async findManyForVariantInPriceList(
variant_id: string,
price_list_id: string,
requiresPriceList = false
): Promise<[MoneyAmount[], number]> {
const qb = this.createQueryBuilder("ma")
.leftJoinAndSelect("ma.price_list", "price_list")
.where("ma.variant_id = :variant_id", { variant_id })
public async deletePriceListPrices(
priceListId: string,
moneyAmountIds: string[]
): Promise<void> {
await this.createQueryBuilder()
.delete()
.from(MoneyAmount)
.where({ price_list_id: priceListId, id: In(moneyAmountIds) })
.execute()
}
public async findManyForVariantInPriceList(
variant_id: string,
price_list_id: string,
requiresPriceList = false
): Promise<[MoneyAmount[], number]> {
const qb = this.createQueryBuilder("ma")
.leftJoinAndSelect("ma.price_list", "price_list")
.where("ma.variant_id = :variant_id", { variant_id })
const getAndWhere = (subQb): WhereExpressionBuilder => {
const andWhere = subQb.where("ma.price_list_id = :price_list_id", {
price_list_id,
})
if (!requiresPriceList) {
andWhere.orWhere("ma.price_list_id IS NULL")
}
return andWhere
}
qb.andWhere(new Brackets(getAndWhere))
return await qb.getManyAndCount()
}
public async findManyForVariantInRegion(
variant_id: string,
region_id?: string,
currency_code?: string,
customer_id?: string,
include_discount_prices?: boolean,
include_tax_inclusive_pricing = false
): Promise<[MoneyAmount[], number]> {
const date = new Date()
const qb = this.createQueryBuilder("ma")
.leftJoinAndSelect("ma.price_list", "price_list")
.where({ variant_id: variant_id })
.andWhere("(ma.price_list_id is null or price_list.status = 'active')")
.andWhere("(price_list.ends_at is null OR price_list.ends_at > :date)", {
date: date.toUTCString(),
})
.andWhere(
"(price_list.starts_at is null OR price_list.starts_at < :date)",
{
date: date.toUTCString(),
}
)
if (include_tax_inclusive_pricing) {
qb.leftJoin("ma.currency", "currency")
.leftJoin("ma.region", "region")
.addSelect(["currency.includes_tax", "region.includes_tax"])
}
if (region_id || currency_code) {
qb.andWhere(
new Brackets((qb) => {
if (region_id && !currency_code) {
qb.where({ region_id: region_id })
}
if (!region_id && currency_code) {
qb.where({ currency_code: currency_code })
}
if (currency_code && region_id) {
qb.where({ region_id: region_id }).orWhere({
currency_code: currency_code,
})
}
const getAndWhere = (subQb): WhereExpressionBuilder => {
const andWhere = subQb.where("ma.price_list_id = :price_list_id", {
price_list_id,
})
)
} else if (!customer_id && !include_discount_prices) {
qb.andWhere("price_list.id IS null")
}
if (!requiresPriceList) {
andWhere.orWhere("ma.price_list_id IS NULL")
}
return andWhere
}
if (customer_id) {
qb.leftJoin("price_list.customer_groups", "cgroup")
.leftJoin(
"customer_group_customers",
"cgc",
"cgc.customer_group_id = cgroup.id"
qb.andWhere(new Brackets(getAndWhere))
return await qb.getManyAndCount()
},
async findManyForVariantInRegion(
variant_id: string,
region_id?: string,
currency_code?: string,
customer_id?: string,
include_discount_prices?: boolean,
include_tax_inclusive_pricing = false
): Promise<[MoneyAmount[], number]> {
const date = new Date()
const qb = this.createQueryBuilder("ma")
.leftJoinAndSelect("ma.price_list", "price_list")
.where({ variant_id: variant_id })
.andWhere("(ma.price_list_id is null or price_list.status = 'active')")
.andWhere(
"(price_list.ends_at is null OR price_list.ends_at > :date)",
{
date: date.toUTCString(),
}
)
.andWhere(
"(cgc.customer_group_id is null OR cgc.customer_id = :customer_id)",
"(price_list.starts_at is null OR price_list.starts_at < :date)",
{
customer_id,
date: date.toUTCString(),
}
)
} else {
qb.leftJoin("price_list.customer_groups", "cgroup").andWhere(
"cgroup.id is null"
if (include_tax_inclusive_pricing) {
qb.leftJoin("ma.currency", "currency")
.leftJoin("ma.region", "region")
.addSelect(["currency.includes_tax", "region.includes_tax"])
}
if (region_id || currency_code) {
qb.andWhere(
new Brackets((qb) => {
if (region_id && !currency_code) {
qb.where({ region_id: region_id })
}
if (!region_id && currency_code) {
qb.where({ currency_code: currency_code })
}
if (currency_code && region_id) {
qb.where({ region_id: region_id }).orWhere({
currency_code: currency_code,
})
}
})
)
} else if (!customer_id && !include_discount_prices) {
qb.andWhere("price_list.id IS null")
}
if (customer_id) {
qb.leftJoin("price_list.customer_groups", "cgroup")
.leftJoin(
"customer_group_customers",
"cgc",
"cgc.customer_group_id = cgroup.id"
)
.andWhere(
"(cgc.customer_group_id is null OR cgc.customer_id = :customer_id)",
{
customer_id,
}
)
} else {
qb.leftJoin("price_list.customer_groups", "cgroup").andWhere(
"cgroup.id is null"
)
}
return await qb.getManyAndCount()
},
async updatePriceListPrices(
priceListId: string,
updates: PriceListPriceUpdateInput[]
): Promise<MoneyAmount[]> {
const [existingPrices, newPrices] = partition(
updates,
(update) => update.id
)
}
return await qb.getManyAndCount()
}
public async updatePriceListPrices(
priceListId: string,
updates: PriceListPriceUpdateInput[]
): Promise<MoneyAmount[]> {
const [existingPrices, newPrices] = partition(
updates,
(update) => update.id
)
const newPriceEntities = newPrices.map((price) =>
this.create({ ...price, price_list_id: priceListId })
)
const newPriceEntities = newPrices.map((price) =>
this.create({ ...price, price_list_id: priceListId })
)
return await this.save([...existingPrices, ...newPriceEntities])
},
})
return await this.save([...existingPrices, ...newPriceEntities])
}
}
export default MoneyAmountRepository
+4 -4
View File
@@ -1,5 +1,5 @@
import { EntityRepository, Repository } from "typeorm"
import { Note } from "../models/note"
import { Note } from "../models"
import { dataSource } from "../loaders/database"
@EntityRepository(Note)
export class NoteRepository extends Repository<Note> {}
export const NoteRepository = dataSource.getRepository(Note)
export default NoteRepository
@@ -1,6 +1,6 @@
import { EntityRepository, Repository } from "typeorm"
import { NotificationProvider } from "../models/notification-provider"
import { dataSource } from "../loaders/database"
@EntityRepository(NotificationProvider)
// eslint-disable-next-line max-len
export class NotificationProviderRepository extends Repository<NotificationProvider> {}
export const NotificationProviderRepository =
dataSource.getRepository(NotificationProvider)
export default NotificationProviderRepository
@@ -1,5 +1,5 @@
import { EntityRepository, Repository } from "typeorm"
import { Notification } from "../models/notification"
import { Notification } from "../models"
import { dataSource } from "../loaders/database"
@EntityRepository(Notification)
export class NotificationRepository extends Repository<Notification> {}
export const NotificationRepository = dataSource.getRepository(Notification)
export default NotificationRepository
+4 -4
View File
@@ -1,5 +1,5 @@
import { EntityRepository, Repository } from "typeorm"
import { Oauth } from "../models/oauth"
import { Oauth } from "../models"
import { dataSource } from "../loaders/database"
@EntityRepository(Oauth)
export class OauthRepository extends Repository<Oauth> {}
export const OauthRepository = dataSource.getRepository(Oauth)
export default OauthRepository
@@ -1,5 +1,5 @@
import { EntityRepository, Repository } from "typeorm"
import { OrderEdit } from "../models"
import { OrderEdit } from "../models/order-edit"
import { dataSource } from "../loaders/database"
@EntityRepository(OrderEdit)
export class OrderEditRepository extends Repository<OrderEdit> {}
export const OrderEditRepository = dataSource.getRepository(OrderEdit)
export default OrderEditRepository
@@ -1,6 +1,6 @@
import { EntityRepository, Repository } from "typeorm"
import { OrderItemChange } from "../models"
import { dataSource } from "../loaders/database"
import { OrderItemChange } from "../models/order-item-change"
@EntityRepository(OrderItemChange)
export class OrderItemChangeRepository extends Repository<OrderItemChange> {}
export const OrderItemChangeRepository =
dataSource.getRepository(OrderItemChange)
export default OrderItemChangeRepository
+16 -12
View File
@@ -1,21 +1,22 @@
import { flatten, groupBy, map, merge } from "lodash"
import { EntityRepository, FindManyOptions, Repository } from "typeorm"
import { FindManyOptions, FindOptionsRelations, In } from "typeorm"
import { Order } from "../models"
import { buildLegacyFieldsListFrom } from "../utils"
import { dataSource } from "../loaders/database"
const ITEMS_REL_NAME = "items"
const REGION_REL_NAME = "region"
@EntityRepository(Order)
export class OrderRepository extends Repository<Order> {
public async findWithRelations(
relations: string[] = [],
export const OrderRepository = dataSource.getRepository(Order).extend({
async findWithRelations(
relations: FindOptionsRelations<Order> = {},
optionsWithoutRelations: Omit<FindManyOptions<Order>, "relations"> = {}
): Promise<Order[]> {
const entities = await this.find(optionsWithoutRelations)
const entitiesIds = entities.map(({ id }) => id)
const groupedRelations: { [topLevel: string]: string[] } = {}
for (const rel of relations) {
for (const rel of buildLegacyFieldsListFrom(relations)) {
const [topLevel] = rel.split(".")
if (groupedRelations[topLevel]) {
groupedRelations[topLevel].push(rel)
@@ -27,7 +28,8 @@ export class OrderRepository extends Repository<Order> {
const entitiesIdsWithRelations = await Promise.all(
Object.entries(groupedRelations).map(async ([topLevel, rels]) => {
// If top level is region or items then get deleted region as well
return this.findByIds(entitiesIds, {
return this.find({
where: { id: In(entitiesIds) },
select: ["id"],
relations: rels,
withDeleted:
@@ -41,10 +43,10 @@ export class OrderRepository extends Repository<Order> {
const entitiesAndRelationsById = groupBy(entitiesAndRelations, "id")
return map(entities, (e) => merge({}, ...entitiesAndRelationsById[e.id]))
}
},
public async findOneWithRelations(
relations: string[] = [],
async findOneWithRelations(
relations: FindOptionsRelations<Order> = {},
optionsWithoutRelations: Omit<FindManyOptions<Order>, "relations"> = {}
): Promise<Order> {
// Limit 1
@@ -55,5 +57,7 @@ export class OrderRepository extends Repository<Order> {
optionsWithoutRelations
)
return result[0]
}
}
},
})
export default OrderRepository
@@ -1,63 +1,65 @@
import { MedusaError } from "medusa-core-utils"
import { PaymentCollection } from "./../models/payment-collection"
import { EntityRepository, Repository } from "typeorm"
import { FindConfig } from "../types/common"
import { dataSource } from "../loaders/database"
@EntityRepository(PaymentCollection)
// eslint-disable-next-line max-len
export class PaymentCollectionRepository extends Repository<PaymentCollection> {
async getPaymentCollectionIdBySessionId(
sessionId: string,
config: FindConfig<PaymentCollection> = {}
): Promise<PaymentCollection> {
const paymentCollection = await this.find({
join: {
alias: "payment_col",
innerJoin: { payment_sessions: "payment_col.payment_sessions" },
},
where: (qb) => {
qb.where(
"payment_col_payment_sessions.payment_session_id = :sessionId",
{ sessionId }
export const PaymentCollectionRepository = dataSource
.getRepository(PaymentCollection)
.extend({
async getPaymentCollectionIdBySessionId(
sessionId: string,
config: FindConfig<PaymentCollection> = {}
): Promise<PaymentCollection> {
const paymentCollection = await this.find({
join: {
alias: "payment_col",
innerJoin: { payment_sessions: "payment_col.payment_sessions" },
},
where: {
payment_sessions: {
id: sessionId,
},
},
relations: config.relations,
select: config.select,
})
if (!paymentCollection.length) {
throw new MedusaError(
MedusaError.Types.NOT_FOUND,
`Payment collection related to Payment Session id ${sessionId} was not found`
)
},
relations: config.relations,
select: config.select,
})
}
if (!paymentCollection.length) {
throw new MedusaError(
MedusaError.Types.NOT_FOUND,
`Payment collection related to Payment Session id ${sessionId} was not found`
)
}
return paymentCollection[0]
},
return paymentCollection[0]
}
async getPaymentCollectionIdByPaymentId(
paymentId: string,
config: FindConfig<PaymentCollection> = {}
): Promise<PaymentCollection> {
const paymentCollection = await this.find({
join: {
alias: "payment_col",
innerJoin: { payments: "payment_col.payments" },
},
where: {
payments: {
id: paymentId,
},
},
relations: config.relations,
select: config.select,
})
async getPaymentCollectionIdByPaymentId(
paymentId: string,
config: FindConfig<PaymentCollection> = {}
): Promise<PaymentCollection> {
const paymentCollection = await this.find({
join: {
alias: "payment_col",
innerJoin: { payments: "payment_col.payments" },
},
where: (qb) => {
qb.where("payment_col_payments.payment_id = :paymentId", { paymentId })
},
relations: config.relations,
select: config.select,
})
if (!paymentCollection.length) {
throw new MedusaError(
MedusaError.Types.NOT_FOUND,
`Payment collection related to Payment id ${paymentId} was not found`
)
}
if (!paymentCollection.length) {
throw new MedusaError(
MedusaError.Types.NOT_FOUND,
`Payment collection related to Payment id ${paymentId} was not found`
)
}
return paymentCollection[0]
}
}
return paymentCollection[0]
},
})
export default PaymentCollectionRepository
@@ -1,5 +1,6 @@
import { EntityRepository, Repository } from "typeorm"
import { PaymentProvider } from "../models/payment-provider"
import { PaymentProvider } from "../models"
import { dataSource } from "../loaders/database"
@EntityRepository(PaymentProvider)
export class PaymentProviderRepository extends Repository<PaymentProvider> {}
export const PaymentProviderRepository =
dataSource.getRepository(PaymentProvider)
export default PaymentProviderRepository
@@ -1,5 +1,5 @@
import { EntityRepository, Repository } from "typeorm"
import { PaymentSession } from "../models/payment-session"
import { PaymentSession } from "../models"
import { dataSource } from "../loaders/database"
@EntityRepository(PaymentSession)
export class PaymentSessionRepository extends Repository<PaymentSession> {}
export const PaymentSessionRepository = dataSource.getRepository(PaymentSession)
export default PaymentSessionRepository
+4 -4
View File
@@ -1,5 +1,5 @@
import { EntityRepository, Repository } from "typeorm"
import { Payment } from "../models/payment"
import { Payment } from "../models"
import { dataSource } from "../loaders/database"
@EntityRepository(Payment)
export class PaymentRepository extends Repository<Payment> {}
export const PaymentRepository = dataSource.getRepository(Payment)
export default PaymentRepository
+26 -113
View File
@@ -1,127 +1,40 @@
import { groupBy, map } from "lodash"
import {
Brackets,
EntityRepository,
FindManyOptions,
FindOperator,
Repository,
} from "typeorm"
import { PriceList } from "../models/price-list"
import { FindOptionsWhere, ILike } from "typeorm"
import { PriceList } from "../models"
import { CustomFindOptions, ExtendedFindConfig } from "../types/common"
import { FilterablePriceListProps } from "../types/price-list"
import { dataSource } from "../loaders/database"
export type PriceListFindOptions = CustomFindOptions<
PriceList,
"status" | "type"
>
@EntityRepository(PriceList)
export class PriceListRepository extends Repository<PriceList> {
public async getFreeTextSearchResultsAndCount(
q: string,
options: PriceListFindOptions = { where: {} },
groups: FindOperator<string[]>,
relations: string[] = []
export const PriceListRepository = dataSource.getRepository(PriceList).extend({
async listAndCount(
query: ExtendedFindConfig<PriceList>,
q?: string
): Promise<[PriceList[], number]> {
options.where = options.where ?? {}
const query_ = { ...query }
const qb = this.createQueryBuilder("price_list")
.leftJoinAndSelect("price_list.customer_groups", "customer_group")
.select(["price_list.id"])
.where(options.where)
.andWhere(
new Brackets((qb) => {
qb.where(`price_list.description ILIKE :q`, { q: `%${q}%` })
.orWhere(`price_list.name ILIKE :q`, { q: `%${q}%` })
.orWhere(`customer_group.name ILIKE :q`, { q: `%${q}%` })
})
)
.skip(options.skip)
.take(options.take)
if (q) {
query_.where = query_.where as FindOptionsWhere<PriceList>
delete query_.where.description
delete query_.where.name
if (groups) {
qb.andWhere("group.id IN (:...ids)", { ids: groups.value })
}
const [results, count] = await qb.getManyAndCount()
const price_lists = await this.findWithRelations(
relations,
results.map((r) => r.id)
)
return [price_lists, count]
}
public async findWithRelations(
relations: string[] = [],
idsOrOptionsWithoutRelations:
| Omit<FindManyOptions<PriceList>, "relations">
| string[] = {}
): Promise<PriceList[]> {
let entities
if (Array.isArray(idsOrOptionsWithoutRelations)) {
entities = await this.findByIds(idsOrOptionsWithoutRelations)
} else {
entities = await this.find(idsOrOptionsWithoutRelations)
}
const groupedRelations: Record<string, string[]> = {}
for (const relation of relations) {
const [topLevel] = relation.split(".")
if (groupedRelations[topLevel]) {
groupedRelations[topLevel].push(relation)
} else {
groupedRelations[topLevel] = [relation]
query_.where.description = ILike(`%${q}%`)
query_.where.name = ILike(`%${q}%`)
query_.where.customer_groups = {
name: ILike(`%${q}%`),
}
}
const entitiesIds = entities.map(({ id }) => id)
const entitiesIdsWithRelations = await Promise.all(
Object.values(groupedRelations).map(async (relations: string[]) => {
return this.findByIds(entitiesIds, {
select: ["id"],
relations: relations as string[],
})
})
).then((entitiesIdsWithRelations) => entitiesIdsWithRelations.flat())
const entitiesAndRelations = entitiesIdsWithRelations.concat(entities)
const entitiesAndRelationsById = groupBy(entitiesAndRelations, "id")
return map(entitiesAndRelationsById, (entityAndRelations) =>
this.merge(this.create(), ...entityAndRelations)
)
}
return await this.findAndCount({
...query_,
relations: {
customer_groups: true,
},
relationLoadStrategy: "query",
})
},
})
public async findOneWithRelations(
relations: (keyof PriceList)[] = [],
options: Omit<FindManyOptions<PriceList>, "relations"> = {}
): Promise<PriceList | undefined> {
options.take = 1
return (await this.findWithRelations(relations, options))?.pop()
}
async listAndCount(
query: ExtendedFindConfig<FilterablePriceListProps>,
groups: FindOperator<string[]>
): Promise<[PriceList[], number]> {
const qb = this.createQueryBuilder("price_list")
.where(query.where)
.skip(query.skip)
.take(query.take)
if (groups) {
qb.leftJoinAndSelect("price_list.customer_groups", "group").andWhere(
"group.id IN (:...ids)",
{ ids: groups.value }
)
}
if (query.relations?.length) {
query.relations.forEach((rel) => {
qb.leftJoinAndSelect(`price_list.${rel}`, rel)
})
}
return await qb.getManyAndCount()
}
}
export default PriceListRepository
@@ -1,120 +1,127 @@
import {
EntityRepository,
TreeRepository,
Brackets,
FindOptionsWhere,
ILike,
getConnection,
DeleteResult,
In,
} from "typeorm"
import { ProductCategory } from "../models/product-category"
import { ExtendedFindConfig, Selector, QuerySelector } from "../types/common"
import { ExtendedFindConfig, QuerySelector } from "../types/common"
import { dataSource } from "../loaders/database"
import { buildLegacyFieldsListFrom } from "../utils"
@EntityRepository(ProductCategory)
export class ProductCategoryRepository extends TreeRepository<ProductCategory> {
public async getFreeTextSearchResultsAndCount(
options: ExtendedFindConfig<ProductCategory, Selector<ProductCategory>> = {
where: {},
},
q: string | undefined,
treeScope: QuerySelector<ProductCategory> = {}
): Promise<[ProductCategory[], number]> {
const entityName = "product_category"
const options_ = { ...options }
const relations = options_.relations || []
export const ProductCategoryRepository = dataSource
.getTreeRepository(ProductCategory)
.extend({
async getFreeTextSearchResultsAndCount(
options: ExtendedFindConfig<ProductCategory> = {
where: {},
},
q: string | undefined,
treeScope: QuerySelector<ProductCategory> = {}
): Promise<[ProductCategory[], number]> {
const entityName = "product_category"
const options_ = { ...options }
options_.where = options_.where as FindOptionsWhere<ProductCategory>
const selectStatements = (relationName: string): string[] => {
const modelColumns = this.manager.connection
.getMetadata(ProductCategory)
.ownColumns.map((column) => column.propertyName)
const legacySelect = buildLegacyFieldsListFrom(options_.select)
const legacyRelations = buildLegacyFieldsListFrom(options_.relations)
return (options_.select || modelColumns).map((column) => {
return `${relationName}.${column}`
})
}
const queryBuilder = this.createQueryBuilder(entityName)
.select(selectStatements(entityName))
.skip(options_.skip)
.take(options_.take)
if (q) {
delete options_.where?.name
delete options_.where?.handle
queryBuilder.where(
new Brackets((bracket) => {
bracket
.where({ name: ILike(`%${q}%`) })
.orWhere({ handle: ILike(`%${q}%`) })
})
)
}
queryBuilder.andWhere(options_.where)
const includedTreeRelations: string[] = relations.filter((rel) =>
ProductCategory.treeRelations.includes(rel)
)
includedTreeRelations.forEach((treeRelation) => {
const treeWhere = Object.entries(treeScope)
.map((entry) => `${treeRelation}.${entry[0]} = :${entry[0]}`)
.join(" AND ")
queryBuilder
.leftJoin(
`${entityName}.${treeRelation}`,
treeRelation,
treeWhere,
treeScope
const selectStatements = (relationName: string): string[] => {
const modelColumns = this.metadata.ownColumns.map(
(column) => column.propertyName
)
.addSelect(selectStatements(treeRelation))
})
const selectColumns = legacySelect.length ? legacySelect : modelColumns
const nonTreeRelations: string[] = relations.filter(
(rel) => !ProductCategory.treeRelations.includes(rel)
)
return selectColumns.map((column) => {
return `${relationName}.${column}`
})
}
nonTreeRelations.forEach((relation) => {
queryBuilder.leftJoinAndSelect(`${entityName}.${relation}`, relation)
})
const queryBuilder = this.createQueryBuilder(entityName)
.select(selectStatements(entityName))
.skip(options_.skip)
.take(options_.take)
if (options_.withDeleted) {
queryBuilder.withDeleted()
}
if (q) {
delete options_.where?.name
delete options_.where?.handle
return await queryBuilder.getManyAndCount()
}
queryBuilder.where(
new Brackets((bracket) => {
bracket
.where({ name: ILike(`%${q}%`) })
.orWhere({ handle: ILike(`%${q}%`) })
})
)
}
async addProducts(
productCategoryId: string,
productIds: string[]
): Promise<void> {
await this.createQueryBuilder()
.insert()
.into(ProductCategory.productCategoryProductJoinTable)
.values(
productIds.map((id) => ({
product_category_id: productCategoryId,
product_id: id,
}))
queryBuilder.andWhere(options_.where)
const includedTreeRelations: string[] = legacyRelations.filter((rel) =>
ProductCategory.treeRelations.includes(rel)
)
.orIgnore()
.execute()
}
async removeProducts(
productCategoryId: string,
productIds: string[]
): Promise<DeleteResult> {
return await this.createQueryBuilder()
.delete()
.from(ProductCategory.productCategoryProductJoinTable)
.where({
product_category_id: productCategoryId,
product_id: In(productIds),
includedTreeRelations.forEach((treeRelation) => {
const treeWhere = Object.entries(treeScope)
.map((entry) => `${treeRelation}.${entry[0]} = :${entry[0]}`)
.join(" AND ")
queryBuilder
.leftJoin(
`${entityName}.${treeRelation}`,
treeRelation,
treeWhere,
treeScope
)
.addSelect(selectStatements(treeRelation))
})
.execute()
}
}
const nonTreeRelations: string[] = legacyRelations.filter(
(rel) => !ProductCategory.treeRelations.includes(rel)
)
nonTreeRelations.forEach((relation) => {
queryBuilder.leftJoinAndSelect(`${entityName}.${relation}`, relation)
})
if (options_.withDeleted) {
queryBuilder.withDeleted()
}
return await queryBuilder.getManyAndCount()
},
async addProducts(
productCategoryId: string,
productIds: string[]
): Promise<void> {
await this.createQueryBuilder()
.insert()
.into(ProductCategory.productCategoryProductJoinTable)
.values(
productIds.map((id) => ({
product_category_id: productCategoryId,
product_id: id,
}))
)
.orIgnore()
.execute()
},
async removeProducts(
productCategoryId: string,
productIds: string[]
): Promise<DeleteResult> {
return await this.createQueryBuilder()
.delete()
.from(ProductCategory.productCategoryProductJoinTable)
.where({
product_category_id: productCategoryId,
product_id: In(productIds),
})
.execute()
},
})
export default ProductCategoryRepository
@@ -1,36 +1,24 @@
import { EntityRepository, Repository } from "typeorm"
import { ProductCollection } from "../models"
import { ExtendedFindConfig, Selector } from "../types/common"
import { dataSource } from "../loaders/database"
import { ExtendedFindConfig } from "../types/common"
@EntityRepository(ProductCollection)
// eslint-disable-next-line max-len
export class ProductCollectionRepository extends Repository<ProductCollection> {
async findAndCountByDiscountConditionId(
conditionId: string,
query: ExtendedFindConfig<ProductCollection, Selector<ProductCollection>>
): Promise<[ProductCollection[], number]> {
const qb = this.createQueryBuilder("pc")
if (query?.select) {
qb.select(query.select.map((select) => `pc.${select}`))
}
if (query.skip) {
qb.skip(query.skip)
}
if (query.take) {
qb.take(query.take)
}
return await qb
.where(query.where)
.innerJoin(
"discount_condition_product_collection",
"dc_pc",
`dc_pc.product_collection_id = pc.id AND dc_pc.condition_id = :dcId`,
{ dcId: conditionId }
)
.getManyAndCount()
}
}
export const ProductCollectionRepository = dataSource
.getRepository(ProductCollection)
.extend({
async findAndCountByDiscountConditionId(
conditionId: string,
query: ExtendedFindConfig<ProductCollection>
): Promise<[ProductCollection[], number]> {
return await this.createQueryBuilder("pc")
.setFindOptions(query)
.innerJoin(
"discount_condition_product_collection",
"dc_pc",
`dc_pc.product_collection_id = pc.id AND dc_pc.condition_id = :dcId`,
{ dcId: conditionId }
)
.getManyAndCount()
},
})
export default ProductCollectionRepository
@@ -1,6 +1,6 @@
import { EntityRepository, Repository } from "typeorm"
import { ProductOptionValue } from "../models/product-option-value"
import { ProductOptionValue } from "../models"
import { dataSource } from "../loaders/database"
@EntityRepository(ProductOptionValue)
// eslint-disable-next-line max-len
export class ProductOptionValueRepository extends Repository<ProductOptionValue> {}
export const ProductOptionValueRepository =
dataSource.getRepository(ProductOptionValue)
export default ProductOptionValueRepository
@@ -1,5 +1,5 @@
import { EntityRepository, Repository } from "typeorm"
import { ProductOption } from "../models/product-option"
import { ProductOption } from "../models"
import { dataSource } from "../loaders/database"
@EntityRepository(ProductOption)
export class ProductOptionRepository extends Repository<ProductOption> {}
export const ProductOptionRepository = dataSource.getRepository(ProductOption)
export default ProductOptionRepository
+58 -67
View File
@@ -1,6 +1,7 @@
import { EntityRepository, In, Repository } from "typeorm"
import { In } from "typeorm"
import { ProductTag } from "../models/product-tag"
import { ExtendedFindConfig, Selector } from "../types/common"
import { ExtendedFindConfig } from "../types/common"
import { dataSource } from "../loaders/database"
type UpsertTagsInput = (Partial<ProductTag> & {
value: string
@@ -12,7 +13,7 @@ type ProductTagSelector = Partial<ProductTag> & {
}
export type DefaultWithoutRelations = Omit<
ExtendedFindConfig<ProductTag, ProductTagSelector>,
ExtendedFindConfig<ProductTag>,
"relations"
>
@@ -22,11 +23,12 @@ export type FindWithoutRelationsOptions = DefaultWithoutRelations & {
}
}
@EntityRepository(ProductTag)
export class ProductTagRepository extends Repository<ProductTag> {
public async listTagsByUsage(count = 10): Promise<ProductTag[]> {
return await this.query(
`
export const ProductTagRepository = dataSource
.getRepository(ProductTag)
.extend({
async listTagsByUsage(count = 10): Promise<ProductTag[]> {
return await this.query(
`
SELECT id, COUNT(pts.product_tag_id) as usage_count, pt.value
FROM product_tag pt
LEFT JOIN product_tags pts ON pt.id = pts.product_tag_id
@@ -34,63 +36,52 @@ export class ProductTagRepository extends Repository<ProductTag> {
ORDER BY usage_count DESC
LIMIT $1
`,
[count]
)
}
public async upsertTags(tags: UpsertTagsInput): Promise<ProductTag[]> {
const tagsValues = tags.map((tag) => tag.value)
const existingTags = await this.find({
where: {
value: In(tagsValues),
},
})
const existingTagsMap = new Map(
existingTags.map<[string, ProductTag]>((tag) => [tag.value, tag])
)
const upsertedTags: ProductTag[] = []
for (const tag of tags) {
const aTag = existingTagsMap.get(tag.value)
if (aTag) {
upsertedTags.push(aTag)
} else {
const newTag = this.create(tag)
const savedTag = await this.save(newTag)
upsertedTags.push(savedTag)
}
}
return upsertedTags
}
async findAndCountByDiscountConditionId(
conditionId: string,
query: ExtendedFindConfig<ProductTag, Selector<ProductTag>>
) {
const qb = this.createQueryBuilder("pt")
if (query?.select) {
qb.select(query.select.map((select) => `pt.${select}`))
}
if (query.skip) {
qb.skip(query.skip)
}
if (query.take) {
qb.take(query.take)
}
return await qb
.where(query.where)
.innerJoin(
"discount_condition_product_tag",
"dc_pt",
`dc_pt.product_tag_id = pt.id AND dc_pt.condition_id = :dcId`,
{ dcId: conditionId }
[count]
)
.getManyAndCount()
}
}
},
async upsertTags(tags: UpsertTagsInput): Promise<ProductTag[]> {
const tagsValues = tags.map((tag) => tag.value)
const existingTags = await this.find({
where: {
value: In(tagsValues),
},
})
const existingTagsMap = new Map(
existingTags.map<[string, ProductTag]>((tag) => [tag.value, tag])
)
const upsertedTags: ProductTag[] = []
for (const tag of tags) {
const aTag = existingTagsMap.get(tag.value)
if (aTag) {
upsertedTags.push(aTag)
} else {
const newTag = this.create(tag)
const savedTag = await this.save(newTag)
upsertedTags.push(savedTag)
}
}
return upsertedTags
},
async findAndCountByDiscountConditionId(
conditionId: string,
query: ExtendedFindConfig<ProductTag>
) {
return await this.createQueryBuilder("pt")
.where(query.where)
.setFindOptions(query)
.innerJoin(
"discount_condition_product_tag",
"dc_pt",
`dc_pt.product_tag_id = pt.id AND dc_pt.condition_id = :dcId`,
{ dcId: conditionId }
)
.getManyAndCount()
},
})
export default ProductTagRepository
@@ -1,5 +1,5 @@
import { EntityRepository, Repository } from "typeorm"
import { ProductTaxRate } from "../models/product-tax-rate"
import { ProductTaxRate } from "../models"
import { dataSource } from "../loaders/database"
@EntityRepository(ProductTaxRate)
export class ProductTaxRateRepository extends Repository<ProductTaxRate> {}
export const ProductTaxRateRepository = dataSource.getRepository(ProductTaxRate)
export default ProductTaxRateRepository
@@ -1,58 +1,47 @@
import { EntityRepository, Repository } from "typeorm"
import { ProductType } from "../models/product-type"
import { ExtendedFindConfig, Selector } from "../types/common"
import { ExtendedFindConfig } from "../types/common"
import { dataSource } from "../loaders/database"
type UpsertTypeInput = Partial<ProductType> & {
value: string
}
@EntityRepository(ProductType)
export class ProductTypeRepository extends Repository<ProductType> {
async upsertType(type?: UpsertTypeInput): Promise<ProductType | null> {
if (!type) {
return null
}
export const ProductTypeRepository = dataSource
.getRepository(ProductType)
.extend({
async upsertType(type?: UpsertTypeInput): Promise<ProductType | null> {
if (!type) {
return null
}
const existing = await this.findOne({
where: { value: type.value },
})
const existing = await this.findOne({
where: { value: type.value },
})
if (existing) {
return existing
}
if (existing) {
return existing
}
const created = this.create({
value: type.value,
})
return await this.save(created)
}
const created = this.create({
value: type.value,
})
return await this.save(created)
},
async findAndCountByDiscountConditionId(
conditionId: string,
query: ExtendedFindConfig<ProductType, Selector<ProductType>>
): Promise<[ProductType[], number]> {
const qb = this.createQueryBuilder("pt")
if (query?.select) {
qb.select(query.select.map((select) => `pt.${select}`))
}
if (query.skip) {
qb.skip(query.skip)
}
if (query.take) {
qb.take(query.take)
}
return await qb
.where(query.where)
.innerJoin(
"discount_condition_product_type",
"dc_pt",
`dc_pt.product_type_id = pt.id AND dc_pt.condition_id = :dcId`,
{ dcId: conditionId }
)
.getManyAndCount()
}
}
async findAndCountByDiscountConditionId(
conditionId: string,
query: ExtendedFindConfig<ProductType>
): Promise<[ProductType[], number]> {
return await this.createQueryBuilder("pt")
.where(query.where)
.setFindOptions(query)
.innerJoin(
"discount_condition_product_type",
"dc_pt",
`dc_pt.product_type_id = pt.id AND dc_pt.condition_id = :dcId`,
{ dcId: conditionId }
)
.getManyAndCount()
},
})
export default ProductTypeRepository
@@ -1,254 +1,12 @@
import {
EntityRepository,
FindConditions,
FindManyOptions,
OrderByCondition,
Repository,
} from "typeorm"
import { flatten, groupBy, map, merge } from "lodash"
import { FindManyOptions } from "typeorm"
import { ProductVariant } from "../models/product-variant"
import { FindOptionsOrder } from "typeorm/find-options/FindOptionsOrder"
import { dataSource } from "../loaders/database"
export type FindWithRelationsOptions = FindManyOptions<ProductVariant> & {
order?: OrderByCondition
order?: FindOptionsOrder<ProductVariant>
withDeleted?: boolean
}
@EntityRepository(ProductVariant)
export class ProductVariantRepository extends Repository<ProductVariant> {
private mergeEntitiesWithRelations(
entitiesAndRelations: Array<Partial<ProductVariant>>
): ProductVariant[] {
const entitiesAndRelationsById = groupBy(entitiesAndRelations, "id")
return map(entitiesAndRelationsById, (entityAndRelations) =>
merge({}, ...entityAndRelations)
)
}
private async queryProductsVariants(
optionsWithoutRelations: FindWithRelationsOptions,
shouldCount = false
): Promise<[ProductVariant[], number]> {
let qb = this.createQueryBuilder("pv")
.select(["pv.id"])
.skip(optionsWithoutRelations.skip)
.take(optionsWithoutRelations.take)
qb = optionsWithoutRelations.where
? qb.where(optionsWithoutRelations.where)
: qb
qb = optionsWithoutRelations.order
? qb.orderBy(optionsWithoutRelations.order)
: qb
if (optionsWithoutRelations.withDeleted) {
qb = qb.withDeleted()
}
let entities: ProductVariant[]
let count = 0
if (shouldCount) {
const result = await qb.getManyAndCount()
entities = result[0]
count = result[1]
} else {
entities = await qb.getMany()
}
return [entities, count]
}
private getGroupedRelations(relations: Array<keyof ProductVariant>): {
[toplevel: string]: string[]
} {
const groupedRelations: { [toplevel: string]: string[] } = {}
for (const rel of relations) {
const [topLevel] = rel.split(".")
if (groupedRelations[topLevel]) {
groupedRelations[topLevel].push(rel)
} else {
groupedRelations[topLevel] = [rel]
}
}
return groupedRelations
}
private async queryProductVariantsWithIds(
entityIds: string[],
groupedRelations: { [toplevel: string]: string[] },
withDeleted = false
): Promise<ProductVariant[]> {
const entitiesIdsWithRelations = await Promise.all(
Object.entries(groupedRelations).map(async ([toplevel, rels]) => {
const querybuilder = this.createQueryBuilder("pv").leftJoinAndSelect(
`pv.${toplevel}`,
toplevel
)
for (const rel of rels) {
const [_, rest] = rel.split(".")
if (!rest) {
continue
}
// Regex matches all '.' except the rightmost
querybuilder.leftJoinAndSelect(
rel.replace(/\.(?=[^.]*\.)/g, "__"),
rel.replace(".", "__")
)
}
if (withDeleted) {
querybuilder
.where("pv.id IN (:...entitiesIds)", {
entitiesIds: entityIds,
})
.withDeleted()
} else {
querybuilder.where(
"pv.deleted_at IS NULL AND pv.id IN (:...entitiesIds)",
{
entitiesIds: entityIds,
}
)
}
return querybuilder.getMany()
})
).then(flatten)
return entitiesIdsWithRelations
}
public async findWithRelationsAndCount(
relations: string[] = [],
idsOrOptionsWithoutRelations: FindWithRelationsOptions | string[] = {
where: {},
},
withDeleted?: boolean
): Promise<[ProductVariant[], number]> {
let count: number
let entities: ProductVariant[]
if (Array.isArray(idsOrOptionsWithoutRelations)) {
entities = await this.findByIds(idsOrOptionsWithoutRelations, {
withDeleted: withDeleted ?? false,
})
count = entities.length
} else {
const result = await this.queryProductsVariants(
idsOrOptionsWithoutRelations,
true
)
entities = result[0]
count = result[1]
}
const entitiesIds = entities.map(({ id }) => id)
if (entitiesIds.length === 0) {
// no need to continue
return [[], count]
}
if (relations.length === 0) {
const options = { ...idsOrOptionsWithoutRelations }
// Since we are finding by the ids that have been retrieved above and those ids are already
// applying skip/take. Remove those options to avoid getting no results
if (typeof options === "object") {
delete (options as FindWithRelationsOptions).skip
delete (options as FindWithRelationsOptions).take
}
const toReturn = await this.findByIds(
entitiesIds,
options as FindConditions<ProductVariant>
)
return [toReturn, count]
}
const groupedRelations = this.getGroupedRelations(
relations as (keyof ProductVariant)[]
)
const entitiesIdsWithRelations = await this.queryProductVariantsWithIds(
entitiesIds,
groupedRelations,
withDeleted
)
const entitiesAndRelations = entitiesIdsWithRelations.concat(entities)
const entitiesToReturn =
this.mergeEntitiesWithRelations(entitiesAndRelations)
return [entitiesToReturn, count]
}
public async findWithRelations(
relations: string[] = [],
idsOrOptionsWithoutRelations: FindWithRelationsOptions | string[] = {},
withDeleted = false
): Promise<ProductVariant[]> {
let entities: ProductVariant[]
if (Array.isArray(idsOrOptionsWithoutRelations)) {
entities = await this.findByIds(idsOrOptionsWithoutRelations, {
withDeleted,
})
} else {
const result = await this.queryProductsVariants(
idsOrOptionsWithoutRelations,
false
)
entities = result[0]
}
const entitiesIds = entities.map(({ id }) => id)
if (entitiesIds.length === 0) {
// no need to continue
return []
}
if (relations.length === 0) {
const options = { ...idsOrOptionsWithoutRelations }
// Since we are finding by the ids that have been retrieved above and those ids are already
// applying skip/take. Remove those options to avoid getting no results
if (typeof options === "object") {
delete (options as FindWithRelationsOptions).skip
delete (options as FindWithRelationsOptions).take
}
return await this.findByIds(
entitiesIds,
options as FindConditions<ProductVariant>
)
}
const groupedRelations = this.getGroupedRelations(
relations as (keyof ProductVariant)[]
)
const entitiesIdsWithRelations = await this.queryProductVariantsWithIds(
entitiesIds,
groupedRelations,
withDeleted
)
const entitiesAndRelations = entitiesIdsWithRelations.concat(entities)
const entitiesToReturn =
this.mergeEntitiesWithRelations(entitiesAndRelations)
return entitiesToReturn
}
public async findOneWithRelations(
relations: Array<keyof ProductVariant> = [],
optionsWithoutRelations: FindWithRelationsOptions = { where: {} }
): Promise<ProductVariant> {
// Limit 1
optionsWithoutRelations.take = 1
const result = await this.findWithRelations(
relations,
optionsWithoutRelations
)
return result[0]
}
}
export const ProductVariantRepository = dataSource.getRepository(ProductVariant)
export default ProductVariantRepository
+238 -499
View File
@@ -1,116 +1,209 @@
import { flatten, groupBy, map, merge } from "lodash"
import {
Brackets,
EntityRepository,
FindOperator,
FindOptionsWhere,
ILike,
In,
Repository,
SelectQueryBuilder,
} from "typeorm"
import { PriceList, Product, ProductCategory, SalesChannel } from "../models"
import {
ExtendedFindConfig,
Selector,
WithRequiredProperty,
} from "../types/common"
import { applyOrdering } from "../utils/repository"
import { Product, ProductCategory } from "../models"
import { ExtendedFindConfig } from "../types/common"
import { dataSource } from "../loaders/database"
import { isObject } from "../utils"
import { ProductFilterOptions } from "../types/product"
export type ProductSelector = Omit<Selector<Product>, "tags"> & {
tags: FindOperator<string[]>
}
export const ProductRepository = dataSource.getRepository(Product).extend({
async bulkAddToCollection(
productIds: string[],
collectionId: string
): Promise<Product[]> {
await this.createQueryBuilder()
.update(Product)
.set({ collection_id: collectionId })
.where({ id: In(productIds) })
.execute()
export type DefaultWithoutRelations = Omit<
ExtendedFindConfig<Product, ProductSelector>,
"relations"
>
return this.findByIds(productIds)
},
export type FindWithoutRelationsOptions = DefaultWithoutRelations & {
where: DefaultWithoutRelations["where"] & {
price_list_id?: FindOperator<PriceList>
sales_channel_id?: FindOperator<SalesChannel>
category_id?: {
value: string[]
}
include_category_children?: boolean
discount_condition_id?: string
}
}
async bulkRemoveFromCollection(
productIds: string[],
collectionId: string
): Promise<Product[]> {
await this.createQueryBuilder()
.update(Product)
.set({ collection_id: null })
.where({ id: In(productIds), collection_id: collectionId })
.execute()
@EntityRepository(Product)
export class ProductRepository extends Repository<Product> {
private mergeEntitiesWithRelations(
entitiesAndRelations: Array<Partial<Product>>
): Product[] {
const entitiesAndRelationsById = groupBy(entitiesAndRelations, "id")
return map(entitiesAndRelationsById, (entityAndRelations) =>
merge({}, ...entityAndRelations)
return this.findByIds(productIds)
},
async isProductInSalesChannels(
id: string,
salesChannelIds: string[]
): Promise<boolean> {
return (
(await this.createQueryBuilder("product")
.leftJoin(
"product.sales_channels",
"sales_channels",
"sales_channels.id IN (:...salesChannelIds)",
{ salesChannelIds }
)
.getCount()) > 0
)
}
},
private async queryProducts(
optionsWithoutRelations: FindWithoutRelationsOptions,
shouldCount = false
async findAndCount(
options: ExtendedFindConfig<Product & ProductFilterOptions>,
q?: string
): Promise<[Product[], number]> {
const queryBuilder = await this.prepareQueryBuilder_(options, q)
return await queryBuilder.getManyAndCount()
},
async findOne(
options: ExtendedFindConfig<Product & ProductFilterOptions>
): Promise<Product | null> {
const queryBuilder = await this.prepareQueryBuilder_(options)
return await queryBuilder.getOne()
},
async prepareQueryBuilder_(
options: ExtendedFindConfig<Product & ProductFilterOptions>,
q?: string
): Promise<SelectQueryBuilder<Product>> {
const options_ = { ...options }
const productAlias = "product"
const queryBuilder = this.createQueryBuilder(productAlias)
const tags = optionsWithoutRelations?.where?.tags
delete optionsWithoutRelations?.where?.tags
// TODO: https://github.com/typeorm/typeorm/issues/9719 waiting an answer before being able to set it to `query`
// Therefore use query when there is only an ordering by the product entity otherwise fallback to join.
// In other word, if the order depth is more than 1 then use join otherwise use query
/* const orderFieldsCollectionPointSeparated = buildLegacyFieldsListFrom(
options.order ?? {}
)
const isDepth1 = !orderFieldsCollectionPointSeparated.some(
(field) => field.indexOf(".") !== -1
)
options_.relationLoadStrategy = isDepth1 ? "query" : "join"*/
options_.relationLoadStrategy = "join"
const price_lists = optionsWithoutRelations?.where?.price_list_id
delete optionsWithoutRelations?.where?.price_list_id
options_.relations = options_.relations ?? {}
options_.where = options_.where as FindOptionsWhere<Product>
const sales_channels = optionsWithoutRelations?.where?.sales_channel_id
delete optionsWithoutRelations?.where?.sales_channel_id
// Add explicit ordering for variant ranking on the variants join directly
// The constraint if there is any will be applied by the options_
if (options_.relations.variants && !isObject(options_.order?.variants)) {
options_.order = options_.order ?? {}
options_.order.variants = {
variant_rank: "ASC",
}
/* // The query strategy, as explain at the top of the function, does not select the column from the separated query
// It is not possible to order with that strategy at the moment and, we are waiting for an answer from the typeorm team
options_.relationLoadStrategy = "join"
queryBuilder.leftJoinAndSelect(`${productAlias}.variants`, "variants")
const categories = optionsWithoutRelations?.where?.category_id
delete optionsWithoutRelations?.where?.category_id
options_.order = options_.order ?? {}
const include_category_children =
optionsWithoutRelations?.where?.include_category_children
delete optionsWithoutRelations?.where?.include_category_children
const discount_condition_id =
optionsWithoutRelations?.where?.discount_condition_id
delete optionsWithoutRelations?.where?.discount_condition_id
const qb = this.createQueryBuilder(productAlias)
.select([`${productAlias}.id`])
.skip(optionsWithoutRelations.skip)
.take(optionsWithoutRelations.take)
if (optionsWithoutRelations.where) {
qb.where(optionsWithoutRelations.where)
}
if (tags) {
qb.leftJoin(`${productAlias}.tags`, "tags").andWhere(
`tags.id IN (:...tag_ids)`,
{
tag_ids: tags.value,
if (!isObject(options_.order.variants)) {
options_.order.variants = {
variant_rank: "ASC",
}
)
}*/
}
if (price_lists) {
qb.leftJoin(`${productAlias}.variants`, "variants")
.leftJoin("variants.prices", "prices")
.andWhere("prices.price_list_id IN (:...price_list_ids)", {
price_list_ids: price_lists.value,
if (options_.where.price_list_id) {
/* options_.relations.variants = {
...(isObject(options_.relations.variants)
? options_.relations.variants
: {}),
prices: true,
}
const priceListIds = (
options_.where.price_list_id as FindOperator<string[]>
).value
delete options_.where.price_list_id
options_.where.variants = {
...(isObject(options_.where.variants) ? options_.where.variants : {}),
prices: [
{
price_list_id: In(priceListIds),
},
],
}*/
const priceListIds = (
options_.where.price_list_id as FindOperator<string[]>
).value
delete options_.where.price_list_id
queryBuilder
.leftJoin(`${productAlias}.variants`, "variants")
.leftJoin("variants.prices", "ma")
.andWhere("ma.price_list_id IN (:...price_list_ids)", {
price_list_ids: priceListIds,
})
}
if (sales_channels) {
qb.innerJoin(
if (options_.where.tags) {
const joinMethod = options_.relations.tags
? queryBuilder.leftJoinAndSelect.bind(queryBuilder)
: queryBuilder.leftJoin.bind(queryBuilder)
const tagIds = (options_.where.tags as FindOperator<string[]>).value
// For an unknown reason, the implementation of the SelectQueryBuilder.setFindOptions -> buildWhere
// Only check if it is a find operator MoreThan or LessThan. Otherwise, it has to be a relation of
// isManyToOne or isOneToOne in order to be valid. Otherwise, it throws `This relation isn't supported by given find operator`
// We might need to wait for an update or open a PR around that subject
joinMethod(`${productAlias}.tags`, "tags").andWhere(
`tags.id IN (:...tag_ids)`,
{
tag_ids: tagIds,
}
)
delete options_.where.tags
}
if (options_.where.sales_channel_id) {
const joinMethod = options_.relations.sales_channel_id
? queryBuilder.innerJoinAndSelect.bind(queryBuilder)
: queryBuilder.innerJoin.bind(queryBuilder)
const scIds = (options_.where.sales_channel_id as FindOperator<string[]>)
.value
// Same comment as in the tags if block above + inner join is only doable using the query builder and not the options
joinMethod(
`${productAlias}.sales_channels`,
"sales_channels",
"sales_channels.id IN (:...sales_channels_ids)",
{ sales_channels_ids: sales_channels.value }
{
sales_channels_ids: scIds,
}
)
delete options_.where.sales_channel_id
}
if (categories) {
let categoryIds = categories.value
if (options_.where.category_id) {
const includeCategoryChildren =
options_.where.include_category_children || false
const joinMethod = options_.relations.category_id
? queryBuilder.innerJoinAndSelect.bind(queryBuilder)
: queryBuilder.innerJoin.bind(queryBuilder)
if (include_category_children) {
let categoryIds = (options_.where.category_id as FindOperator<string[]>)
.value
// Same comment as in the tags if block above + inner join is only doable using the query builder and not the options
if (includeCategoryChildren) {
const categoryRepository =
this.manager.getTreeRepository(ProductCategory)
const categories = await categoryRepository.find({
@@ -139,406 +232,75 @@ export class ProductRepository extends Repository<Product> {
}
}
if (categoryIds.length) {
qb.innerJoin(
`${productAlias}.categories`,
"categories",
"categories.id IN (:...categoryIds)",
{ categoryIds }
)
}
}
if (discount_condition_id) {
qb.innerJoin(
"discount_condition_product",
"dc_product",
`dc_product.product_id = ${productAlias}.id AND dc_product.condition_id = :dcId`,
{ dcId: discount_condition_id }
)
}
const joinedWithPriceLists = !!price_lists
applyOrdering({
repository: this,
order: optionsWithoutRelations.order ?? {},
qb,
alias: productAlias,
shouldJoin: (relation) => relation !== "prices" || !joinedWithPriceLists,
})
if (optionsWithoutRelations.withDeleted) {
qb.withDeleted()
}
let entities: Product[]
let count = 0
if (shouldCount) {
const result = await qb.getManyAndCount()
entities = result[0]
count = result[1]
} else {
entities = await qb.getMany()
}
return [entities, count]
}
private getGroupedRelations(relations: string[]): {
[toplevel: string]: string[]
} {
const groupedRelations: { [toplevel: string]: string[] } = {}
for (const rel of relations) {
const [topLevel] = rel.split(".")
if (groupedRelations[topLevel]) {
groupedRelations[topLevel].push(rel)
} else {
groupedRelations[topLevel] = [rel]
}
}
return groupedRelations
}
private async queryProductsWithIds(
entityIds: string[],
groupedRelations: { [toplevel: string]: string[] },
withDeleted = false,
select: (keyof Product)[] = [],
order: { [column: string]: "ASC" | "DESC" } = {}
): Promise<Product[]> {
const entitiesIdsWithRelations = await Promise.all(
Object.entries(groupedRelations).map(async ([toplevel, rels]) => {
let querybuilder = this.createQueryBuilder("products")
if (select && select.length) {
querybuilder.select(select.map((f) => `products.${f}`))
}
if (toplevel === "variants") {
querybuilder = querybuilder.leftJoinAndSelect(
`products.${toplevel}`,
toplevel,
"variants.deleted_at IS NULL"
)
if (!Object.keys(order).some((key) => key.startsWith("variants"))) {
// variant_rank being select false, apply the filter here directly
querybuilder.addOrderBy(`${toplevel}.variant_rank`, "ASC")
}
} else {
querybuilder = querybuilder.leftJoinAndSelect(
`products.${toplevel}`,
toplevel
)
}
for (const rel of rels) {
const [_, rest] = rel.split(".")
if (!rest) {
continue
}
// Regex matches all '.' except the rightmost
querybuilder = querybuilder.leftJoinAndSelect(
rel.replace(/\.(?=[^.]*\.)/g, "__"),
rel.replace(".", "__")
)
}
if (withDeleted) {
querybuilder = querybuilder
.where("products.id IN (:...entitiesIds)", {
entitiesIds: entityIds,
})
.withDeleted()
} else {
querybuilder = querybuilder.where(
"products.deleted_at IS NULL AND products.id IN (:...entitiesIds)",
{
entitiesIds: entityIds,
}
)
}
return querybuilder.getMany()
})
).then(flatten)
return entitiesIdsWithRelations
}
public async findWithRelationsAndCount(
relations: string[] = [],
idsOrOptionsWithoutRelations: FindWithoutRelationsOptions = { where: {} }
): Promise<[Product[], number]> {
let count: number
let entities: Product[]
if (Array.isArray(idsOrOptionsWithoutRelations)) {
entities = await this.findByIds(idsOrOptionsWithoutRelations, {
withDeleted: idsOrOptionsWithoutRelations.withDeleted ?? false,
})
count = entities.length
} else {
const result = await this.queryProducts(
idsOrOptionsWithoutRelations,
true
)
entities = result[0]
count = result[1]
}
const entitiesIds = entities.map(({ id }) => id)
if (entitiesIds.length === 0) {
// no need to continue
return [[], count]
}
if (relations.length === 0) {
const options = { ...idsOrOptionsWithoutRelations }
// Since we are finding by the ids that have been retrieved above and those ids are already
// applying skip/take. Remove those options to avoid getting no results
delete options.skip
delete options.take
const toReturn = await this.findByIds(entitiesIds, options)
return [toReturn, toReturn.length]
}
const groupedRelations = this.getGroupedRelations(relations)
const entitiesIdsWithRelations = await this.queryProductsWithIds(
entitiesIds,
groupedRelations,
idsOrOptionsWithoutRelations.withDeleted,
idsOrOptionsWithoutRelations.select,
idsOrOptionsWithoutRelations.order
)
const entitiesAndRelations = groupBy(entitiesIdsWithRelations, "id")
const entitiesToReturn = map(entitiesIds, (id) =>
merge({}, ...entitiesAndRelations[id])
)
return [entitiesToReturn, count]
}
public async findWithRelations(
relations: string[] = [],
idsOrOptionsWithoutRelations: FindWithoutRelationsOptions | string[] = {
where: {},
},
withDeleted = false
): Promise<Product[]> {
let entities: Product[]
if (Array.isArray(idsOrOptionsWithoutRelations)) {
entities = await this.findByIds(idsOrOptionsWithoutRelations, {
withDeleted,
})
} else {
const result = await this.queryProducts(
idsOrOptionsWithoutRelations,
false
)
entities = result[0]
}
const entitiesIds = entities.map(({ id }) => id)
if (entitiesIds.length === 0) {
// no need to continue
return []
}
if (
relations.length === 0 &&
!Array.isArray(idsOrOptionsWithoutRelations)
) {
return await this.findByIds(entitiesIds, idsOrOptionsWithoutRelations)
}
const groupedRelations = this.getGroupedRelations(relations)
const entitiesIdsWithRelations = await this.queryProductsWithIds(
entitiesIds,
groupedRelations,
withDeleted
)
const entitiesAndRelations = entitiesIdsWithRelations.concat(entities)
const entitiesToReturn =
this.mergeEntitiesWithRelations(entitiesAndRelations)
return entitiesToReturn
}
public async findOneWithRelations(
relations: string[] = [],
optionsWithoutRelations: FindWithoutRelationsOptions = { where: {} }
): Promise<Product> {
// Limit 1
optionsWithoutRelations.take = 1
const result = await this.findWithRelations(
relations,
optionsWithoutRelations
)
return result[0]
}
public async bulkAddToCollection(
productIds: string[],
collectionId: string
): Promise<Product[]> {
await this.createQueryBuilder()
.update(Product)
.set({ collection_id: collectionId })
.where({ id: In(productIds) })
.execute()
return this.findByIds(productIds)
}
public async bulkRemoveFromCollection(
productIds: string[],
collectionId: string
): Promise<Product[]> {
await this.createQueryBuilder()
.update(Product)
.set({ collection_id: null })
.where({ id: In(productIds), collection_id: collectionId })
.execute()
return this.findByIds(productIds)
}
public async getFreeTextSearchResultsAndCount(
q: string,
options: FindWithoutRelationsOptions = { where: {} },
relations: string[] = []
): Promise<[Product[], number]> {
const productAlias = "product"
const pricesAlias = "prices"
const variantsAlias = "variants"
const collectionAlias = "collection"
const tagsAlias = "tags"
const tags = options.where.tags
delete options.where.tags
const price_lists = options.where.price_list_id
delete options.where.price_list_id
const sales_channels = options.where.sales_channel_id
delete options.where.sales_channel_id
const discount_condition_id = options.where.discount_condition_id
delete options.where.discount_condition_id
const cleanedOptions = this._cleanOptions(options)
let qb = this.createQueryBuilder(`${productAlias}`)
.leftJoinAndSelect(`${productAlias}.variants`, variantsAlias)
.leftJoinAndSelect(`${productAlias}.collection`, `${collectionAlias}`)
.select([`${productAlias}.id`])
.where(cleanedOptions.where)
.andWhere(
new Brackets((qb) => {
qb.where(`${productAlias}.description ILIKE :q`, { q: `%${q}%` })
.orWhere(`${productAlias}.title ILIKE :q`, { q: `%${q}%` })
.orWhere(`${variantsAlias}.title ILIKE :q`, { q: `%${q}%` })
.orWhere(`${variantsAlias}.sku ILIKE :q`, { q: `%${q}%` })
.orWhere(`${collectionAlias}.title ILIKE :q`, { q: `%${q}%` })
})
)
.skip(cleanedOptions.skip)
.take(cleanedOptions.take)
if (discount_condition_id) {
qb.innerJoin(
"discount_condition_product",
"dc_product",
`dc_product.product_id = ${productAlias}.id AND dc_product.condition_id = :dcId`,
{ dcId: discount_condition_id }
)
}
if (tags) {
qb.leftJoin(`${productAlias}.tags`, tagsAlias).andWhere(
`${tagsAlias}.id IN (:...tag_ids)`,
joinMethod(
`${productAlias}.categories`,
"categories",
"categories.id IN (:...categoryIds)",
{
tag_ids: tags.value,
categoryIds,
}
)
delete options_.where.category_id
}
if (price_lists) {
const variantPricesAlias = `${variantsAlias}_prices`
qb.leftJoin(`${productAlias}.variants`, variantPricesAlias)
.leftJoin(`${variantPricesAlias}.prices`, pricesAlias)
.andWhere(`${pricesAlias}.price_list_id IN (:...price_list_ids)`, {
price_list_ids: price_lists.value,
})
}
delete options_.where.include_category_children
if (sales_channels) {
qb.innerJoin(
`${productAlias}.sales_channels`,
"sales_channels",
"sales_channels.id IN (:...sales_channels_ids)",
{ sales_channels_ids: sales_channels.value }
if (options_.where.discount_condition_id) {
// inner join is only doable using the query builder and not the options
queryBuilder.innerJoin(
"discount_condition_product",
"dc_product",
`dc_product.product_id = product.id AND dc_product.condition_id = :dcId`,
{ dcId: options_.where.discount_condition_id }
)
delete options_.where.discount_condition_id
}
// TODO: move back to the service layer
if (q) {
options_.relations = options_.relations ?? {}
options_.relations.variants = options_.relations.variants ?? true
options_.relations.collection = options_.relations.collection ?? true
options_.where = [
{
...options_.where,
description: ILike(`%${q}%`),
},
{
...options_.where,
title: ILike(`%${q}%`),
},
{
...options_.where,
variants: {
title: ILike(`%${q}%`),
},
},
{
...options_.where,
variants: {
sku: ILike(`%${q}%`),
},
},
{
...options_.where,
collection: {
title: ILike(`%${q}%`),
},
},
]
}
const joinedWithTags = !!tags
const joinedWithPriceLists = !!price_lists
applyOrdering({
repository: this,
order: options.order ?? {},
qb,
alias: productAlias,
shouldJoin: (relation) =>
relation !== variantsAlias &&
(relation !== pricesAlias || !joinedWithPriceLists) &&
(relation !== tagsAlias || !joinedWithTags),
})
if (cleanedOptions.withDeleted) {
qb = qb.withDeleted()
if (options_.withDeleted) {
queryBuilder.withDeleted()
}
const [results, count] = await qb.getManyAndCount()
const orderedResultsSet = new Set(results.map((p) => p.id))
const products = await this.findWithRelations(
relations,
[...orderedResultsSet],
cleanedOptions.withDeleted
)
const productsMap = new Map(products.map((p) => [p.id, p]))
// Looping through the orderedResultsSet in order to maintain the original order and assign the data returned by findWithRelations
const orderedProducts: Product[] = []
orderedResultsSet.forEach((id) => {
orderedProducts.push(productsMap.get(id)!)
})
return [orderedProducts, count]
}
public async isProductInSalesChannels(
id: string,
salesChannelIds: string[]
): Promise<boolean> {
return (
(await this.createQueryBuilder("product")
.leftJoin(
"product.sales_channels",
"sales_channels",
"sales_channels.id IN (:...salesChannelIds)",
{ salesChannelIds }
)
.getCount()) > 0
)
}
queryBuilder.setFindOptions(options_)
return queryBuilder
},
/**
* Upserts shipping profile for products
@@ -546,7 +308,7 @@ export class ProductRepository extends Repository<Product> {
* @param shippingProfileId ID of shipping profile to assign to products
* @returns updated products
*/
public async upsertShippingProfile(
async upsertShippingProfile(
productIds: string[],
shippingProfileId: string
): Promise<Product[]> {
@@ -557,30 +319,7 @@ export class ProductRepository extends Repository<Product> {
.execute()
return await this.findByIds(productIds)
}
},
})
private _cleanOptions(
options: FindWithoutRelationsOptions
): WithRequiredProperty<FindWithoutRelationsOptions, "where"> {
const where = options.where ?? {}
if ("description" in where) {
delete where.description
}
if ("title" in where) {
delete where.title
}
if ("price_list_id" in where) {
delete where?.price_list_id
}
if ("discount_condition_id" in where) {
delete where?.discount_condition_id
}
return {
...options,
where,
}
}
}
export default ProductRepository
@@ -1,91 +1,94 @@
import { Brackets, EntityRepository, In, Repository } from "typeorm"
import { Brackets, In } from "typeorm"
import { PublishableApiKeySalesChannel, SalesChannel } from "../models"
import { dataSource } from "../loaders/database"
@EntityRepository(PublishableApiKeySalesChannel)
export class PublishableApiKeySalesChannelRepository extends Repository<PublishableApiKeySalesChannel> {
/**
* Query a list of sales channels that are assigned to the publishable key scope
*
* @param publishableApiKeyId - id of the key to retrieve channels for
* @param config - querying params
*/
public async findSalesChannels(
publishableApiKeyId: string,
config?: { q?: string }
): Promise<SalesChannel[]> {
const query = this.createQueryBuilder("PublishableKeySalesChannel")
.select("PublishableKeySalesChannel.sales_channel_id")
.innerJoinAndMapOne(
"PublishableKeySalesChannel.sales_channel_id",
SalesChannel,
"SalesChannel",
"PublishableKeySalesChannel.sales_channel_id = SalesChannel.id"
)
.where(
"PublishableKeySalesChannel.publishable_key_id = :publishableApiKeyId",
{
publishableApiKeyId,
}
export const PublishableApiKeySalesChannelRepository = dataSource
.getRepository(PublishableApiKeySalesChannel)
.extend({
/**
* Query a list of sales channels that are assigned to the publishable key scope
*
* @param publishableApiKeyId - id of the key to retrieve channels for
* @param config - querying params
*/
async findSalesChannels(
publishableApiKeyId: string,
config?: { q?: string }
): Promise<SalesChannel[]> {
const query = this.createQueryBuilder("PublishableKeySalesChannel")
.select("PublishableKeySalesChannel.sales_channel_id")
.innerJoinAndMapOne(
"PublishableKeySalesChannel.sales_channel_id",
SalesChannel,
"SalesChannel",
"PublishableKeySalesChannel.sales_channel_id = SalesChannel.id"
)
.where(
"PublishableKeySalesChannel.publishable_key_id = :publishableApiKeyId",
{
publishableApiKeyId,
}
)
if (config?.q) {
query.andWhere(
new Brackets((qb) => {
qb.where(`SalesChannel.description ILIKE :q`, {
q: `%${config.q}%`,
}).orWhere(`SalesChannel.name ILIKE :q`, { q: `%${config.q}%` })
})
)
}
const records = await query.getMany()
return records.map(
(record) => record.sales_channel_id as unknown as SalesChannel
)
},
if (config?.q) {
query.andWhere(
new Brackets((qb) => {
qb.where(`SalesChannel.description ILIKE :q`, {
q: `%${config.q}%`,
}).orWhere(`SalesChannel.name ILIKE :q`, { q: `%${config.q}%` })
})
)
}
/**
* Assign (multiple) sales channels to the Publishable Key scope
*
* @param publishableApiKeyId - publishable key id
* @param salesChannelIds - an array of SC ids
*/
async addSalesChannels(
publishableApiKeyId: string,
salesChannelIds: string[]
): Promise<void> {
await this.createQueryBuilder()
.insert()
.into("publishable_api_key_sales_channel")
.values(
salesChannelIds.map((id) => ({
sales_channel_id: id,
publishable_key_id: publishableApiKeyId,
}))
)
.orIgnore()
.execute()
},
const records = await query.getMany()
return records.map(
(record) => record.sales_channel_id as unknown as SalesChannel
)
}
/**
* Assign (multiple) sales channels to the Publishable Key scope
*
* @param publishableApiKeyId - publishable key id
* @param salesChannelIds - an array of SC ids
*/
public async addSalesChannels(
publishableApiKeyId: string,
salesChannelIds: string[]
): Promise<void> {
await this.createQueryBuilder()
.insert()
.into("publishable_api_key_sales_channel")
.values(
salesChannelIds.map((id) => ({
sales_channel_id: id,
/**
* Remove multiple sales channels from the PK scope
*
* @param publishableApiKeyId -publishable key id
* @param salesChannelIds - an array of SC ids
*/
async removeSalesChannels(
publishableApiKeyId: string,
salesChannelIds: string[]
): Promise<void> {
await this.createQueryBuilder()
.delete()
.from("publishable_api_key_sales_channel")
.where({
sales_channel_id: In(salesChannelIds),
publishable_key_id: publishableApiKeyId,
}))
)
.orIgnore()
.execute()
}
/**
* Remove multiple sales channels from the PK scope
*
* @param publishableApiKeyId -publishable key id
* @param salesChannelIds - an array of SC ids
*/
public async removeSalesChannels(
publishableApiKeyId: string,
salesChannelIds: string[]
): Promise<void> {
await this.createQueryBuilder()
.delete()
.from("publishable_api_key_sales_channel")
.where({
sales_channel_id: In(salesChannelIds),
publishable_key_id: publishableApiKeyId,
})
.execute()
}
}
})
.execute()
},
})
export default PublishableApiKeySalesChannelRepository
@@ -1,71 +1,6 @@
import { flatten, groupBy, merge } from "lodash"
import { EntityRepository, FindManyOptions, Repository } from "typeorm"
import { PublishableApiKey } from "../models"
import { dataSource } from "../loaders/database"
@EntityRepository(PublishableApiKey)
export class PublishableApiKeyRepository extends Repository<PublishableApiKey> {
public async findWithRelations(
relations: (keyof PublishableApiKey | string)[] = [],
idsOrOptionsWithoutRelations:
| Omit<FindManyOptions<PublishableApiKey>, "relations">
| string[] = {}
): Promise<[PublishableApiKey[], number]> {
let entities: PublishableApiKey[] = []
let count = 0
if (Array.isArray(idsOrOptionsWithoutRelations)) {
entities = await this.findByIds(idsOrOptionsWithoutRelations)
count = idsOrOptionsWithoutRelations.length
} else {
const [results, resultCount] = await this.findAndCount(
idsOrOptionsWithoutRelations
)
entities = results
count = resultCount
}
const entitiesIds = entities.map(({ id }) => id)
const groupedRelations = {}
for (const rel of relations) {
const [topLevel] = rel.split(".")
if (groupedRelations[topLevel]) {
groupedRelations[topLevel].push(rel)
} else {
groupedRelations[topLevel] = [rel]
}
}
const entitiesIdsWithRelations = await Promise.all(
Object.entries(groupedRelations).map(async ([_, rels]) => {
return this.findByIds(entitiesIds, {
select: ["id"],
relations: rels as string[],
})
})
).then(flatten)
const entitiesAndRelations = entitiesIdsWithRelations.concat(entities)
const entitiesAndRelationsById = groupBy(entitiesAndRelations, "id")
return [
Object.values(entitiesAndRelationsById).map((v) => merge({}, ...v)),
count,
]
}
public async findOneWithRelations(
relations: Array<keyof PublishableApiKey> = [],
optionsWithoutRelations: Omit<
FindManyOptions<PublishableApiKey>,
"relations"
> = {}
): Promise<PublishableApiKey> {
// Limit 1
optionsWithoutRelations.take = 1
const [result] = await this.findWithRelations(
relations,
optionsWithoutRelations
)
return result[0]
}
}
// eslint-disable-next-line max-len
export const PublishableApiKeyRepository = dataSource.getRepository(PublishableApiKey)
export default PublishableApiKeyRepository
+4 -4
View File
@@ -1,5 +1,5 @@
import { EntityRepository, Repository } from "typeorm"
import { Refund } from "../models/refund"
import { Refund } from "../models"
import { dataSource } from "../loaders/database"
@EntityRepository(Refund)
export class RefundRepository extends Repository<Refund> {}
export const RefundRepository = dataSource.getRepository(Refund)
export default RefundRepository
+4 -4
View File
@@ -1,5 +1,5 @@
import { EntityRepository, Repository } from "typeorm"
import { Region } from "../models/region"
import { Region } from "../models"
import { dataSource } from "../loaders/database"
@EntityRepository(Region)
export class RegionRepository extends Repository<Region> {}
export const RegionRepository = dataSource.getRepository(Region)
export default RegionRepository
@@ -1,5 +1,5 @@
import { EntityRepository, Repository } from "typeorm"
import { ReturnItem } from "../models/return-item"
import { ReturnItem } from "../models"
import { dataSource } from "../loaders/database"
@EntityRepository(ReturnItem)
export class ReturnItemRepository extends Repository<ReturnItem> {}
export const ReturnItemRepository = dataSource.getRepository(ReturnItem)
export default ReturnItemRepository
@@ -1,5 +1,5 @@
import { EntityRepository, Repository } from "typeorm"
import { ReturnReason } from "../models/return-reason"
import { ReturnReason } from "../models"
import { dataSource } from "../loaders/database"
@EntityRepository(ReturnReason)
export class ReturnReasonRepository extends Repository<ReturnReason> {}
export const ReturnReasonRepository = dataSource.getRepository(ReturnReason)
export default ReturnReasonRepository
+4 -4
View File
@@ -1,5 +1,5 @@
import { EntityRepository, Repository } from "typeorm"
import { Return } from "../models/return"
import { Return } from "../models"
import { dataSource } from "../loaders/database"
@EntityRepository(Return)
export class ReturnRepository extends Repository<Return> {}
export const ReturnRepository = dataSource.getRepository(Return)
export default ReturnRepository
+62 -129
View File
@@ -1,139 +1,72 @@
import {
Brackets,
DeleteResult,
EntityRepository,
FindManyOptions,
In,
Repository,
} from "typeorm"
import { Brackets, DeleteResult, FindOptionsWhere, In } from "typeorm"
import { SalesChannel } from "../models"
import { ExtendedFindConfig, Selector } from "../types/common"
import { flatten, groupBy, merge } from "lodash"
import { ExtendedFindConfig } from "../types/common"
import { dataSource } from "../loaders/database"
@EntityRepository(SalesChannel)
export class SalesChannelRepository extends Repository<SalesChannel> {
public async findWithRelations(
relations: (keyof SalesChannel | string)[] = [],
idsOrOptionsWithoutRelations:
| Omit<FindManyOptions<SalesChannel>, "relations">
| string[] = {}
): Promise<[SalesChannel[], number]> {
let entities: SalesChannel[] = []
let count = 0
if (Array.isArray(idsOrOptionsWithoutRelations)) {
entities = await this.findByIds(idsOrOptionsWithoutRelations)
count = idsOrOptionsWithoutRelations.length
} else {
const [results, resultCount] = await this.findAndCount(
idsOrOptionsWithoutRelations
)
entities = results
count = resultCount
}
const entitiesIds = entities.map(({ id }) => id)
const groupedRelations = {}
for (const rel of relations) {
const [topLevel] = rel.split(".")
if (groupedRelations[topLevel]) {
groupedRelations[topLevel].push(rel)
} else {
groupedRelations[topLevel] = [rel]
export const SalesChannelRepository = dataSource
.getRepository(SalesChannel)
.extend({
async getFreeTextSearchResultsAndCount(
q: string,
options: ExtendedFindConfig<SalesChannel> = {
where: {},
}
}
): Promise<[SalesChannel[], number]> {
const options_ = { ...options }
options_.where = options_.where as FindOptionsWhere<SalesChannel>
const entitiesIdsWithRelations = await Promise.all(
Object.entries(groupedRelations).map(async ([_, rels]) => {
return this.findByIds(entitiesIds, {
select: ["id"],
relations: rels as string[],
})
})
).then(flatten)
const entitiesAndRelations = entitiesIdsWithRelations.concat(entities)
delete options_?.where?.name
delete options_?.where?.description
const entitiesAndRelationsById = groupBy(entitiesAndRelations, "id")
return [
Object.values(entitiesAndRelationsById).map((v) => merge({}, ...v)),
count,
]
}
let qb = this.createQueryBuilder("sales_channel")
.select()
.where(options_.where)
.andWhere(
new Brackets((qb) => {
qb.where(`sales_channel.description ILIKE :q`, {
q: `%${q}%`,
}).orWhere(`sales_channel.name ILIKE :q`, { q: `%${q}%` })
})
)
.skip(options.skip)
.take(options.take)
public async getFreeTextSearchResultsAndCount(
q: string,
options: ExtendedFindConfig<SalesChannel, Selector<SalesChannel>> = {
where: {},
}
): Promise<[SalesChannel[], number]> {
const options_ = { ...options }
delete options_?.where?.name
delete options_?.where?.description
if (options.withDeleted) {
qb = qb.withDeleted()
}
let qb = this.createQueryBuilder("sales_channel")
.select()
.where(options_.where)
.andWhere(
new Brackets((qb) => {
qb.where(`sales_channel.description ILIKE :q`, {
q: `%${q}%`,
}).orWhere(`sales_channel.name ILIKE :q`, { q: `%${q}%` })
})
)
.skip(options.skip)
.take(options.take)
return await qb.getManyAndCount()
},
if (options.withDeleted) {
qb = qb.withDeleted()
}
return await qb.getManyAndCount()
}
async removeProducts(
salesChannelId: string,
productIds: string[]
): Promise<DeleteResult> {
return await this.createQueryBuilder()
.delete()
.from("product_sales_channel")
.where({
sales_channel_id: salesChannelId,
product_id: In(productIds),
})
.execute()
}
async addProducts(
salesChannelId: string,
productIds: string[]
): Promise<void> {
await this.createQueryBuilder()
.insert()
.into("product_sales_channel")
.values(
productIds.map((id) => ({
async removeProducts(
salesChannelId: string,
productIds: string[]
): Promise<DeleteResult> {
return await this.createQueryBuilder()
.delete()
.from("product_sales_channel")
.where({
sales_channel_id: salesChannelId,
product_id: id,
}))
)
.orIgnore()
.execute()
}
product_id: In(productIds),
})
.execute()
},
public async findOneWithRelations(
relations: Array<keyof SalesChannel> = [],
optionsWithoutRelations: Omit<
FindManyOptions<SalesChannel>,
"relations"
> = {}
): Promise<SalesChannel> {
// Limit 1
optionsWithoutRelations.take = 1
const [result] = await this.findWithRelations(
relations,
optionsWithoutRelations
)
return result[0]
}
}
async addProducts(
salesChannelId: string,
productIds: string[]
): Promise<void> {
await this.createQueryBuilder()
.insert()
.into("product_sales_channel")
.values(
productIds.map((id) => ({
sales_channel_id: salesChannelId,
product_id: id,
}))
)
.orIgnore()
.execute()
},
})
export default SalesChannelRepository
@@ -1,41 +1,43 @@
import { EntityRepository, Repository } from "typeorm"
import { ShippingMethodTaxLine } from "../models/shipping-method-tax-line"
import { ShippingMethodTaxLine } from "../models"
import { QueryDeepPartialEntity } from "typeorm/query-builder/QueryPartialEntity"
import { dataSource } from "../loaders/database"
@EntityRepository(ShippingMethodTaxLine)
// eslint-disable-next-line max-len
export class ShippingMethodTaxLineRepository extends Repository<ShippingMethodTaxLine> {
async upsertLines(
lines: ShippingMethodTaxLine[]
): Promise<ShippingMethodTaxLine[]> {
const insertResult = await this.createQueryBuilder()
.insert()
.values(lines as QueryDeepPartialEntity<ShippingMethodTaxLine>[])
.orUpdate({
conflict_target: ["shipping_method_id", "code"],
overwrite: ["rate", "name", "updated_at"],
})
.execute()
export const ShippingMethodTaxLineRepository = dataSource
.getRepository(ShippingMethodTaxLine)
.extend({
async upsertLines(
lines: ShippingMethodTaxLine[]
): Promise<ShippingMethodTaxLine[]> {
const insertResult = await this.createQueryBuilder()
.insert()
.values(lines as QueryDeepPartialEntity<ShippingMethodTaxLine>[])
.orUpdate({
conflict_target: ["shipping_method_id", "code"],
overwrite: ["rate", "name", "updated_at"],
})
.execute()
return insertResult.identifiers as ShippingMethodTaxLine[]
}
return insertResult.identifiers as ShippingMethodTaxLine[]
},
async deleteForCart(cartId: string): Promise<void> {
const qb = this.createQueryBuilder("line")
.select(["line.id"])
.innerJoin("shipping_method", "sm", "sm.id = line.shipping_method_id")
.innerJoin(
"cart",
"c",
"sm.cart_id = :cartId AND c.completed_at is NULL",
{ cartId }
)
async deleteForCart(cartId: string): Promise<void> {
const qb = this.createQueryBuilder("line")
.select(["line.id"])
.innerJoin("shipping_method", "sm", "sm.id = line.shipping_method_id")
.innerJoin(
"cart",
"c",
"sm.cart_id = :cartId AND c.completed_at is NULL",
{ cartId }
)
const toDelete = await qb.getMany()
const toDelete = await qb.getMany()
await this.createQueryBuilder()
.delete()
.whereInIds(toDelete.map((d) => d.id))
.execute()
}
}
await this.createQueryBuilder()
.delete()
.whereInIds(toDelete.map((d) => d.id))
.execute()
},
})
export default ShippingMethodTaxLineRepository
@@ -1,5 +1,5 @@
import { EntityRepository, Repository } from "typeorm"
import { ShippingMethod } from "../models/shipping-method"
import { ShippingMethod } from "../models"
import { dataSource } from "../loaders/database"
@EntityRepository(ShippingMethod)
export class ShippingMethodRepository extends Repository<ShippingMethod> {}
export const ShippingMethodRepository = dataSource.getRepository(ShippingMethod)
export default ShippingMethodRepository
@@ -1,6 +1,7 @@
import { EntityRepository, Repository } from "typeorm"
import { ShippingOptionRequirement } from "../models/shipping-option-requirement"
import { ShippingOptionRequirement } from "../models"
import { dataSource } from "../loaders/database"
@EntityRepository(ShippingOptionRequirement)
// eslint-disable-next-line max-len
export class ShippingOptionRequirementRepository extends Repository<ShippingOptionRequirement> {}
export const ShippingOptionRequirementRepository = dataSource.getRepository(
ShippingOptionRequirement
)
export default ShippingOptionRequirementRepository
@@ -1,18 +1,22 @@
import { EntityRepository, In, Repository } from "typeorm"
import { ShippingOption } from "../models/shipping-option"
import { ShippingOption } from "../models"
import { dataSource } from "../loaders/database"
import { In } from "typeorm"
@EntityRepository(ShippingOption)
export class ShippingOptionRepository extends Repository<ShippingOption> {
public async upsertShippingProfile(
shippingOptionIds: string[],
shippingProfileId: string
): Promise<ShippingOption[]> {
await this.createQueryBuilder()
.update(ShippingOption)
.set({ profile_id: shippingProfileId })
.where({ id: In(shippingOptionIds) })
.execute()
export const ShippingOptionRepository = dataSource
.getRepository(ShippingOption)
.extend({
async upsertShippingProfile(
shippingOptionIds: string[],
shippingProfileId: string
): Promise<ShippingOption[]> {
await this.createQueryBuilder()
.update(ShippingOption)
.set({ profile_id: shippingProfileId })
.where({ id: In(shippingOptionIds) })
.execute()
return this.findByIds(shippingOptionIds)
}
}
return this.findByIds(shippingOptionIds)
},
})
export default ShippingOptionRepository
Loaded 100 of 275 files, more files were not shown because too many files have changed in this diff. Show more