feat(medusa): Convert RegionService to TypeScript (#1914)

This commit is contained in:
Frane Polić authored and GitHub committed 2022-08-22 19:59:02 +02:00
1 parent 80e02130b4
commit bda83a84bc
11 files changed
+387 -285

No files matched your search

+5
View File
@@ -0,0 +1,5 @@
---
"@medusajs/medusa": patch
---
Convert RegionService to TypeScript
@@ -2309,7 +2309,6 @@ describe("/admin/discounts", () => {
}) })
const cond = discountCondition.data.discount_condition const cond = discountCondition.data.discount_condition
console.log(cond.products)
expect(discountCondition.status).toEqual(200) expect(discountCondition.status).toEqual(200)
expect(cond).toMatchSnapshot({ expect(cond).toMatchSnapshot({
@@ -1,10 +1,16 @@
import { IsArray, IsNumber, IsOptional, IsString } from "class-validator" import {
import { defaultAdminRegionFields, defaultAdminRegionRelations } from "." IsArray,
IsNumber,
IsObject,
IsOptional,
IsString,
} from "class-validator"
import { EntityManager } from "typeorm" import { EntityManager } from "typeorm"
import { validator } from "../../../../utils/validator"
import { Region } from "../../../.." import { Region } from "../../../.."
import RegionService from "../../../../services/region" import RegionService from "../../../../services/region"
import { validator } from "../../../../utils/validator" import { defaultAdminRegionRelations, defaultAdminRegionFields } from "."
/** /**
* @oas [post] /regions * @oas [post] /regions
@@ -113,4 +119,8 @@ export class AdminPostRegionsReq {
@IsArray() @IsArray()
@IsString({ each: true }) @IsString({ each: true })
countries: string[] countries: string[]
@IsObject()
@IsOptional()
metadata?: Record<string, unknown>
} }
@@ -58,7 +58,7 @@ export default (app) => {
return app return app
} }
export const defaultAdminRegionFields = [ export const defaultAdminRegionFields: (keyof Region)[] = [
"id", "id",
"name", "name",
"automatic_taxes", "automatic_taxes",
@@ -2,14 +2,15 @@ import {
IsArray, IsArray,
IsBoolean, IsBoolean,
IsNumber, IsNumber,
IsObject,
IsOptional, IsOptional,
IsString, IsString,
} from "class-validator" } from "class-validator"
import { defaultAdminRegionFields, defaultAdminRegionRelations } from "."
import { EntityManager } from "typeorm" import { EntityManager } from "typeorm"
import RegionService from "../../../../services/region"
import { validator } from "../../../../utils/validator" import { validator } from "../../../../utils/validator"
import RegionService from "../../../../services/region"
import { defaultAdminRegionRelations, defaultAdminRegionFields } from "."
/** /**
* @oas [post] /regions/{id} * @oas [post] /regions/{id}
@@ -139,4 +140,8 @@ export class AdminPostRegionsRegionReq {
@IsString({ each: true }) @IsString({ each: true })
@IsOptional() @IsOptional()
countries?: string[] countries?: string[]
@IsObject()
@IsOptional()
metadata?: Record<string, unknown>
} }
@@ -1,16 +1,23 @@
import { IdMap, MockManager, MockRepository } from "medusa-test-utils" import { IdMap, MockManager, MockRepository } from "medusa-test-utils"
import RegionService from "../region" import RegionService from "../region"
import {
EventBusService,
FulfillmentProviderService,
PaymentProviderService,
StoreService,
} from "../index"
import { CreateRegionInput } from "../../types/region"
const eventBusService = { const eventBusService = {
emit: jest.fn(), emit: jest.fn(),
withTransaction: function () { withTransaction: function () {
return this return this
}, },
} } as unknown as EventBusService
describe("RegionService", () => { describe("RegionService", () => {
describe("create", () => {
const regionRepository = MockRepository({}) const regionRepository = MockRepository({})
const ppRepository = MockRepository({ const ppRepository = MockRepository({
findOne: (query) => { findOne: (query) => {
if (query.where.id === "should_fail") { if (query.where.id === "should_fail") {
@@ -62,8 +69,15 @@ describe("RegionService", () => {
currencies: [{ code: "dkk" }, { code: "usd" }, { code: "eur" }], currencies: [{ code: "dkk" }, { code: "usd" }, { code: "eur" }],
} }
}, },
} } as unknown as StoreService
const taxProviderRepository = MockRepository({})
const fulfillmentProviderService = {} as FulfillmentProviderService
const paymentProviderService = {} as PaymentProviderService
describe("create", () => {
const regionService = new RegionService({ const regionService = new RegionService({
manager: MockManager, manager: MockManager,
eventBusService, eventBusService,
@@ -73,6 +87,9 @@ describe("RegionService", () => {
regionRepository, regionRepository,
countryRepository, countryRepository,
storeService, storeService,
fulfillmentProviderService,
taxProviderRepository,
paymentProviderService,
}) })
beforeEach(async () => { beforeEach(async () => {
@@ -85,7 +102,7 @@ describe("RegionService", () => {
currency_code: "USD", currency_code: "USD",
tax_rate: 0.25, tax_rate: 0.25,
countries: ["US"], countries: ["US"],
}) } as CreateRegionInput)
expect(regionRepository.create).toHaveBeenCalledTimes(1) expect(regionRepository.create).toHaveBeenCalledTimes(1)
expect(regionRepository.create).toHaveBeenCalledWith({ expect(regionRepository.create).toHaveBeenCalledWith({
@@ -106,7 +123,7 @@ describe("RegionService", () => {
currency_code: "EUR", currency_code: "EUR",
tax_rate: 0.25, tax_rate: 0.25,
countries: ["DK"], countries: ["DK"],
}) } as CreateRegionInput)
} catch (error) { } catch (error) {
expect(error.message).toBe( expect(error.message).toBe(
`Denmark already exists in region ${IdMap.getId("dk-reg")}` `Denmark already exists in region ${IdMap.getId("dk-reg")}`
@@ -149,7 +166,7 @@ describe("RegionService", () => {
tax_rate: 0.25, tax_rate: 0.25,
countries: ["US"], countries: ["US"],
payment_providers: ["should_fail"], payment_providers: ["should_fail"],
}) } as CreateRegionInput)
} catch (error) { } catch (error) {
expect(error.message).toBe("Payment provider not found") expect(error.message).toBe("Payment provider not found")
} }
@@ -163,7 +180,7 @@ describe("RegionService", () => {
tax_rate: 0.25, tax_rate: 0.25,
countries: ["US"], countries: ["US"],
fulfillment_providers: ["should_fail"], fulfillment_providers: ["should_fail"],
}) } as CreateRegionInput)
} catch (error) { } catch (error) {
expect(error.message).toBe("Fulfillment provider not found") expect(error.message).toBe("Fulfillment provider not found")
} }
@@ -179,6 +196,14 @@ describe("RegionService", () => {
manager: MockManager, manager: MockManager,
eventBusService, eventBusService,
regionRepository, regionRepository,
fulfillmentProviderService,
taxProviderRepository,
paymentProviderService,
fulfillmentProviderRepository: fpRepository,
paymentProviderRepository: ppRepository,
currencyRepository,
countryRepository,
storeService,
}) })
beforeEach(async () => { beforeEach(async () => {
@@ -196,27 +221,6 @@ describe("RegionService", () => {
}) })
describe("validateFields_", () => { describe("validateFields_", () => {
const regionRepository = MockRepository({})
const ppRepository = MockRepository({
findOne: (query) => {
if (query.where.id === "should_fail") {
return Promise.resolve(undefined)
}
return Promise.resolve({
id: "default_provider",
})
},
})
const fpRepository = MockRepository({
findOne: (query) => {
if (query.where.id === "should_fail") {
return Promise.resolve(undefined)
}
return Promise.resolve({
id: "default_provider",
})
},
})
const countryRepository = MockRepository({ const countryRepository = MockRepository({
findOne: (query) => { findOne: (query) => {
if (query.where.iso_2 === "dk") { if (query.where.iso_2 === "dk") {
@@ -234,24 +238,16 @@ describe("RegionService", () => {
}, },
}) })
const storeService = {
withTransaction: function() {
return this
},
retrieve: () => {
return {
id: IdMap.getId("test-store"),
currencies: [{ code: "dkk" }, { code: "usd" }, { code: "eur" }],
}
},
}
const regionService = new RegionService({ const regionService = new RegionService({
manager: MockManager, manager: MockManager,
eventBusService, eventBusService,
regionRepository,
fulfillmentProviderService,
taxProviderRepository,
paymentProviderService,
fulfillmentProviderRepository: fpRepository, fulfillmentProviderRepository: fpRepository,
paymentProviderRepository: ppRepository, paymentProviderRepository: ppRepository,
regionRepository, currencyRepository,
countryRepository, countryRepository,
storeService, storeService,
}) })
@@ -262,13 +258,19 @@ describe("RegionService", () => {
it("throws on invalid country code", async () => { it("throws on invalid country code", async () => {
await expect( await expect(
regionService.validateFields_({ countries: ["ddd"] }) // @ts-ignore
regionService.validateFields({
countries: ["ddd"],
} as CreateRegionInput)
).rejects.toThrow("Invalid country code") ).rejects.toThrow("Invalid country code")
}) })
it("throws on in use country code", async () => { it("throws on in use country code", async () => {
await expect( await expect(
regionService.validateFields_({ countries: ["DK"] }) // @ts-ignore
regionService.validateFields({
countries: ["DK"],
} as CreateRegionInput)
).rejects.toThrow( ).rejects.toThrow(
`Denmark already exists in region ${IdMap.getId("dk-reg")}` `Denmark already exists in region ${IdMap.getId("dk-reg")}`
) )
@@ -276,15 +278,19 @@ describe("RegionService", () => {
it("throws on unknown payment providers", async () => { it("throws on unknown payment providers", async () => {
await expect( await expect(
regionService.validateFields_({ payment_providers: ["should_fail"] }) // @ts-ignore
regionService.validateFields({
payment_providers: ["should_fail"],
} as CreateRegionInput)
).rejects.toThrow("Payment provider not found") ).rejects.toThrow("Payment provider not found")
}) })
it("throws on unknown fulfillment providers", async () => { it("throws on unknown fulfillment providers", async () => {
await expect( await expect(
regionService.validateFields_({ // @ts-ignore
regionService.validateFields({
fulfillment_providers: ["should_fail"], fulfillment_providers: ["should_fail"],
}) } as CreateRegionInput)
).rejects.toThrow("Fulfillment provider not found") ).rejects.toThrow("Fulfillment provider not found")
}) })
}) })
@@ -293,66 +299,18 @@ describe("RegionService", () => {
const regionRepository = MockRepository({ const regionRepository = MockRepository({
findOne: () => Promise.resolve({ id: IdMap.getId("test-region") }), findOne: () => Promise.resolve({ id: IdMap.getId("test-region") }),
}) })
const ppRepository = MockRepository({
findOne: (query) => {
if (query.where.id === "should_fail") {
return Promise.resolve(undefined)
}
return Promise.resolve({
id: "default_provider",
})
},
})
const fpRepository = MockRepository({
findOne: (query) => {
if (query.where.id === "should_fail") {
return Promise.resolve(undefined)
}
return Promise.resolve({
id: "default_provider",
})
},
})
const countryRepository = MockRepository({
findOne: (query) => {
if (query.where.iso_2 === "dk") {
return Promise.resolve({
id: IdMap.getId("dk"),
name: "Denmark",
region_id: IdMap.getId("dk-reg"),
})
}
return Promise.resolve({
id: IdMap.getId("test-country"),
name: "World",
})
},
})
const storeService = {
withTransaction: function() {
return this
},
retrieve: () => {
return {
id: IdMap.getId("test-store"),
currencies: [{ code: "dkk" }, { code: "usd" }, { code: "eur" }],
}
},
}
const currencyRepository = MockRepository({
findOne: () => Promise.resolve({ code: "eur" }),
})
const regionService = new RegionService({ const regionService = new RegionService({
manager: MockManager, manager: MockManager,
eventBusService, eventBusService,
regionRepository,
fulfillmentProviderService,
taxProviderRepository,
paymentProviderService,
fulfillmentProviderRepository: fpRepository, fulfillmentProviderRepository: fpRepository,
paymentProviderRepository: ppRepository, paymentProviderRepository: ppRepository,
regionRepository,
countryRepository,
currencyRepository, currencyRepository,
countryRepository,
storeService, storeService,
}) })
@@ -395,14 +353,21 @@ describe("RegionService", () => {
}), }),
}) })
const countryRepository = MockRepository({ const countryRepository = MockRepository({
findOne: (query) => Promise.resolve(), findOne: () => Promise.resolve(),
}) })
const regionService = new RegionService({ const regionService = new RegionService({
manager: MockManager, manager: MockManager,
eventBusService, eventBusService,
regionRepository, regionRepository,
fulfillmentProviderService,
taxProviderRepository,
paymentProviderService,
fulfillmentProviderRepository: fpRepository,
paymentProviderRepository: ppRepository,
currencyRepository,
countryRepository, countryRepository,
storeService,
}) })
beforeEach(async () => { beforeEach(async () => {
@@ -434,6 +399,7 @@ describe("RegionService", () => {
return Promise.resolve({ id: IdMap.getId("region") }) return Promise.resolve({ id: IdMap.getId("region") })
}, },
}) })
const countryRepository = MockRepository({ const countryRepository = MockRepository({
findOne: (query) => { findOne: (query) => {
if (query.where.iso_2 === "dk") { if (query.where.iso_2 === "dk") {
@@ -454,7 +420,14 @@ describe("RegionService", () => {
manager: MockManager, manager: MockManager,
eventBusService, eventBusService,
regionRepository, regionRepository,
fulfillmentProviderService,
taxProviderRepository,
paymentProviderService,
fulfillmentProviderRepository: fpRepository,
paymentProviderRepository: ppRepository,
currencyRepository,
countryRepository, countryRepository,
storeService,
}) })
beforeEach(async () => { beforeEach(async () => {
@@ -499,7 +472,7 @@ describe("RegionService", () => {
manager: MockManager, manager: MockManager,
eventBusService, eventBusService,
regionRepository, regionRepository,
}) } as any)
beforeEach(async () => { beforeEach(async () => {
jest.clearAllMocks() jest.clearAllMocks()
@@ -524,33 +497,19 @@ describe("RegionService", () => {
payment_providers: [{ id: "sweden_provider" }], payment_providers: [{ id: "sweden_provider" }],
}), }),
}) })
const ppRepository = MockRepository({
findOne: (query) => {
if (query.where.id === "should_fail") {
return Promise.resolve(undefined)
}
return Promise.resolve({
id: "default_provider",
})
},
})
const fpRepository = MockRepository({
findOne: (query) => {
if (query.where.id === "should_fail") {
return Promise.resolve(undefined)
}
return Promise.resolve({
id: "default_provider",
})
},
})
const regionService = new RegionService({ const regionService = new RegionService({
manager: MockManager, manager: MockManager,
eventBusService, eventBusService,
regionRepository,
fulfillmentProviderService,
taxProviderRepository,
paymentProviderService,
fulfillmentProviderRepository: fpRepository, fulfillmentProviderRepository: fpRepository,
paymentProviderRepository: ppRepository, paymentProviderRepository: ppRepository,
regionRepository, currencyRepository,
countryRepository,
storeService,
}) })
beforeEach(async () => { beforeEach(async () => {
@@ -609,8 +568,15 @@ describe("RegionService", () => {
const regionService = new RegionService({ const regionService = new RegionService({
manager: MockManager, manager: MockManager,
eventBusService, eventBusService,
fulfillmentProviderRepository: fpRepository,
regionRepository, regionRepository,
fulfillmentProviderService,
taxProviderRepository,
paymentProviderService,
fulfillmentProviderRepository: fpRepository,
paymentProviderRepository: ppRepository,
currencyRepository,
countryRepository,
storeService,
}) })
beforeEach(async () => { beforeEach(async () => {
@@ -660,7 +626,7 @@ describe("RegionService", () => {
manager: MockManager, manager: MockManager,
eventBusService, eventBusService,
regionRepository, regionRepository,
}) } as any)
beforeEach(async () => { beforeEach(async () => {
jest.clearAllMocks() jest.clearAllMocks()
@@ -695,7 +661,7 @@ describe("RegionService", () => {
manager: MockManager, manager: MockManager,
eventBusService, eventBusService,
regionRepository, regionRepository,
}) } as any)
beforeEach(async () => { beforeEach(async () => {
jest.clearAllMocks() jest.clearAllMocks()
+5 -2
View File
@@ -393,7 +393,7 @@ class CartService extends TransactionBaseService {
} }
} else { } else {
if (data.shipping_address) { if (data.shipping_address) {
if (!regCountries.includes(data.shipping_address.country_code)) { if (!regCountries.includes(data.shipping_address.country_code!)) {
throw new MedusaError( throw new MedusaError(
MedusaError.Types.NOT_ALLOWED, MedusaError.Types.NOT_ALLOWED,
"Shipping country not in region" "Shipping country not in region"
@@ -403,7 +403,10 @@ class CartService extends TransactionBaseService {
} }
if (data.shipping_address_id) { if (data.shipping_address_id) {
const addr = await addressRepo.findOne(data.shipping_address_id) const addr = await addressRepo.findOne(data.shipping_address_id)
if (addr && !regCountries.includes(addr.country_code)) { if (
addr?.country_code &&
!regCountries.includes(addr.country_code)
) {
throw new MedusaError( throw new MedusaError(
MedusaError.Types.NOT_ALLOWED, MedusaError.Types.NOT_ALLOWED,
"Shipping country not in region" "Shipping country not in region"
+1 -1
View File
@@ -64,7 +64,7 @@ class PricingService extends TransactionBaseService {
context: PriceSelectionContext context: PriceSelectionContext
): Promise<PricingContext> { ): Promise<PricingContext> {
let automaticTaxes = false let automaticTaxes = false
let taxRate = null let taxRate: number | null = null
let currencyCode = context.currency_code let currencyCode = context.currency_code
if (context.region_id) { if (context.region_id) {
@@ -360,7 +360,7 @@ class ProductVariantService extends BaseService {
/** /**
* Updates a variant's prices. * Updates a variant's prices.
* Deletes any prices that are not in the update object, and is not associated with a price list. * Deletes any prices that are not in the update object, and is not associated with a price list.
* @param variantId - the id of variant variant * @param variantId - the id of variant
* @param prices - the update prices * @param prices - the update prices
* @returns {Promise<void>} empty promise * @returns {Promise<void>} empty promise
*/ */
@@ -1,18 +1,64 @@
import { DeepPartial, EntityManager } from "typeorm"
import { MedusaError } from "medusa-core-utils" import { MedusaError } from "medusa-core-utils"
import { BaseService } from "medusa-interfaces"
import StoreService from "./store"
import EventBusService from "./event-bus"
import { countries } from "../utils/countries" import { countries } from "../utils/countries"
import { TransactionBaseService } from "../interfaces"
import { RegionRepository } from "../repositories/region"
import { CountryRepository } from "../repositories/country"
import { CurrencyRepository } from "../repositories/currency"
import { PaymentProviderRepository } from "../repositories/payment-provider"
import { FulfillmentProviderRepository } from "../repositories/fulfillment-provider"
import { TaxProviderRepository } from "../repositories/tax-provider"
import FulfillmentProviderService from "./fulfillment-provider"
import { Country, Currency, Region } from "../models"
import { FindConfig, Selector } from "../types/common"
import { CreateRegionInput, UpdateRegionInput } from "../types/region"
import { buildQuery, setMetadata } from "../utils"
import { PaymentProviderService } from "./index"
type InjectedDependencies = {
manager: EntityManager
storeService: StoreService
eventBusService: EventBusService
paymentProviderService: PaymentProviderService
fulfillmentProviderService: FulfillmentProviderService
regionRepository: typeof RegionRepository
countryRepository: typeof CountryRepository
currencyRepository: typeof CurrencyRepository
taxProviderRepository: typeof TaxProviderRepository
paymentProviderRepository: typeof PaymentProviderRepository
fulfillmentProviderRepository: typeof FulfillmentProviderRepository
}
/** /**
* Provides layer to manipulate regions. * Provides layer to manipulate regions.
* @extends BaseService * @extends BaseService
*/ */
class RegionService extends BaseService { class RegionService extends TransactionBaseService {
static Events = { static Events = {
UPDATED: "region.updated", UPDATED: "region.updated",
CREATED: "region.created", CREATED: "region.created",
DELETED: "region.deleted", DELETED: "region.deleted",
} }
protected manager_: EntityManager
protected transactionManager_: EntityManager | undefined
protected readonly eventBus_: EventBusService
protected readonly storeService_: StoreService
protected readonly paymentProviderService_: PaymentProviderService
protected readonly fulfillmentProviderService_: FulfillmentProviderService
protected readonly regionRepository_: typeof RegionRepository
protected readonly countryRepository_: typeof CountryRepository
protected readonly currencyRepository_: typeof CurrencyRepository
protected readonly paymentProviderRepository_: typeof PaymentProviderRepository
protected readonly fulfillmentProviderRepository_: typeof FulfillmentProviderRepository
protected readonly taxProviderRepository_: typeof TaxProviderRepository
constructor({ constructor({
manager, manager,
regionRepository, regionRepository,
@@ -25,75 +71,42 @@ class RegionService extends BaseService {
taxProviderRepository, taxProviderRepository,
paymentProviderService, paymentProviderService,
fulfillmentProviderService, fulfillmentProviderService,
}) { }: InjectedDependencies) {
super() super({
manager,
/** @private @const {EntityManager} */ regionRepository,
this.manager_ = manager countryRepository,
storeService,
/** @private @const {RegionRepository} */ eventBusService,
this.regionRepository_ = regionRepository currencyRepository,
paymentProviderRepository,
/** @private @const {CountryRepository} */ fulfillmentProviderRepository,
this.countryRepository_ = countryRepository taxProviderRepository,
paymentProviderService,
/** @private @const {StoreService} */ fulfillmentProviderService,
this.storeService_ = storeService
/** @private @const {EventBus} */
this.eventBus_ = eventBusService
/** @private @const {CurrencyRepository} */
this.currencyRepository_ = currencyRepository
/** @private @const {PaymentProviderRepository} */
this.paymentProviderRepository_ = paymentProviderRepository
/** @private @const {FulfillmentProviderRepository} */
this.fulfillmentProviderRepository_ = fulfillmentProviderRepository
/** @private @const {PaymentProviderService} */
this.paymentProviderService_ = paymentProviderService
/** @private @const {typeof TaxProviderService} */
this.taxProviderRepository_ = taxProviderRepository
/** @private @const {FulfillmentProviderService} */
this.fulfillmentProviderService_ = fulfillmentProviderService
}
withTransaction(transactionManager) {
if (!transactionManager) {
return this
}
const cloned = new RegionService({
manager: transactionManager,
regionRepository: this.regionRepository_,
currencyRepository: this.currencyRepository_,
countryRepository: this.countryRepository_,
storeService: this.storeService_,
eventBusService: this.eventBus_,
paymentProviderRepository: this.paymentProviderRepository_,
paymentProviderService: this.paymentProviderService_,
taxProviderRepository: this.taxProviderRepository_,
taxProviderService: this.taxProviderService_,
fulfillmentProviderRepository: this.fulfillmentProviderRepository_,
fulfillmentProviderService: this.fulfillmentProviderService_,
}) })
cloned.transactionManager_ = transactionManager this.manager_ = manager
this.regionRepository_ = regionRepository
return cloned this.countryRepository_ = countryRepository
this.storeService_ = storeService
this.eventBus_ = eventBusService
this.currencyRepository_ = currencyRepository
this.paymentProviderRepository_ = paymentProviderRepository
this.fulfillmentProviderRepository_ = fulfillmentProviderRepository
this.paymentProviderService_ = paymentProviderService
this.taxProviderRepository_ = taxProviderRepository
this.fulfillmentProviderService_ = fulfillmentProviderService
} }
/** /**
* Creates a region. * Creates a region.
* @param {Region} regionObject - the unvalidated region *
* @return {Region} the newly created region * @param data - the unvalidated region
* @return the newly created region
*/ */
async create(regionObject) { async create(data: CreateRegionInput): Promise<Region> {
return this.atomicPhase_(async (manager) => { return await this.atomicPhase_(async (manager) => {
const regionRepository = manager.getCustomRepository( const regionRepository = manager.getCustomRepository(
this.regionRepository_ this.regionRepository_
) )
@@ -101,13 +114,14 @@ class RegionService extends BaseService {
this.currencyRepository_ this.currencyRepository_
) )
const { metadata, currency_code, ...toValidate } = regionObject const regionObject = { ...data } as DeepPartial<Region>
const { metadata, currency_code, ...toValidate } = data
const validated = await this.validateFields_(toValidate) const validated = await this.validateFields(toValidate)
if (currency_code) { if (currency_code) {
// will throw if currency is not added to store currencies // will throw if currency is not added to store currencies
await this.validateCurrency_(currency_code) await this.validateCurrency(currency_code)
const currency = await currencyRepository.findOne({ const currency = await currencyRepository.findOne({
where: { code: currency_code.toLowerCase() }, where: { code: currency_code.toLowerCase() },
}) })
@@ -124,14 +138,17 @@ class RegionService extends BaseService {
} }
if (metadata) { if (metadata) {
regionObject.metadata = this.setMetadata_(regionObject, metadata) regionObject.metadata = setMetadata(
{ metadata: regionObject.metadata ?? null },
metadata
)
} }
for (const [key, value] of Object.entries(validated)) { for (const [key, value] of Object.entries(validated)) {
regionObject[key] = value regionObject[key] = value
} }
const created = regionRepository.create(regionObject) const created = regionRepository.create(regionObject) as Region
const result = await regionRepository.save(created) const result = await regionRepository.save(created)
await this.eventBus_ await this.eventBus_
@@ -146,12 +163,13 @@ class RegionService extends BaseService {
/** /**
* Updates a region * Updates a region
* @param {string} regionId - the region to update *
* @param {object} update - the data to update the region with * @param regionId - the region to update
* @return {Promise} the result of the update operation * @param update - the data to update the region with
* @return the result of the update operation
*/ */
async update(regionId, update) { async update(regionId: string, update: UpdateRegionInput): Promise<Region> {
return this.atomicPhase_(async (manager) => { return await this.atomicPhase_(async (manager) => {
const regionRepository = manager.getCustomRepository( const regionRepository = manager.getCustomRepository(
this.regionRepository_ this.regionRepository_
) )
@@ -163,11 +181,11 @@ class RegionService extends BaseService {
const { metadata, currency_code, ...toValidate } = update const { metadata, currency_code, ...toValidate } = update
const validated = await this.validateFields_(toValidate, region.id) const validated = await this.validateFields(toValidate, region.id)
if (currency_code) { if (currency_code) {
// will throw if currency is not added to store currencies // will throw if currency is not added to store currencies
await this.validateCurrency_(currency_code) await this.validateCurrency(currency_code)
const currency = await currencyRepository.findOne({ const currency = await currencyRepository.findOne({
where: { code: currency_code.toLowerCase() }, where: { code: currency_code.toLowerCase() },
}) })
@@ -183,7 +201,7 @@ class RegionService extends BaseService {
} }
if (metadata) { if (metadata) {
region.metadata = this.setMetadata_(region, metadata) region.metadata = setMetadata(region, metadata)
} }
for (const [key, value] of Object.entries(validated)) { for (const [key, value] of Object.entries(validated)) {
@@ -204,13 +222,19 @@ class RegionService extends BaseService {
} }
/** /**
* Validates fields for creation and updates. If the region already exisits * Validates fields for creation and updates. If the region already exists
* the id can be passed to check that country updates are allowed. * the id can be passed to check that country updates are allowed.
* @param {object} region - the region data to validate *
* @param {string?} id - optional id of the region to check against * @param regionData - the region data to validate
* @return {object} the validated region data * @param id - optional id of the region to check against
* @return the validated region data
*/ */
async validateFields_(region, id = undefined) { protected async validateFields<
T extends CreateRegionInput | UpdateRegionInput
>(
regionData: Omit<T, "metadata" | "currency_code">,
id?: T extends UpdateRegionInput ? string : undefined
): Promise<DeepPartial<Region>> {
const ppRepository = this.manager_.getCustomRepository( const ppRepository = this.manager_.getCustomRepository(
this.paymentProviderRepository_ this.paymentProviderRepository_
) )
@@ -221,23 +245,25 @@ class RegionService extends BaseService {
this.taxProviderRepository_ this.taxProviderRepository_
) )
const region = { ...regionData } as DeepPartial<Region>
if (region.tax_rate) { if (region.tax_rate) {
this.validateTaxRate_(region.tax_rate) this.validateTaxRate(region.tax_rate)
} }
if (region.countries) { if (regionData.countries) {
region.countries = await Promise.all( region.countries = await Promise.all(
region.countries.map((countryCode) => regionData.countries!.map((countryCode) =>
this.validateCountry_(countryCode, id) this.validateCountry(countryCode, id!)
) )
).catch((err) => { ).catch((err) => {
throw err throw err
}) })
} }
if (region.tax_provider_id) { if ((regionData as UpdateRegionInput).tax_provider_id) {
const tp = await tpRepository.findOne({ const tp = await tpRepository.findOne({
where: { id: region.tax_provider_id }, where: { id: (regionData as UpdateRegionInput).tax_provider_id },
}) })
if (!tp) { if (!tp) {
throw new MedusaError( throw new MedusaError(
@@ -247,9 +273,9 @@ class RegionService extends BaseService {
} }
} }
if (region.payment_providers) { if (regionData.payment_providers) {
region.payment_providers = await Promise.all( region.payment_providers = await Promise.all(
region.payment_providers.map(async (pId) => { regionData.payment_providers.map(async (pId) => {
const pp = await ppRepository.findOne({ where: { id: pId } }) const pp = await ppRepository.findOne({ where: { id: pId } })
if (!pp) { if (!pp) {
throw new MedusaError( throw new MedusaError(
@@ -263,9 +289,9 @@ class RegionService extends BaseService {
) )
} }
if (region.fulfillment_providers) { if (regionData.fulfillment_providers) {
region.fulfillment_providers = await Promise.all( region.fulfillment_providers = await Promise.all(
region.fulfillment_providers.map(async (fId) => { regionData.fulfillment_providers.map(async (fId) => {
const fp = await fpRepository.findOne({ where: { id: fId } }) const fp = await fpRepository.findOne({ where: { id: fId } })
if (!fp) { if (!fp) {
throw new MedusaError( throw new MedusaError(
@@ -284,9 +310,12 @@ class RegionService extends BaseService {
/** /**
* Validates a tax rate. Will throw if the tax rate is not between 0 and 1. * Validates a tax rate. Will throw if the tax rate is not between 0 and 1.
* @param {number} taxRate - a number representing the tax rate of the region *
* @param taxRate - a number representing the tax rate of the region
* @throws if the tax rate isn't number between 0-100
* @return void
*/ */
validateTaxRate_(taxRate) { protected validateTaxRate(taxRate: number): void | never {
if (taxRate > 100 || taxRate < 0) { if (taxRate > 100 || taxRate < 0) {
throw new MedusaError( throw new MedusaError(
MedusaError.Types.INVALID_DATA, MedusaError.Types.INVALID_DATA,
@@ -297,9 +326,14 @@ class RegionService extends BaseService {
/** /**
* Validates a currency code. Will throw if the currency code doesn't exist. * Validates a currency code. Will throw if the currency code doesn't exist.
* @param {string} currencyCode - an ISO currency code *
* @param currencyCode - an ISO currency code
* @throws if the provided currency code is invalid
* @return void
*/ */
async validateCurrency_(currencyCode) { protected async validateCurrency(
currencyCode: Currency["code"]
): Promise<void | never> {
const store = await this.storeService_ const store = await this.storeService_
.withTransaction(this.transactionManager_) .withTransaction(this.transactionManager_)
.retrieve({ relations: ["currencies"] }) .retrieve({ relations: ["currencies"] })
@@ -317,10 +351,15 @@ class RegionService extends BaseService {
/** /**
* Validates a country code. Will normalize the code before checking for * Validates a country code. Will normalize the code before checking for
* existence. * existence.
* @param {string} code - a 2 digit alphanumeric ISO country code *
* @param {string} regionId - the id of the current region to check against * @param code - a 2 digit alphanumeric ISO country code
* @param regionId - the id of the current region to check against
* @return the validated Country
*/ */
async validateCountry_(code, regionId) { protected async validateCountry(
code: Country["iso_2"],
regionId: string
): Promise<Country | never> {
const countryRepository = this.manager_.getCustomRepository( const countryRepository = this.manager_.getCustomRepository(
this.countryRepository_ this.countryRepository_
) )
@@ -359,7 +398,17 @@ class RegionService extends BaseService {
return country return country
} }
async retrieveByCountryCode(code, config = {}) { /**
* Retrieve a region by country code.
*
* @param code - a 2 digit alphanumeric ISO country code
* @param config - region find config
* @return a Region with country code
*/
async retrieveByCountryCode(
code: Country["iso_2"],
config: FindConfig<Region> = {}
): Promise<Region | never> {
const countryRepository = this.manager_.getCustomRepository( const countryRepository = this.manager_.getCustomRepository(
this.countryRepository_ this.countryRepository_
) )
@@ -389,17 +438,20 @@ class RegionService extends BaseService {
/** /**
* Retrieves a region by its id. * Retrieves a region by its id.
* @param {string} regionId - the id of the region to retrieve *
* @param {object} config - configuration settings * @param regionId - the id of the region to retrieve
* @return {Region} the region * @param config - configuration settings
* @return the region
*/ */
async retrieve(regionId, config = {}) { async retrieve(
regionId: string,
config: FindConfig<Region> = {}
): Promise<Region | never> {
const regionRepository = this.manager_.getCustomRepository( const regionRepository = this.manager_.getCustomRepository(
this.regionRepository_ this.regionRepository_
) )
const validatedId = this.validateId_(regionId) const query = buildQuery({ id: regionId }, config)
const query = this.buildQuery_({ id: validatedId }, config)
const region = await regionRepository.findOne(query) const region = await regionRepository.findOne(query)
if (!region) { if (!region) {
@@ -408,29 +460,39 @@ class RegionService extends BaseService {
`Region with ${regionId} was not found` `Region with ${regionId} was not found`
) )
} }
return region return region
} }
/** /**
* Lists all regions based on a query * Lists all regions based on a query
*
* @param {object} selector - query object for find * @param {object} selector - query object for find
* @param {object} config - configuration settings * @param {object} config - configuration settings
* @return {Promise} result of the find operation * @return {Promise} result of the find operation
*/ */
async list(selector = {}, config = { relations: [], skip: 0, take: 10 }) { async list(
selector: Selector<Region> = {},
config: FindConfig<Region> = {
relations: [],
skip: 0,
take: 10,
}
): Promise<Region[]> {
const regionRepo = this.manager_.getCustomRepository(this.regionRepository_) const regionRepo = this.manager_.getCustomRepository(this.regionRepository_)
const query = this.buildQuery_(selector, config) const query = buildQuery(selector, config)
return regionRepo.find(query) return regionRepo.find(query)
} }
/** /**
* Deletes a region. * Deletes a region.
* @param {string} regionId - the region to delete *
* @return {Promise} the result of the delete operation * @param regionId - the region to delete
* @return the result of the delete operation
*/ */
async delete(regionId) { async delete(regionId: string): Promise<void> {
return this.atomicPhase_(async (manager) => { return await this.atomicPhase_(async (manager) => {
const regionRepo = manager.getCustomRepository(this.regionRepository_) const regionRepo = manager.getCustomRepository(this.regionRepository_)
const countryRepo = manager.getCustomRepository(this.countryRepository_) const countryRepo = manager.getCustomRepository(this.countryRepository_)
@@ -455,15 +517,16 @@ class RegionService extends BaseService {
/** /**
* Adds a country to the region. * Adds a country to the region.
* @param {string} regionId - the region to add a country to *
* @param {string} code - a 2 digit alphanumeric ISO country code. * @param regionId - the region to add a country to
* @return {Promise} the result of the update operation * @param code - a 2 digit alphanumeric ISO country code.
* @return the updated Region
*/ */
async addCountry(regionId, code) { async addCountry(regionId: string, code: Country["iso_2"]): Promise<Region> {
return this.atomicPhase_(async (manager) => { return await this.atomicPhase_(async (manager) => {
const regionRepo = manager.getCustomRepository(this.regionRepository_) const regionRepo = manager.getCustomRepository(this.regionRepository_)
const country = await this.validateCountry_(code, regionId) const country = await this.validateCountry(code, regionId)
const region = await this.retrieve(regionId, { relations: ["countries"] }) const region = await this.retrieve(regionId, { relations: ["countries"] })
@@ -491,13 +554,17 @@ class RegionService extends BaseService {
} }
/** /**
* Removes a country from a Region * Removes a country from a Region.
* @param {string} regionId - the region to remove from *
* @param {string} code - a 2 digit alphanumeric ISO country code to remove * @param regionId - the region to remove from
* @return {Promise} the result of the update operation * @param code - a 2 digit alphanumeric ISO country code to remove
* @return the updated Region
*/ */
async removeCountry(regionId, code) { async removeCountry(
return this.atomicPhase_(async (manager) => { regionId: string,
code: Country["iso_2"]
): Promise<Region> {
return await this.atomicPhase_(async (manager) => {
const regionRepo = manager.getCustomRepository(this.regionRepository_) const regionRepo = manager.getCustomRepository(this.regionRepository_)
const region = await this.retrieve(regionId, { relations: ["countries"] }) const region = await this.retrieve(regionId, { relations: ["countries"] })
@@ -521,6 +588,7 @@ class RegionService extends BaseService {
id: updated.id, id: updated.id,
fields: ["countries"], fields: ["countries"],
}) })
return updated return updated
}) })
} }
@@ -528,12 +596,16 @@ class RegionService extends BaseService {
/** /**
* Adds a payment provider that is available in the region. Fails if the * Adds a payment provider that is available in the region. Fails if the
* provider doesn't exist. * provider doesn't exist.
* @param {string} regionId - the region to add the provider to *
* @param {string} providerId - the provider to add to the region * @param regionId - the region to add the provider to
* @return {Promise} the result of the update operation * @param providerId - the provider to add to the region
* @return the updated Region
*/ */
async addPaymentProvider(regionId, providerId) { async addPaymentProvider(
return this.atomicPhase_(async (manager) => { regionId: string,
providerId: string
): Promise<Region | never> {
return await this.atomicPhase_(async (manager) => {
const regionRepo = manager.getCustomRepository(this.regionRepository_) const regionRepo = manager.getCustomRepository(this.regionRepository_)
const ppRepo = manager.getCustomRepository( const ppRepo = manager.getCustomRepository(
this.paymentProviderRepository_ this.paymentProviderRepository_
@@ -575,12 +647,16 @@ class RegionService extends BaseService {
/** /**
* Adds a fulfillment provider that is available in the region. Fails if the * Adds a fulfillment provider that is available in the region. Fails if the
* provider doesn't exist. * provider doesn't exist.
* @param {string} regionId - the region to add the provider to *
* @param {string} providerId - the provider to add to the region * @param regionId - the region to add the provider to
* @return {Promise} the result of the update operation * @param providerId - the provider to add to the region
* @return the updated Region
*/ */
async addFulfillmentProvider(regionId, providerId) { async addFulfillmentProvider(
return this.atomicPhase_(async (manager) => { regionId: string,
providerId: string
): Promise<Region | never> {
return await this.atomicPhase_(async (manager) => {
const regionRepo = manager.getCustomRepository(this.regionRepository_) const regionRepo = manager.getCustomRepository(this.regionRepository_)
const fpRepo = manager.getCustomRepository( const fpRepo = manager.getCustomRepository(
this.fulfillmentProviderRepository_ this.fulfillmentProviderRepository_
@@ -592,7 +668,7 @@ class RegionService extends BaseService {
// Check if region already has payment provider // Check if region already has payment provider
if (region.fulfillment_providers.find(({ id }) => id === providerId)) { if (region.fulfillment_providers.find(({ id }) => id === providerId)) {
return Promise.resolve() return Promise.resolve(region)
} }
const fp = await fpRepo.findOne({ where: { id: providerId } }) const fp = await fpRepo.findOne({ where: { id: providerId } })
@@ -613,18 +689,23 @@ class RegionService extends BaseService {
id: updated.id, id: updated.id,
fields: ["fulfillment_providers"], fields: ["fulfillment_providers"],
}) })
return updated return updated
}) })
} }
/** /**
* Removes a payment provider from a region. Is idempotent. * Removes a payment provider from a region. Is idempotent.
* @param {string} regionId - the region to remove the provider from *
* @param {string} providerId - the provider to remove from the region * @param regionId - the region to remove the provider from
* @return {Promise} the result of the update operation * @param providerId - the provider to remove from the region
* @return the updated Region
*/ */
async removePaymentProvider(regionId, providerId) { async removePaymentProvider(
return this.atomicPhase_(async (manager) => { regionId: string,
providerId: string
): Promise<Region | never> {
return await this.atomicPhase_(async (manager) => {
const regionRepo = manager.getCustomRepository(this.regionRepository_) const regionRepo = manager.getCustomRepository(this.regionRepository_)
const region = await this.retrieve(regionId, { const region = await this.retrieve(regionId, {
@@ -633,7 +714,7 @@ class RegionService extends BaseService {
// Check if region already has payment provider // Check if region already has payment provider
if (!region.payment_providers.find(({ id }) => id === providerId)) { if (!region.payment_providers.find(({ id }) => id === providerId)) {
return Promise.resolve() return Promise.resolve(region)
} }
region.payment_providers = region.payment_providers.filter( region.payment_providers = region.payment_providers.filter(
@@ -647,18 +728,23 @@ class RegionService extends BaseService {
id: updated.id, id: updated.id,
fields: ["payment_providers"], fields: ["payment_providers"],
}) })
return updated return updated
}) })
} }
/** /**
* Removes a fulfillment provider from a region. Is idempotent. * Removes a fulfillment provider from a region. Is idempotent.
* @param {string} regionId - the region to remove the provider from *
* @param {string} providerId - the provider to remove from the region * @param regionId - the region to remove the provider from
* @return {Promise} the result of the update operation * @param providerId - the provider to remove from the region
* @return the updated Region
*/ */
async removeFulfillmentProvider(regionId, providerId) { async removeFulfillmentProvider(
return this.atomicPhase_(async (manager) => { regionId: string,
providerId: string
): Promise<Region | never> {
return await this.atomicPhase_(async (manager) => {
const regionRepo = manager.getCustomRepository(this.regionRepository_) const regionRepo = manager.getCustomRepository(this.regionRepository_)
const region = await this.retrieve(regionId, { const region = await this.retrieve(regionId, {
@@ -667,7 +753,7 @@ class RegionService extends BaseService {
// Check if region already has payment provider // Check if region already has payment provider
if (!region.fulfillment_providers.find(({ id }) => id === providerId)) { if (!region.fulfillment_providers.find(({ id }) => id === providerId)) {
return Promise.resolve() return Promise.resolve(region)
} }
region.fulfillment_providers = region.fulfillment_providers.filter( region.fulfillment_providers = region.fulfillment_providers.filter(
@@ -681,6 +767,7 @@ class RegionService extends BaseService {
id: updated.id, id: updated.id,
fields: ["fulfillment_providers"], fields: ["fulfillment_providers"],
}) })
return updated return updated
}) })
} }
+27
View File
@@ -0,0 +1,27 @@
import { FindConfig } from "./common"
import { Region } from "../models"
export type UpdateRegionInput = {
name?: string
currency_code?: string
tax_code?: string
tax_rate?: number
gift_cards_taxable?: boolean
automatic_taxes?: boolean
tax_provider_id?: string | null
payment_providers?: string[]
fulfillment_providers?: string[]
countries?: string[]
metadata?: Record<string, unknown>
}
export type CreateRegionInput = {
name: string
currency_code: string
tax_code?: string
tax_rate: number
payment_providers: string[]
fulfillment_providers: string[]
countries: string[]
metadata?: Record<string, unknown>
}