chore: Abstract module service (#6188)

**What**
- Remove services that do not have any custom business and replace them with a simple interfaces
- Abstract module service provide the following base implementation
  - retrieve
  - list
  - listAndCount
  - delete
  - softDelete
  - restore

The above methods are created for the main model and also for each other models for which a config is provided

all method such as list, listAndCount, delete, softDelete and restore are pluralized with the model it refers to

**Migration**
- [x] product
- [x] pricing
- [x] promotion
- [x] cart
- [x] auth
- [x] customer
- [x] payment
- [x] Sales channel
- [x] Workflow-*


**Usage**

**Module**

The module service can now extend the ` ModulesSdkUtils.abstractModuleServiceFactory` which returns a class with the default implementation for each method and each model following the standard naming convention mentioned above.
This factory have 3 template arguments being the container, the main model DTO and an object representing the other model with a config object that contains at list the DTO and optionally a singular and plural property in case it needs to be set manually. It looks like the following:

```ts
export default class PricingModuleService</* ... */>
  extends ModulesSdkUtils.abstractModuleServiceFactory<
    InjectedDependencies,
    PricingTypes.PriceSetDTO,
    {
      Currency: { dto: PricingTypes.CurrencyDTO }
      MoneyAmount: { dto: PricingTypes.MoneyAmountDTO }
      PriceSetMoneyAmount: { dto: PricingTypes.PriceSetMoneyAmountDTO }
      PriceSetMoneyAmountRules: {
        dto: PricingTypes.PriceSetMoneyAmountRulesDTO
      }
      PriceRule: { dto: PricingTypes.PriceRuleDTO }
      RuleType: { dto: PricingTypes.RuleTypeDTO }
      PriceList: { dto: PricingTypes.PriceListDTO }
      PriceListRule: { dto: PricingTypes.PriceListRuleDTO }
    }
  >(PriceSet, generateMethodForModels, entityNameToLinkableKeysMap)
  implements PricingTypes.IPricingModuleService
{
// ...
}
```

In the above, the singular and plural can be inferred as there is no tricky naming. Also, the default implementation does not remove the fact that you need to provides all the overloads etc in your module service interface. The above will provide a default implementation following the interface `AbstractModuleService` which is also auto generated, hence you will have the following methods available:

**for the main model**
- list
- retrieve
- listAndCount 
- delete
- softDelete
- restore


**for the other models**
- list**MyModels**
- retrieve**MyModel**
- listAndCount**MyModels**
- delete**MyModels**
- softDelete**MyModels**
- restore**MyModels**

**Internal module service**

The internal module service can now extend `ModulesSdkUtils.internalModuleServiceFactory` which takes only one template argument which is the container type. 
All internal services provides a default implementation for all retrieve, list, listAndCount, create, update, delete, softDelete, restore methods which follow the following interface `ModulesSdkTypes.InternalModuleService`:

```ts
export interface InternalModuleService<
  TEntity extends {},
  TContainer extends object = object
> {
  get __container__(): TContainer

  retrieve(
    idOrObject: string,
    config?: FindConfig<any>,
    sharedContext?: Context
  ): Promise<TEntity>
  retrieve(
    idOrObject: object,
    config?: FindConfig<any>,
    sharedContext?: Context
  ): Promise<TEntity>

  list(
    filters?: FilterQuery<any> | BaseFilterable<FilterQuery<any>>,
    config?: FindConfig<any>,
    sharedContext?: Context
  ): Promise<TEntity[]>

  listAndCount(
    filters?: FilterQuery<any> | BaseFilterable<FilterQuery<any>>,
    config?: FindConfig<any>,
    sharedContext?: Context
  ): Promise<[TEntity[], number]>

  create(data: any[], sharedContext?: Context): Promise<TEntity[]>
  create(data: any, sharedContext?: Context): Promise<TEntity>

  update(data: any[], sharedContext?: Context): Promise<TEntity[]>
  update(data: any, sharedContext?: Context): Promise<TEntity>
  update(
    selectorAndData: {
      selector: FilterQuery<any> | BaseFilterable<FilterQuery<any>>
      data: any
    },
    sharedContext?: Context
  ): Promise<TEntity[]>
  update(
    selectorAndData: {
      selector: FilterQuery<any> | BaseFilterable<FilterQuery<any>>
      data: any
    }[],
    sharedContext?: Context
  ): Promise<TEntity[]>

  delete(idOrSelector: string, sharedContext?: Context): Promise<void>
  delete(idOrSelector: string[], sharedContext?: Context): Promise<void>
  delete(idOrSelector: object, sharedContext?: Context): Promise<void>
  delete(idOrSelector: object[], sharedContext?: Context): Promise<void>
  delete(
    idOrSelector: {
      selector: FilterQuery<any> | BaseFilterable<FilterQuery<any>>
    },
    sharedContext?: Context
  ): Promise<void>

  softDelete(
    idsOrFilter: string[] | InternalFilterQuery,
    sharedContext?: Context
  ): Promise<[TEntity[], Record<string, unknown[]>]>

  restore(
    idsOrFilter: string[] | InternalFilterQuery,
    sharedContext?: Context
  ): Promise<[TEntity[], Record<string, unknown[]>]>

  upsert(data: any[], sharedContext?: Context): Promise<TEntity[]>
  upsert(data: any, sharedContext?: Context): Promise<TEntity>
}
```

When a service is auto generated you can use that interface to type your class property representing the expected internal service.

**Repositories**

The repositories can now extend `DALUtils.mikroOrmBaseRepositoryFactory` which takes one template argument being the entity or the template entity and provides all the default implementation. If the repository is auto generated you can type it using the `RepositoryService` interface. Here is the new interface typings.

```ts
export interface RepositoryService<T = any> extends BaseRepositoryService<T> {
  find(options?: FindOptions<T>, context?: Context): Promise<T[]>

  findAndCount(
    options?: FindOptions<T>,
    context?: Context
  ): Promise<[T[], number]>

  create(data: any[], context?: Context): Promise<T[]>

  // Becareful here, if you have a custom internal service, the update data should never be the entity otherwise
 // both entity and update will point to the same ref and create issues with mikro orm
  update(data: { entity; update }[], context?: Context): Promise<T[]>

  delete(
    idsOrPKs: FilterQuery<T> & BaseFilterable<FilterQuery<T>>,
    context?: Context
  ): Promise<void>

  /**
   * Soft delete entities and cascade to related entities if configured.
   *
   * @param idsOrFilter
   * @param context
   *
   * @returns [T[], Record<string, string[]>] the second value being the map of the entity names and ids that were soft deleted
   */
  softDelete(
    idsOrFilter: string[] | InternalFilterQuery,
    context?: Context
  ): Promise<[T[], Record<string, unknown[]>]>

  restore(
    idsOrFilter: string[] | InternalFilterQuery,
    context?: Context
  ): Promise<[T[], Record<string, unknown[]>]>

  upsert(data: any[], context?: Context): Promise<T[]>
}
```
This commit is contained in:
Adrien de Peretti
2024-02-02 15:20:32 +01:00
committed by GitHub
parent abc30517cb
commit a7be5d7b6d
163 changed files with 2867 additions and 5080 deletions

View File

@@ -2,27 +2,32 @@ import { DAL } from "@medusajs/types"
import { generateEntityId } from "@medusajs/utils"
import {
BeforeCreate,
Cascade,
Entity,
Index,
ManyToOne,
OnInit,
OptionalProps,
PrimaryKey,
Property,
ManyToOne,
Cascade,
Index,
} from "@mikro-orm/core"
import Customer from "./customer"
type OptionalAddressProps = DAL.EntityDateColumns // TODO: To be revisited when more clear
export const UNIQUE_CUSTOMER_SHIPPING_ADDRESS =
"IDX_customer_address_unique_customer_shipping"
export const UNIQUE_CUSTOMER_BILLING_ADDRESS =
"IDX_customer_address_unique_customer_billing"
@Entity({ tableName: "customer_address" })
@Index({
name: "IDX_customer_address_unique_customer_shipping",
name: UNIQUE_CUSTOMER_SHIPPING_ADDRESS,
expression:
'create unique index "IDX_customer_address_unique_customer_shipping" on "customer_address" ("customer_id") where "is_default_shipping" = true',
})
@Index({
name: "IDX_customer_address_unique_customer_billing",
name: UNIQUE_CUSTOMER_BILLING_ADDRESS,
expression:
'create unique index "IDX_customer_address_unique_customer_billing" on "customer_address" ("customer_id") where "is_default_billing" = true',
})

View File

@@ -1,23 +0,0 @@
import { DAL } from "@medusajs/types"
import { ModulesSdkUtils } from "@medusajs/utils"
import { Address } from "@models"
import { CreateAddressDTO, UpdateAddressDTO } from "@types"
type InjectedDependencies = {
addressRepository: DAL.RepositoryService
}
export default class AddressService<
TEntity extends Address = Address
> extends ModulesSdkUtils.abstractServiceFactory<
InjectedDependencies,
{
create: CreateAddressDTO
update: UpdateAddressDTO
}
>(Address)<TEntity> {
constructor(container: InjectedDependencies) {
// @ts-ignore
super(...arguments)
}
}

View File

@@ -1,25 +0,0 @@
import { DAL } from "@medusajs/types"
import { ModulesSdkUtils } from "@medusajs/utils"
import { CustomerGroupCustomer } from "@models"
type CreateCustomerGroupCustomerDTO = {
customer_id: string
customer_group_id: string
created_by?: string
}
type InjectedDependencies = {
customerGroupRepository: DAL.RepositoryService
}
export default class CustomerGroupCustomerService<
TEntity extends CustomerGroupCustomer = CustomerGroupCustomer
> extends ModulesSdkUtils.abstractServiceFactory<
InjectedDependencies,
{ create: CreateCustomerGroupCustomerDTO }
>(CustomerGroupCustomer)<TEntity> {
constructor(container: InjectedDependencies) {
// @ts-ignore
super(...arguments)
}
}

View File

@@ -1,23 +0,0 @@
import { DAL } from "@medusajs/types"
import { ModulesSdkUtils } from "@medusajs/utils"
import { CustomerGroup } from "@models"
import { CreateCustomerGroupDTO, UpdateCustomerGroupDTO } from "@medusajs/types"
type InjectedDependencies = {
customerGroupRepository: DAL.RepositoryService
}
export default class CustomerGroupService<
TEntity extends CustomerGroup = CustomerGroup
> extends ModulesSdkUtils.abstractServiceFactory<
InjectedDependencies,
{
create: CreateCustomerGroupDTO
update: UpdateCustomerGroupDTO
}
>(CustomerGroup)<TEntity> {
constructor(container: InjectedDependencies) {
// @ts-ignore
super(...arguments)
}
}

View File

@@ -1,48 +1,69 @@
import {
Context,
CustomerDTO,
CustomerTypes,
DAL,
FindConfig,
ICustomerModuleService,
InternalModuleDeclaration,
ModuleJoinerConfig,
CustomerTypes,
SoftDeleteReturn,
RestoreReturn,
ModulesSdkTypes,
} from "@medusajs/types"
import {
InjectManager,
InjectTransactionManager,
MedusaContext,
mapObjectTo,
isString,
isObject,
isDuplicateError,
isString,
MedusaContext,
MedusaError,
ModulesSdkUtils,
} from "@medusajs/utils"
import { entityNameToLinkableKeysMap, joinerConfig } from "../joiner-config"
import * as services from "../services"
import { MedusaError } from "@medusajs/utils"
import {
Address,
Customer,
CustomerGroup,
CustomerGroupCustomer,
} from "@models"
import { EntityManager } from "@mikro-orm/core"
const UNIQUE_CUSTOMER_SHIPPING_ADDRESS =
"IDX_customer_address_unique_customer_shipping"
const UNIQUE_CUSTOMER_BILLING_ADDRESS =
"IDX_customer_address_unique_customer_billing"
import {
UNIQUE_CUSTOMER_BILLING_ADDRESS,
UNIQUE_CUSTOMER_SHIPPING_ADDRESS,
} from "../models/address"
type InjectedDependencies = {
baseRepository: DAL.RepositoryService
customerService: services.CustomerService
addressService: services.AddressService
customerGroupService: services.CustomerGroupService
customerGroupCustomerService: services.CustomerGroupCustomerService
customerService: ModulesSdkTypes.InternalModuleService<any>
addressService: ModulesSdkTypes.InternalModuleService<any>
customerGroupService: ModulesSdkTypes.InternalModuleService<any>
customerGroupCustomerService: ModulesSdkTypes.InternalModuleService<any>
}
export default class CustomerModuleService implements ICustomerModuleService {
const generateMethodForModels = [Address, CustomerGroup, CustomerGroupCustomer]
export default class CustomerModuleService<
TAddress extends Address = Address,
TCustomer extends Customer = Customer,
TCustomerGroup extends CustomerGroup = CustomerGroup,
TCustomerGroupCustomer extends CustomerGroupCustomer = CustomerGroupCustomer
>
// TODO seb I let you manage that when you are moving forward
extends ModulesSdkUtils.abstractModuleServiceFactory<
InjectedDependencies,
CustomerDTO,
{
Address: { dto: any }
CustomerGroup: { dto: any }
CustomerGroupCustomer: { dto: any }
}
>(Customer, generateMethodForModels, entityNameToLinkableKeysMap)
implements ICustomerModuleService
{
protected baseRepository_: DAL.RepositoryService
protected customerService_: services.CustomerService
protected addressService_: services.AddressService
protected customerGroupService_: services.CustomerGroupService
protected customerGroupCustomerService_: services.CustomerGroupCustomerService
protected customerService_: ModulesSdkTypes.InternalModuleService<TCustomer>
protected addressService_: ModulesSdkTypes.InternalModuleService<TAddress>
protected customerGroupService_: ModulesSdkTypes.InternalModuleService<TCustomerGroup>
protected customerGroupCustomerService_: ModulesSdkTypes.InternalModuleService<TCustomerGroupCustomer>
constructor(
{
@@ -54,6 +75,9 @@ export default class CustomerModuleService implements ICustomerModuleService {
}: InjectedDependencies,
protected readonly moduleDeclaration: InternalModuleDeclaration
) {
// @ts-ignore
super(...arguments)
this.baseRepository_ = baseRepository
this.customerService_ = customerService
this.addressService_ = addressService
@@ -65,26 +89,6 @@ export default class CustomerModuleService implements ICustomerModuleService {
return joinerConfig
}
@InjectManager("baseRepository_")
async retrieve(
id: string,
config: FindConfig<CustomerTypes.CustomerDTO> = {},
@MedusaContext() sharedContext: Context = {}
): Promise<CustomerTypes.CustomerDTO> {
const customer = await this.customerService_.retrieve(
id,
config,
sharedContext
)
return await this.baseRepository_.serialize<CustomerTypes.CustomerDTO>(
customer,
{
populate: true,
}
)
}
async create(
data: CustomerTypes.CreateCustomerDTO,
sharedContext?: Context
@@ -95,13 +99,33 @@ export default class CustomerModuleService implements ICustomerModuleService {
sharedContext?: Context
): Promise<CustomerTypes.CustomerDTO[]>
@InjectTransactionManager("baseRepository_")
@InjectManager("baseRepository_")
async create(
dataOrArray:
| CustomerTypes.CreateCustomerDTO
| CustomerTypes.CreateCustomerDTO[],
@MedusaContext() sharedContext: Context = {}
) {
): Promise<CustomerTypes.CustomerDTO | CustomerTypes.CustomerDTO[]> {
const customers = await this.create_(dataOrArray, sharedContext).catch(
this.handleDbErrors
)
const serialized = await this.baseRepository_.serialize<
CustomerTypes.CustomerDTO[]
>(customers, {
populate: true,
})
return Array.isArray(dataOrArray) ? serialized : serialized[0]
}
@InjectTransactionManager("baseRepository_")
async create_(
dataOrArray:
| CustomerTypes.CreateCustomerDTO
| CustomerTypes.CreateCustomerDTO[],
@MedusaContext() sharedContext: Context = {}
): Promise<CustomerTypes.CustomerDTO[]> {
const data = Array.isArray(dataOrArray) ? dataOrArray : [dataOrArray]
const customers = await this.customerService_.create(data, sharedContext)
@@ -121,12 +145,7 @@ export default class CustomerModuleService implements ICustomerModuleService {
await this.addAddresses(addressDataWithCustomerIds, sharedContext)
const serialized = await this.baseRepository_.serialize<
CustomerTypes.CustomerDTO[]
>(customers, {
populate: true,
})
return Array.isArray(dataOrArray) ? serialized : serialized[0]
return customers as unknown as CustomerTypes.CustomerDTO[]
}
update(
@@ -151,37 +170,38 @@ export default class CustomerModuleService implements ICustomerModuleService {
data: CustomerTypes.CustomerUpdatableFields,
@MedusaContext() sharedContext: Context = {}
) {
let updateData: CustomerTypes.UpdateCustomerDTO[] = []
let updateData:
| CustomerTypes.UpdateCustomerDTO
| CustomerTypes.UpdateCustomerDTO[]
| {
selector: CustomerTypes.FilterableCustomerProps
data: CustomerTypes.CustomerUpdatableFields
} = []
if (isString(idsOrSelector)) {
updateData = [
{
id: idsOrSelector,
...data,
},
]
updateData = {
id: idsOrSelector,
...data,
}
} else if (Array.isArray(idsOrSelector)) {
updateData = idsOrSelector.map((id) => ({
id,
...data,
}))
} else {
const ids = await this.customerService_.list(
idsOrSelector,
{ select: ["id"] },
sharedContext
)
updateData = ids.map(({ id }) => ({
id,
...data,
}))
updateData = {
selector: idsOrSelector,
data: data,
}
}
const customers = await this.customerService_.update(
updateData,
sharedContext
)
const serialized = await this.baseRepository_.serialize<
CustomerTypes.CustomerDTO[]
CustomerTypes.CustomerDTO | CustomerTypes.CustomerDTO[]
>(customers, {
populate: true,
})
@@ -189,78 +209,6 @@ export default class CustomerModuleService implements ICustomerModuleService {
return isString(idsOrSelector) ? serialized[0] : serialized
}
delete(customerId: string, sharedContext?: Context): Promise<void>
delete(customerIds: string[], sharedContext?: Context): Promise<void>
delete(
selector: CustomerTypes.FilterableCustomerProps,
sharedContext?: Context
): Promise<void>
@InjectTransactionManager("baseRepository_")
async delete(
idsOrSelector: string | string[] | CustomerTypes.FilterableCustomerProps,
@MedusaContext() sharedContext: Context = {}
) {
let toDelete = Array.isArray(idsOrSelector)
? idsOrSelector
: [idsOrSelector as string]
if (isObject(idsOrSelector)) {
const ids = await this.customerService_.list(
idsOrSelector,
{
select: ["id"],
},
sharedContext
)
toDelete = ids.map(({ id }) => id)
}
return await this.customerService_.delete(toDelete, sharedContext)
}
@InjectManager("baseRepository_")
async list(
filters: CustomerTypes.FilterableCustomerProps = {},
config: FindConfig<CustomerTypes.CustomerDTO> = {},
@MedusaContext() sharedContext: Context = {}
) {
const customers = await this.customerService_.list(
filters,
config,
sharedContext
)
return await this.baseRepository_.serialize<CustomerTypes.CustomerDTO[]>(
customers,
{
populate: true,
}
)
}
@InjectManager("baseRepository_")
async listAndCount(
filters: CustomerTypes.FilterableCustomerProps = {},
config: FindConfig<CustomerTypes.CustomerDTO> = {},
@MedusaContext() sharedContext: Context = {}
): Promise<[CustomerTypes.CustomerDTO[], number]> {
const [customers, count] = await this.customerService_.listAndCount(
filters,
config,
sharedContext
)
return [
await this.baseRepository_.serialize<CustomerTypes.CustomerDTO[]>(
customers,
{
populate: true,
}
),
count,
]
}
async createCustomerGroup(
dataOrArrayOfData: CustomerTypes.CreateCustomerGroupDTO,
sharedContext?: Context
@@ -278,55 +226,36 @@ export default class CustomerModuleService implements ICustomerModuleService {
| CustomerTypes.CreateCustomerGroupDTO[],
@MedusaContext() sharedContext: Context = {}
) {
const data = Array.isArray(dataOrArrayOfData)
? dataOrArrayOfData
: [dataOrArrayOfData]
const groups = await this.customerGroupService_.create(
dataOrArrayOfData,
sharedContext
)
const groups = await this.customerGroupService_.create(data, sharedContext)
const serialized = await this.baseRepository_.serialize<
CustomerTypes.CustomerGroupDTO[]
return await this.baseRepository_.serialize<
CustomerTypes.CustomerGroupDTO | CustomerTypes.CustomerGroupDTO[]
>(groups, {
populate: true,
})
return Array.isArray(dataOrArrayOfData) ? serialized : serialized[0]
}
@InjectManager("baseRepository_")
async retrieveCustomerGroup(
groupId: string,
config: FindConfig<CustomerTypes.CustomerGroupDTO> = {},
@MedusaContext() sharedContext: Context = {}
) {
const group = await this.customerGroupService_.retrieve(
groupId,
config,
sharedContext
)
return await this.baseRepository_.serialize<CustomerTypes.CustomerGroupDTO>(
group,
{ populate: true }
)
}
async updateCustomerGroup(
async updateCustomerGroups(
groupId: string,
data: CustomerTypes.CustomerGroupUpdatableFields,
sharedContext?: Context
): Promise<CustomerTypes.CustomerGroupDTO>
async updateCustomerGroup(
async updateCustomerGroups(
groupIds: string[],
data: CustomerTypes.CustomerGroupUpdatableFields,
sharedContext?: Context
): Promise<CustomerTypes.CustomerGroupDTO[]>
async updateCustomerGroup(
async updateCustomerGroups(
selector: CustomerTypes.FilterableCustomerGroupProps,
data: CustomerTypes.CustomerGroupUpdatableFields,
sharedContext?: Context
): Promise<CustomerTypes.CustomerGroupDTO[]>
@InjectTransactionManager("baseRepository_")
async updateCustomerGroup(
async updateCustomerGroups(
groupIdOrSelector:
| string
| string[]
@@ -334,29 +263,27 @@ export default class CustomerModuleService implements ICustomerModuleService {
data: CustomerTypes.CustomerGroupUpdatableFields,
@MedusaContext() sharedContext: Context = {}
) {
let updateData: CustomerTypes.UpdateCustomerGroupDTO[] = []
if (isString(groupIdOrSelector)) {
updateData = [
{
id: groupIdOrSelector,
...data,
},
]
} else if (Array.isArray(groupIdOrSelector)) {
updateData = groupIdOrSelector.map((id) => ({
let updateData:
| CustomerTypes.UpdateCustomerGroupDTO
| CustomerTypes.UpdateCustomerGroupDTO[]
| {
selector: CustomerTypes.FilterableCustomerGroupProps
data: CustomerTypes.CustomerGroupUpdatableFields
} = []
if (isString(groupIdOrSelector) || Array.isArray(groupIdOrSelector)) {
const groupIdOrSelectorArray = Array.isArray(groupIdOrSelector)
? groupIdOrSelector
: [groupIdOrSelector]
updateData = groupIdOrSelectorArray.map((id) => ({
id,
...data,
}))
} else {
const ids = await this.customerGroupService_.list(
groupIdOrSelector,
{ select: ["id"] },
sharedContext
)
updateData = ids.map(({ id }) => ({
id,
...data,
}))
updateData = {
selector: groupIdOrSelector,
data: data,
}
}
const groups = await this.customerGroupService_.update(
@@ -376,39 +303,6 @@ export default class CustomerModuleService implements ICustomerModuleService {
>(groups, { populate: true })
}
deleteCustomerGroup(groupId: string, sharedContext?: Context): Promise<void>
deleteCustomerGroup(
groupIds: string[],
sharedContext?: Context
): Promise<void>
deleteCustomerGroup(
selector: CustomerTypes.FilterableCustomerGroupProps,
sharedContext?: Context
): Promise<void>
@InjectTransactionManager("baseRepository_")
async deleteCustomerGroup(
groupIdOrSelector:
| string
| string[]
| CustomerTypes.FilterableCustomerGroupProps,
@MedusaContext() sharedContext: Context = {}
) {
let toDelete = Array.isArray(groupIdOrSelector)
? groupIdOrSelector
: [groupIdOrSelector as string]
if (isObject(groupIdOrSelector)) {
const ids = await this.customerGroupService_.list(
groupIdOrSelector,
{ select: ["id"] },
sharedContext
)
toDelete = ids.map(({ id }) => id)
}
return await this.customerGroupService_.delete(toDelete, sharedContext)
}
async addCustomerToGroup(
groupCustomerPair: CustomerTypes.GroupCustomerPair,
sharedContext?: Context
@@ -425,17 +319,20 @@ export default class CustomerModuleService implements ICustomerModuleService {
@MedusaContext() sharedContext: Context = {}
): Promise<{ id: string } | { id: string }[]> {
const groupCustomers = await this.customerGroupCustomerService_.create(
Array.isArray(data) ? data : [data],
data,
sharedContext
)
if (Array.isArray(data)) {
return groupCustomers.map((gc) => ({ id: gc.id }))
return (groupCustomers as unknown as TCustomerGroupCustomer[]).map(
(gc) => ({ id: gc.id })
)
}
return { id: groupCustomers[0].id }
return { id: groupCustomers.id }
}
// TODO: should be createAddresses to conform to the convention
async addAddresses(
addresses: CustomerTypes.CreateCustomerAddressDTO[],
sharedContext?: Context
@@ -445,7 +342,7 @@ export default class CustomerModuleService implements ICustomerModuleService {
sharedContext?: Context
): Promise<CustomerTypes.CustomerAddressDTO>
@InjectTransactionManager("baseRepository_")
@InjectManager("baseRepository_")
async addAddresses(
data:
| CustomerTypes.CreateCustomerAddressDTO
@@ -454,13 +351,10 @@ export default class CustomerModuleService implements ICustomerModuleService {
): Promise<
CustomerTypes.CustomerAddressDTO | CustomerTypes.CustomerAddressDTO[]
> {
const addresses = await this.addressService_.create(
Array.isArray(data) ? data : [data],
sharedContext
const addresses = await this.addAddresses_(data, sharedContext).catch(
this.handleDbErrors
)
await this.flush(sharedContext).catch(this.handleDbErrors)
const serialized = await this.baseRepository_.serialize<
CustomerTypes.CustomerAddressDTO[]
>(addresses, { populate: true })
@@ -472,24 +366,39 @@ export default class CustomerModuleService implements ICustomerModuleService {
return serialized[0]
}
async updateAddress(
@InjectTransactionManager("baseRepository_")
private async addAddresses_(
data:
| CustomerTypes.CreateCustomerAddressDTO
| CustomerTypes.CreateCustomerAddressDTO[],
@MedusaContext() sharedContext: Context = {}
) {
const addresses = await this.addressService_.create(
Array.isArray(data) ? data : [data],
sharedContext
)
return addresses
}
async updateAddresses(
addressId: string,
data: CustomerTypes.UpdateCustomerAddressDTO,
sharedContext?: Context
): Promise<CustomerTypes.CustomerAddressDTO>
async updateAddress(
async updateAddresses(
addressIds: string[],
data: CustomerTypes.UpdateCustomerAddressDTO,
sharedContext?: Context
): Promise<CustomerTypes.CustomerAddressDTO[]>
async updateAddress(
async updateAddresses(
selector: CustomerTypes.FilterableCustomerAddressProps,
data: CustomerTypes.UpdateCustomerAddressDTO,
sharedContext?: Context
): Promise<CustomerTypes.CustomerAddressDTO[]>
@InjectTransactionManager("baseRepository_")
async updateAddress(
async updateAddresses(
addressIdOrSelector:
| string
| string[]
@@ -497,7 +406,12 @@ export default class CustomerModuleService implements ICustomerModuleService {
data: CustomerTypes.UpdateCustomerAddressDTO,
@MedusaContext() sharedContext: Context = {}
) {
let updateData: CustomerTypes.UpdateCustomerAddressDTO[] = []
let updateData:
| CustomerTypes.UpdateCustomerAddressDTO[]
| {
selector: CustomerTypes.FilterableCustomerAddressProps
data: CustomerTypes.UpdateCustomerAddressDTO
} = []
if (isString(addressIdOrSelector)) {
updateData = [
{
@@ -511,15 +425,10 @@ export default class CustomerModuleService implements ICustomerModuleService {
...data,
}))
} else {
const ids = await this.addressService_.list(
addressIdOrSelector,
{ select: ["id"] },
sharedContext
)
updateData = ids.map(({ id }) => ({
id,
...data,
}))
updateData = {
selector: addressIdOrSelector,
data,
}
}
const addresses = await this.addressService_.update(
@@ -540,78 +449,6 @@ export default class CustomerModuleService implements ICustomerModuleService {
return serialized
}
async deleteAddress(addressId: string, sharedContext?: Context): Promise<void>
async deleteAddress(
addressIds: string[],
sharedContext?: Context
): Promise<void>
async deleteAddress(
selector: CustomerTypes.FilterableCustomerAddressProps,
sharedContext?: Context
): Promise<void>
@InjectTransactionManager("baseRepository_")
async deleteAddress(
addressIdOrSelector:
| string
| string[]
| CustomerTypes.FilterableCustomerAddressProps,
@MedusaContext() sharedContext: Context = {}
) {
let toDelete = Array.isArray(addressIdOrSelector)
? addressIdOrSelector
: [addressIdOrSelector as string]
if (isObject(addressIdOrSelector)) {
const ids = await this.addressService_.list(
addressIdOrSelector,
{ select: ["id"] },
sharedContext
)
toDelete = ids.map(({ id }) => id)
}
await this.addressService_.delete(toDelete, sharedContext)
}
@InjectManager("baseRepository_")
async listAddresses(
filters?: CustomerTypes.FilterableCustomerAddressProps,
config?: FindConfig<CustomerTypes.CustomerAddressDTO>,
@MedusaContext() sharedContext: Context = {}
): Promise<CustomerTypes.CustomerAddressDTO[]> {
const addresses = await this.addressService_.list(
filters,
config,
sharedContext
)
return await this.baseRepository_.serialize<
CustomerTypes.CustomerAddressDTO[]
>(addresses, { populate: true })
}
@InjectManager("baseRepository_")
async listAndCountAddresses(
filters?: CustomerTypes.FilterableCustomerAddressProps,
config?: FindConfig<CustomerTypes.CustomerAddressDTO>,
@MedusaContext() sharedContext: Context = {}
): Promise<[CustomerTypes.CustomerAddressDTO[], number]> {
const [addresses, count] = await this.addressService_.listAndCount(
filters,
config,
sharedContext
)
return [
await this.baseRepository_.serialize<CustomerTypes.CustomerAddressDTO[]>(
addresses,
{ populate: true }
),
count,
]
}
async removeCustomerFromGroup(
groupCustomerPair: CustomerTypes.GroupCustomerPair,
sharedContext?: Context
@@ -636,153 +473,6 @@ export default class CustomerModuleService implements ICustomerModuleService {
)
}
@InjectManager("baseRepository_")
async listCustomerGroupRelations(
filters?: CustomerTypes.FilterableCustomerGroupCustomerProps,
config?: FindConfig<CustomerTypes.CustomerGroupCustomerDTO>,
@MedusaContext() sharedContext: Context = {}
) {
const groupCustomers = await this.customerGroupCustomerService_.list(
filters,
config,
sharedContext
)
return await this.baseRepository_.serialize<
CustomerTypes.CustomerGroupCustomerDTO[]
>(groupCustomers, {
populate: true,
})
}
@InjectManager("baseRepository_")
async listCustomerGroups(
filters: CustomerTypes.FilterableCustomerGroupProps = {},
config: FindConfig<CustomerTypes.CustomerGroupDTO> = {},
@MedusaContext() sharedContext: Context = {}
) {
const groups = await this.customerGroupService_.list(
filters,
config,
sharedContext
)
return await this.baseRepository_.serialize<
CustomerTypes.CustomerGroupDTO[]
>(groups, {
populate: true,
})
}
@InjectManager("baseRepository_")
async listAndCountCustomerGroups(
filters: CustomerTypes.FilterableCustomerGroupProps = {},
config: FindConfig<CustomerTypes.CustomerGroupDTO> = {},
@MedusaContext() sharedContext: Context = {}
): Promise<[CustomerTypes.CustomerGroupDTO[], number]> {
const [groups, count] = await this.customerGroupService_.listAndCount(
filters,
config,
sharedContext
)
return [
await this.baseRepository_.serialize<CustomerTypes.CustomerGroupDTO[]>(
groups,
{
populate: true,
}
),
count,
]
}
@InjectTransactionManager("baseRepository_")
async softDeleteCustomerGroup<
TReturnableLinkableKeys extends string = string
>(
groupIds: string[],
config: SoftDeleteReturn<TReturnableLinkableKeys> = {},
@MedusaContext() sharedContext: Context = {}
) {
const [_, cascadedEntitiesMap] =
await this.customerGroupService_.softDelete(groupIds, sharedContext)
return config.returnLinkableKeys
? mapObjectTo<Record<TReturnableLinkableKeys, string[]>>(
cascadedEntitiesMap,
entityNameToLinkableKeysMap,
{
pick: config.returnLinkableKeys,
}
)
: void 0
}
@InjectTransactionManager("baseRepository_")
async restoreCustomerGroup<TReturnableLinkableKeys extends string = string>(
groupIds: string[],
config: RestoreReturn<TReturnableLinkableKeys> = {},
@MedusaContext() sharedContext: Context = {}
) {
const [_, cascadedEntitiesMap] = await this.customerGroupService_.restore(
groupIds,
sharedContext
)
return config.returnLinkableKeys
? mapObjectTo<Record<TReturnableLinkableKeys, string[]>>(
cascadedEntitiesMap,
entityNameToLinkableKeysMap,
{
pick: config.returnLinkableKeys,
}
)
: void 0
}
@InjectTransactionManager("baseRepository_")
async softDelete<TReturnableLinkableKeys extends string = string>(
customerIds: string[],
config: SoftDeleteReturn<TReturnableLinkableKeys> = {},
@MedusaContext() sharedContext: Context = {}
) {
const [_, cascadedEntitiesMap] = await this.customerService_.softDelete(
customerIds,
sharedContext
)
return config.returnLinkableKeys
? mapObjectTo<Record<TReturnableLinkableKeys, string[]>>(
cascadedEntitiesMap,
entityNameToLinkableKeysMap,
{
pick: config.returnLinkableKeys,
}
)
: void 0
}
@InjectTransactionManager("baseRepository_")
async restore<TReturnableLinkableKeys extends string = string>(
customerIds: string[],
config: RestoreReturn<TReturnableLinkableKeys> = {},
@MedusaContext() sharedContext: Context = {}
) {
const [_, cascadedEntitiesMap] = await this.customerService_.restore(
customerIds,
sharedContext
)
return config.returnLinkableKeys
? mapObjectTo<Record<TReturnableLinkableKeys, string[]>>(
cascadedEntitiesMap,
entityNameToLinkableKeysMap,
{
pick: config.returnLinkableKeys,
}
)
: void 0
}
private async flush(context: Context) {
const em = (context.manager ?? context.transactionManager) as EntityManager
await em.flush()

View File

@@ -1,22 +0,0 @@
import { CustomerTypes, DAL } from "@medusajs/types"
import { ModulesSdkUtils } from "@medusajs/utils"
import { Customer } from "@models"
type InjectedDependencies = {
customerRepository: DAL.RepositoryService
}
export default class CustomerService<
TEntity extends Customer = Customer
> extends ModulesSdkUtils.abstractServiceFactory<
InjectedDependencies,
{
create: CustomerTypes.CreateCustomerDTO
update: CustomerTypes.UpdateCustomerDTO
}
>(Customer)<TEntity> {
constructor(container: InjectedDependencies) {
// @ts-ignore
super(...arguments)
}
}

View File

@@ -1,5 +1 @@
export { default as AddressService } from "./address"
export { default as CustomerGroupService } from "./customer-group"
export { default as CustomerService } from "./customer"
export { default as CustomerModuleService } from "./customer-module"
export { default as CustomerGroupCustomerService } from "./customer-group-customer"

View File

@@ -1,5 +1,8 @@
import { Logger } from "@medusajs/types"
export * from "./address"
export * as ServiceTypes from "./services"
export * from "./services"
export type InitializeModuleInjectableDependencies = {
logger?: Logger
}

View File

@@ -0,0 +1,5 @@
export interface CreateCustomerGroupCustomerDTO {
customer_id: string
customer_group_id: string
created_by?: string
}

View File

@@ -0,0 +1,2 @@
export * from "./address"
export * from "./customer-group-customer"