feat(cart): Cart + Address services (#6055)
Duplicate of #6008, but I messed up my git history big time :'( **What** - CartService and CartRepository + tests - AddressService and AddressRepository + tests - CartModuleService skeleton. Full implementation + tests will be added in a separate PR
This commit is contained in:
1 parent
b6ac768698
commit
ce81cade88
20 files changed
+847
-33
No files matched your search
@@ -67,12 +67,13 @@ export default class Cart {
|
||||
metadata?: Record<string, unknown> | null
|
||||
|
||||
@OneToMany(() => LineItem, (lineItem) => lineItem.cart, {
|
||||
orphanRemoval: true,
|
||||
cascade: [Cascade.REMOVE],
|
||||
})
|
||||
items = new Collection<LineItem>(this)
|
||||
|
||||
@OneToMany(() => ShippingMethod, (shippingMethod) => shippingMethod.cart, {
|
||||
orphanRemoval: true,
|
||||
cascade: [Cascade.REMOVE],
|
||||
|
||||
})
|
||||
shipping_methods = new Collection<ShippingMethod>(this)
|
||||
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import { Context } from "@medusajs/types"
|
||||
import { DALUtils } from "@medusajs/utils"
|
||||
import { SqlEntityManager } from "@mikro-orm/postgresql"
|
||||
import { Address } from "@models"
|
||||
import { CreateAddressDTO, UpdateAddressDTO } from "../types"
|
||||
|
||||
export class AddressRepository extends DALUtils.mikroOrmBaseRepositoryFactory<
|
||||
Address,
|
||||
{
|
||||
create: CreateAddressDTO
|
||||
}
|
||||
>(Address) {
|
||||
async update(
|
||||
data: { address: Address; update: UpdateAddressDTO }[],
|
||||
context: Context = {}
|
||||
): Promise<Address[]> {
|
||||
const manager = this.getActiveManager<SqlEntityManager>(context)
|
||||
|
||||
const entities = data.map(({ address, update }) => {
|
||||
return manager.assign(address, update)
|
||||
})
|
||||
|
||||
manager.persist(entities)
|
||||
|
||||
return entities
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { Context } from "@medusajs/types"
|
||||
import { DALUtils } from "@medusajs/utils"
|
||||
import { SqlEntityManager } from "@mikro-orm/postgresql"
|
||||
import { Cart } from "@models"
|
||||
import { CreateCartDTO, UpdateCartDTO } from "../types"
|
||||
|
||||
export class CartRepository extends DALUtils.mikroOrmBaseRepositoryFactory<
|
||||
Cart,
|
||||
{
|
||||
create: CreateCartDTO
|
||||
}
|
||||
>(Cart) {
|
||||
async update(
|
||||
data: { cart: Cart; update: UpdateCartDTO }[],
|
||||
context: Context = {}
|
||||
): Promise<Cart[]> {
|
||||
const manager = this.getActiveManager<SqlEntityManager>(context)
|
||||
|
||||
const entities = data.map(({ cart, update }) => {
|
||||
return manager.assign(cart, update)
|
||||
})
|
||||
|
||||
manager.persist(entities)
|
||||
|
||||
return entities
|
||||
}
|
||||
}
|
||||
@@ -1 +1,4 @@
|
||||
export { MikroOrmBaseRepository as BaseRepository } from "@medusajs/utils"
|
||||
export * from "./address"
|
||||
export * from "./cart"
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
import {
|
||||
AddressDTO,
|
||||
Context,
|
||||
DAL,
|
||||
FilterableAddressProps,
|
||||
FindConfig,
|
||||
} from "@medusajs/types"
|
||||
import {
|
||||
InjectManager,
|
||||
InjectTransactionManager,
|
||||
MedusaContext,
|
||||
MedusaError,
|
||||
ModulesSdkUtils,
|
||||
retrieveEntity,
|
||||
} from "@medusajs/utils"
|
||||
import { Address } from "@models"
|
||||
import { AddressRepository } from "../repositories/address"
|
||||
import { CreateAddressDTO, UpdateAddressDTO } from "../types"
|
||||
|
||||
type InjectedDependencies = {
|
||||
addressRepository: DAL.RepositoryService
|
||||
}
|
||||
|
||||
export default class AddressService<TEntity extends Address = Address> {
|
||||
protected readonly addressRepository_: DAL.RepositoryService
|
||||
|
||||
constructor({ addressRepository }: InjectedDependencies) {
|
||||
this.addressRepository_ = addressRepository
|
||||
}
|
||||
|
||||
@InjectManager("addressRepository_")
|
||||
async retrieve(
|
||||
id: string,
|
||||
config: FindConfig<AddressDTO> = {},
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<TEntity> {
|
||||
return (await retrieveEntity<Address, AddressDTO>({
|
||||
id: id,
|
||||
entityName: Address.name,
|
||||
repository: this.addressRepository_,
|
||||
config,
|
||||
sharedContext,
|
||||
})) as TEntity
|
||||
}
|
||||
|
||||
@InjectManager("addressRepository_")
|
||||
async list(
|
||||
filters: FilterableAddressProps = {},
|
||||
config: FindConfig<AddressDTO> = {},
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<TEntity[]> {
|
||||
const queryOptions = ModulesSdkUtils.buildQuery<Address>(filters, config)
|
||||
|
||||
return (await this.addressRepository_.find(
|
||||
queryOptions,
|
||||
sharedContext
|
||||
)) as TEntity[]
|
||||
}
|
||||
|
||||
@InjectManager("addressRepository_")
|
||||
async listAndCount(
|
||||
filters: FilterableAddressProps = {},
|
||||
config: FindConfig<AddressDTO> = {},
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<[TEntity[], number]> {
|
||||
const queryOptions = ModulesSdkUtils.buildQuery<Address>(filters, config)
|
||||
|
||||
return (await this.addressRepository_.findAndCount(
|
||||
queryOptions,
|
||||
sharedContext
|
||||
)) as [TEntity[], number]
|
||||
}
|
||||
|
||||
@InjectTransactionManager("addressRepository_")
|
||||
async create(
|
||||
data: CreateAddressDTO[],
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<TEntity[]> {
|
||||
return (await (this.addressRepository_ as AddressRepository).create(
|
||||
data,
|
||||
sharedContext
|
||||
)) as TEntity[]
|
||||
}
|
||||
|
||||
@InjectTransactionManager("addressRepository_")
|
||||
async update(
|
||||
data: UpdateAddressDTO[],
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<TEntity[]> {
|
||||
const existingAddresses = await this.list(
|
||||
{ id: data.map(({ id }) => id) },
|
||||
{},
|
||||
sharedContext
|
||||
)
|
||||
|
||||
const existingAddressesMap = new Map(
|
||||
existingAddresses.map<[string, Address]>((addr) => [addr.id, addr])
|
||||
)
|
||||
|
||||
const updates: { address: Address; update: UpdateAddressDTO }[] = []
|
||||
|
||||
for (const update of data) {
|
||||
const address = existingAddressesMap.get(update.id)
|
||||
|
||||
if (!address) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_FOUND,
|
||||
`Address with id "${update.id}" not found`
|
||||
)
|
||||
}
|
||||
|
||||
updates.push({ address, update })
|
||||
}
|
||||
|
||||
return (await (this.addressRepository_ as AddressRepository).update(
|
||||
updates,
|
||||
sharedContext
|
||||
)) as TEntity[]
|
||||
}
|
||||
|
||||
@InjectTransactionManager("addressRepository_")
|
||||
async delete(
|
||||
ids: string[],
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<void> {
|
||||
await this.addressRepository_.delete(ids, sharedContext)
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,35 @@
|
||||
import {
|
||||
CartDTO,
|
||||
Context,
|
||||
CreateCartDTO,
|
||||
DAL,
|
||||
FilterableCartProps,
|
||||
FindConfig,
|
||||
ICartModuleService,
|
||||
InternalModuleDeclaration,
|
||||
ModuleJoinerConfig,
|
||||
UpdateCartDTO,
|
||||
} from "@medusajs/types"
|
||||
|
||||
import { Cart } from "@models"
|
||||
|
||||
import {
|
||||
InjectManager,
|
||||
InjectTransactionManager,
|
||||
MedusaContext,
|
||||
} from "@medusajs/utils"
|
||||
import { joinerConfig } from "../joiner-config"
|
||||
import AddressService from "./address"
|
||||
import CartService from "./cart"
|
||||
|
||||
type InjectedDependencies = {
|
||||
baseRepository: DAL.RepositoryService
|
||||
cartService: CartService
|
||||
addressService: AddressService
|
||||
}
|
||||
|
||||
// TODO: implement ICartModuleService from @medusajs/types
|
||||
export default class CartModuleService<TCart extends Cart = Cart> {
|
||||
export default class CartModuleService implements ICartModuleService {
|
||||
protected baseRepository_: DAL.RepositoryService
|
||||
protected cartService_: CartService
|
||||
protected addressService_: AddressService
|
||||
|
||||
constructor(
|
||||
{ baseRepository }: InjectedDependencies,
|
||||
@@ -26,4 +41,138 @@ export default class CartModuleService<TCart extends Cart = Cart> {
|
||||
__joinerConfig(): ModuleJoinerConfig {
|
||||
return joinerConfig
|
||||
}
|
||||
|
||||
@InjectManager("baseRepository_")
|
||||
async retrieve(
|
||||
id: string,
|
||||
config: FindConfig<CartDTO> = {},
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<CartDTO> {
|
||||
const cart = await this.cartService_.retrieve(id, config, sharedContext)
|
||||
|
||||
return await this.baseRepository_.serialize<CartDTO>(cart, {
|
||||
populate: true,
|
||||
})
|
||||
}
|
||||
|
||||
@InjectManager("baseRepository_")
|
||||
async list(
|
||||
filters: FilterableCartProps = {},
|
||||
config: FindConfig<CartDTO> = {},
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<CartDTO[]> {
|
||||
const carts = await this.cartService_.list(filters, config, sharedContext)
|
||||
|
||||
return await this.baseRepository_.serialize<CartDTO[]>(carts, {
|
||||
populate: true,
|
||||
})
|
||||
}
|
||||
|
||||
@InjectManager("baseRepository_")
|
||||
async listAndCount(
|
||||
filters: FilterableCartProps = {},
|
||||
config: FindConfig<CartDTO> = {},
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<[CartDTO[], number]> {
|
||||
const [carts, count] = await this.cartService_.listAndCount(
|
||||
filters,
|
||||
config,
|
||||
sharedContext
|
||||
)
|
||||
|
||||
return [
|
||||
await this.baseRepository_.serialize<CartDTO[]>(carts, {
|
||||
populate: true,
|
||||
}),
|
||||
count,
|
||||
]
|
||||
}
|
||||
|
||||
async create(
|
||||
data: CreateCartDTO[],
|
||||
sharedContext?: Context
|
||||
): Promise<CartDTO[]>
|
||||
|
||||
async create(data: CreateCartDTO, sharedContext?: Context): Promise<CartDTO>
|
||||
|
||||
@InjectManager("baseRepository_")
|
||||
async create(
|
||||
data: CreateCartDTO[] | CreateCartDTO,
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<CartDTO[] | CartDTO> {
|
||||
const input = Array.isArray(data) ? data : [data]
|
||||
|
||||
const carts = await this.create_(input, sharedContext)
|
||||
|
||||
const result = await this.list(
|
||||
{ id: carts.map((p) => p!.id) },
|
||||
{
|
||||
relations: ["shipping_address", "billing_address"],
|
||||
},
|
||||
sharedContext
|
||||
)
|
||||
|
||||
return (Array.isArray(data) ? result : result[0]) as CartDTO | CartDTO[]
|
||||
}
|
||||
|
||||
@InjectTransactionManager("baseRepository_")
|
||||
protected async create_(
|
||||
data: CreateCartDTO[],
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
) {
|
||||
return await this.cartService_.create(data, sharedContext)
|
||||
}
|
||||
|
||||
async update(
|
||||
data: UpdateCartDTO[],
|
||||
sharedContext?: Context
|
||||
): Promise<CartDTO[]>
|
||||
|
||||
async update(data: UpdateCartDTO, sharedContext?: Context): Promise<CartDTO>
|
||||
|
||||
@InjectManager("baseRepository_")
|
||||
async update(
|
||||
data: UpdateCartDTO[] | UpdateCartDTO,
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<CartDTO[] | CartDTO> {
|
||||
const input = Array.isArray(data) ? data : [data]
|
||||
const carts = await this.update_(input, sharedContext)
|
||||
|
||||
const result = await this.list(
|
||||
{ id: carts.map((p) => p!.id) },
|
||||
{
|
||||
relations: ["shipping_address", "billing_address"],
|
||||
},
|
||||
sharedContext
|
||||
)
|
||||
|
||||
return (Array.isArray(data) ? result : result[0]) as CartDTO | CartDTO[]
|
||||
}
|
||||
|
||||
@InjectTransactionManager("baseRepository_")
|
||||
protected async update_(
|
||||
data: UpdateCartDTO[],
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
) {
|
||||
return await this.cartService_.update(data, sharedContext)
|
||||
}
|
||||
|
||||
async delete(
|
||||
ids: string[],
|
||||
sharedContext?: Context
|
||||
): Promise<void>
|
||||
|
||||
async delete(
|
||||
ids: string,
|
||||
sharedContext?: Context
|
||||
): Promise<void>
|
||||
|
||||
@InjectTransactionManager("baseRepository_")
|
||||
async delete(
|
||||
ids: string[] | string,
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<void> {
|
||||
const cartIds = Array.isArray(ids) ? ids : [ids]
|
||||
await this.cartService_.delete(cartIds, sharedContext)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
import {
|
||||
CartDTO,
|
||||
Context,
|
||||
DAL,
|
||||
FilterableCartProps,
|
||||
FindConfig,
|
||||
} from "@medusajs/types"
|
||||
import {
|
||||
InjectManager,
|
||||
InjectTransactionManager,
|
||||
MedusaContext,
|
||||
MedusaError,
|
||||
ModulesSdkUtils,
|
||||
retrieveEntity,
|
||||
} from "@medusajs/utils"
|
||||
import { Cart } from "@models"
|
||||
import { CartRepository } from "@repositories"
|
||||
import { CreateCartDTO, UpdateCartDTO } from "@types"
|
||||
|
||||
type InjectedDependencies = {
|
||||
cartRepository: DAL.RepositoryService
|
||||
}
|
||||
|
||||
export default class CartService<TEntity extends Cart = Cart> {
|
||||
protected readonly cartRepository_: DAL.RepositoryService
|
||||
|
||||
constructor({ cartRepository }: InjectedDependencies) {
|
||||
this.cartRepository_ = cartRepository
|
||||
}
|
||||
|
||||
@InjectManager("cartRepository_")
|
||||
async retrieve(
|
||||
id: string,
|
||||
config: FindConfig<CartDTO> = {},
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<TEntity> {
|
||||
return (await retrieveEntity<Cart, CartDTO>({
|
||||
id: id,
|
||||
entityName: Cart.name,
|
||||
repository: this.cartRepository_,
|
||||
config,
|
||||
sharedContext,
|
||||
})) as TEntity
|
||||
}
|
||||
|
||||
@InjectManager("cartRepository_")
|
||||
async list(
|
||||
filters: FilterableCartProps = {},
|
||||
config: FindConfig<CartDTO> = {},
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<TEntity[]> {
|
||||
const queryOptions = ModulesSdkUtils.buildQuery<Cart>(filters, config)
|
||||
|
||||
return (await this.cartRepository_.find(
|
||||
queryOptions,
|
||||
sharedContext
|
||||
)) as TEntity[]
|
||||
}
|
||||
|
||||
@InjectManager("cartRepository_")
|
||||
async listAndCount(
|
||||
filters: FilterableCartProps = {},
|
||||
config: FindConfig<CartDTO> = {},
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<[TEntity[], number]> {
|
||||
const queryOptions = ModulesSdkUtils.buildQuery<Cart>(filters, config)
|
||||
|
||||
return (await this.cartRepository_.findAndCount(
|
||||
queryOptions,
|
||||
sharedContext
|
||||
)) as [TEntity[], number]
|
||||
}
|
||||
|
||||
@InjectTransactionManager("cartRepository_")
|
||||
async create(
|
||||
data: CreateCartDTO[],
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<TEntity[]> {
|
||||
return (await (this.cartRepository_ as CartRepository).create(
|
||||
data,
|
||||
sharedContext
|
||||
)) as TEntity[]
|
||||
}
|
||||
|
||||
@InjectTransactionManager("cartRepository_")
|
||||
async update(
|
||||
data: UpdateCartDTO[],
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<TEntity[]> {
|
||||
const existingCarts = await this.list(
|
||||
{ id: data.map(({ id }) => id) },
|
||||
{},
|
||||
sharedContext
|
||||
)
|
||||
|
||||
const existingCartsMap = new Map(
|
||||
existingCarts.map<[string, Cart]>((cart) => [cart.id, cart])
|
||||
)
|
||||
|
||||
const updates: { cart: Cart; update: UpdateCartDTO }[] = []
|
||||
|
||||
for (const update of data) {
|
||||
const cart = existingCartsMap.get(update.id)
|
||||
|
||||
if (!cart) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_FOUND,
|
||||
`Cart with id "${update.id}" not found`
|
||||
)
|
||||
}
|
||||
|
||||
updates.push({ cart, update })
|
||||
}
|
||||
|
||||
return (await (this.cartRepository_ as CartRepository).update(
|
||||
updates,
|
||||
sharedContext
|
||||
)) as TEntity[]
|
||||
}
|
||||
|
||||
@InjectTransactionManager("cartRepository_")
|
||||
async delete(
|
||||
ids: string[],
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<void> {
|
||||
await this.cartRepository_.delete(ids, sharedContext)
|
||||
}
|
||||
}
|
||||
@@ -1,2 +1,4 @@
|
||||
export { default as CartModuleService } from "./cart-module";
|
||||
export { default as AddressService } from "./address"
|
||||
export { default as CartService } from "./cart"
|
||||
export { default as CartModuleService } from "./cart-module"
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
export interface UpsertAddressDTO {
|
||||
customer_id?: string
|
||||
company?: string
|
||||
first_name?: string
|
||||
last_name?: string
|
||||
address_1?: string
|
||||
address_2?: string
|
||||
city?: string
|
||||
country_code?: string
|
||||
province?: string
|
||||
postal_code?: string
|
||||
phone?: string
|
||||
metadata?: Record<string, unknown>
|
||||
}
|
||||
|
||||
export interface UpdateAddressDTO extends UpsertAddressDTO {
|
||||
id: string
|
||||
}
|
||||
|
||||
export interface CreateAddressDTO extends UpsertAddressDTO {}
|
||||
@@ -0,0 +1,18 @@
|
||||
export interface CreateCartDTO {
|
||||
region_id?: string
|
||||
customer_id?: string
|
||||
sales_channel_id?: string
|
||||
email?: string
|
||||
currency_code: string
|
||||
metadata?: Record<string, unknown>
|
||||
}
|
||||
|
||||
export interface UpdateCartDTO {
|
||||
id: string
|
||||
region_id?: string
|
||||
customer_id?: string
|
||||
sales_channel_id?: string
|
||||
email?: string
|
||||
currency_code?: string
|
||||
metadata?: Record<string, unknown>
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
import { Logger } from "@medusajs/types"
|
||||
export * from "./address"
|
||||
export * from "./cart"
|
||||
|
||||
export type InitializeModuleInjectableDependencies = {
|
||||
logger?: Logger
|
||||
|
||||
Reference in new issue
Block a user