feat(medusa): Convert GiftCardService to Typescript (#1664)
This commit is contained in:
1 parent
7b09b8c36c
commit
1585b7ae2b
11 files changed
+384
-357
No files matched your search
@@ -77,10 +77,9 @@ export class AdminPostGiftCardsReq {
|
|||||||
@IsBoolean()
|
@IsBoolean()
|
||||||
is_disabled?: boolean
|
is_disabled?: boolean
|
||||||
|
|
||||||
@IsOptional()
|
|
||||||
@IsString()
|
@IsString()
|
||||||
region_id?: string
|
region_id: string
|
||||||
|
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
metadata?: object
|
metadata?: Record<string, unknown>
|
||||||
}
|
}
|
||||||
@@ -2,14 +2,23 @@ import { Router } from "express"
|
|||||||
import "reflect-metadata"
|
import "reflect-metadata"
|
||||||
import { GiftCard } from "../../../.."
|
import { GiftCard } from "../../../.."
|
||||||
import { DeleteResponse, PaginatedResponse } from "../../../../types/common"
|
import { DeleteResponse, PaginatedResponse } from "../../../../types/common"
|
||||||
import middlewares from "../../../middlewares"
|
import middlewares, { transformQuery } from "../../../middlewares"
|
||||||
|
import { AdminGetGiftCardsParams } from "./list-gift-cards"
|
||||||
|
|
||||||
const route = Router()
|
const route = Router()
|
||||||
|
|
||||||
export default (app) => {
|
export default (app) => {
|
||||||
app.use("/gift-cards", route)
|
app.use("/gift-cards", route)
|
||||||
|
|
||||||
route.get("/", middlewares.wrap(require("./list-gift-cards").default))
|
route.get(
|
||||||
|
"/",
|
||||||
|
transformQuery(AdminGetGiftCardsParams, {
|
||||||
|
defaultFields: defaultAdminGiftCardFields,
|
||||||
|
defaultRelations: defaultAdminGiftCardRelations,
|
||||||
|
isList: true,
|
||||||
|
}),
|
||||||
|
middlewares.wrap(require("./list-gift-cards").default)
|
||||||
|
)
|
||||||
|
|
||||||
route.post("/", middlewares.wrap(require("./create-gift-card").default))
|
route.post("/", middlewares.wrap(require("./create-gift-card").default))
|
||||||
|
|
||||||
@@ -22,7 +31,7 @@ export default (app) => {
|
|||||||
return app
|
return app
|
||||||
}
|
}
|
||||||
|
|
||||||
export const defaultAdminGiftCardFields = [
|
export const defaultAdminGiftCardFields: (keyof GiftCard)[] = [
|
||||||
"id",
|
"id",
|
||||||
"code",
|
"code",
|
||||||
"value",
|
"value",
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Type } from "class-transformer"
|
import { Type } from "class-transformer"
|
||||||
import { IsInt, IsOptional, IsString } from "class-validator"
|
import { IsInt, IsOptional, IsString } from "class-validator"
|
||||||
|
import { pickBy } from "lodash"
|
||||||
import { defaultAdminGiftCardFields, defaultAdminGiftCardRelations } from "."
|
import { defaultAdminGiftCardFields, defaultAdminGiftCardRelations } from "."
|
||||||
import { GiftCardService } from "../../../../services"
|
import { GiftCardService } from "../../../../services"
|
||||||
import { validator } from "../../../../utils/validator"
|
import { validator } from "../../../../utils/validator"
|
||||||
@@ -27,21 +28,12 @@ import { validator } from "../../../../utils/validator"
|
|||||||
export default async (req, res) => {
|
export default async (req, res) => {
|
||||||
const validated = await validator(AdminGetGiftCardsParams, req.query)
|
const validated = await validator(AdminGetGiftCardsParams, req.query)
|
||||||
|
|
||||||
const selector = {}
|
|
||||||
|
|
||||||
if (validated.q && typeof validated.q !== "undefined") {
|
|
||||||
selector["q"] = validated.q
|
|
||||||
}
|
|
||||||
|
|
||||||
const giftCardService: GiftCardService = req.scope.resolve("giftCardService")
|
const giftCardService: GiftCardService = req.scope.resolve("giftCardService")
|
||||||
|
|
||||||
const giftCards = await giftCardService.list(selector, {
|
const giftCards = await giftCardService.list(
|
||||||
select: defaultAdminGiftCardFields,
|
pickBy(req.filterableFields, (val) => typeof val !== "undefined"),
|
||||||
relations: defaultAdminGiftCardRelations,
|
req.listConfig
|
||||||
order: { created_at: "DESC" },
|
)
|
||||||
limit: validated.limit,
|
|
||||||
skip: validated.offset,
|
|
||||||
})
|
|
||||||
|
|
||||||
res.status(200).json({
|
res.status(200).json({
|
||||||
gift_cards: giftCards,
|
gift_cards: giftCards,
|
||||||
|
|||||||
@@ -83,5 +83,5 @@ export class AdminPostGiftCardsGiftCardReq {
|
|||||||
region_id?: string
|
region_id?: string
|
||||||
|
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
metadata?: object
|
metadata?: Record<string, unknown>
|
||||||
}
|
}
|
||||||
@@ -14,7 +14,12 @@ export default (app) => {
|
|||||||
|
|
||||||
export const defaultStoreGiftCardRelations = ["region"]
|
export const defaultStoreGiftCardRelations = ["region"]
|
||||||
|
|
||||||
export const defaultStoreGiftCardFields = ["id", "code", "value", "balance"]
|
export const defaultStoreGiftCardFields: (keyof GiftCard)[] = [
|
||||||
|
"id",
|
||||||
|
"code",
|
||||||
|
"value",
|
||||||
|
"balance",
|
||||||
|
]
|
||||||
|
|
||||||
export const allowedStoreGiftCardRelations = ["region"]
|
export const allowedStoreGiftCardRelations = ["region"]
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,20 @@
|
|||||||
import { flatten, groupBy, map, merge } from "lodash"
|
import { flatten, groupBy, merge } from "lodash"
|
||||||
import { EntityRepository, FindManyOptions, Repository } from "typeorm"
|
import {
|
||||||
|
Brackets,
|
||||||
|
EntityRepository,
|
||||||
|
FindManyOptions,
|
||||||
|
Repository,
|
||||||
|
} from "typeorm"
|
||||||
import { GiftCard } from "../models/gift-card"
|
import { GiftCard } from "../models/gift-card"
|
||||||
|
import { ExtendedFindConfig, QuerySelector, Writable } from "../types/common"
|
||||||
|
|
||||||
@EntityRepository(GiftCard)
|
@EntityRepository(GiftCard)
|
||||||
export class GiftCardRepository extends Repository<GiftCard> {
|
export class GiftCardRepository extends Repository<GiftCard> {
|
||||||
public async findWithRelations(
|
public async findWithRelations(
|
||||||
relations: Array<keyof GiftCard> = [],
|
relations: (keyof GiftCard | string)[] = [],
|
||||||
idsOrOptionsWithoutRelations: Omit<
|
idsOrOptionsWithoutRelations:
|
||||||
FindManyOptions<GiftCard>,
|
| Omit<FindManyOptions<GiftCard>, "relations">
|
||||||
"relations"
|
| string[] = {}
|
||||||
> = {}
|
|
||||||
): Promise<GiftCard[]> {
|
): Promise<GiftCard[]> {
|
||||||
let entities
|
let entities
|
||||||
if (Array.isArray(idsOrOptionsWithoutRelations)) {
|
if (Array.isArray(idsOrOptionsWithoutRelations)) {
|
||||||
@@ -40,9 +45,45 @@ export class GiftCardRepository extends Repository<GiftCard> {
|
|||||||
const entitiesAndRelations = entitiesIdsWithRelations.concat(entities)
|
const entitiesAndRelations = entitiesIdsWithRelations.concat(entities)
|
||||||
|
|
||||||
const entitiesAndRelationsById = groupBy(entitiesAndRelations, "id")
|
const entitiesAndRelationsById = groupBy(entitiesAndRelations, "id")
|
||||||
return map(entitiesAndRelationsById, entityAndRelations =>
|
return Object.values(entitiesAndRelationsById).map((v) => merge({}, ...v))
|
||||||
merge({}, ...entityAndRelations)
|
}
|
||||||
|
|
||||||
|
protected async queryGiftCards(
|
||||||
|
q: string,
|
||||||
|
where: Partial<Writable<QuerySelector<GiftCard>>>,
|
||||||
|
rels: (keyof GiftCard | string)[]
|
||||||
|
): Promise<GiftCard[]> {
|
||||||
|
const raw = await this.createQueryBuilder("gift_card")
|
||||||
|
.leftJoinAndSelect("gift_card.order", "order")
|
||||||
|
.select(["gift_card.id"])
|
||||||
|
.where(where)
|
||||||
|
.andWhere(
|
||||||
|
new Brackets((qb) => {
|
||||||
|
return qb
|
||||||
|
.where(`gift_card.code ILIKE :q`, { q: `%${q}%` })
|
||||||
|
.orWhere(`display_id::varchar(255) ILIKE :dId`, { dId: `${q}` })
|
||||||
|
})
|
||||||
)
|
)
|
||||||
|
.getMany()
|
||||||
|
|
||||||
|
return this.findWithRelations(
|
||||||
|
rels,
|
||||||
|
raw.map((i) => i.id)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
public async listGiftCards(
|
||||||
|
query: ExtendedFindConfig<GiftCard, QuerySelector<GiftCard>>,
|
||||||
|
rels: (keyof GiftCard | string)[] = [],
|
||||||
|
q?: string
|
||||||
|
): Promise<GiftCard[]> {
|
||||||
|
if (q) {
|
||||||
|
const where = query.where
|
||||||
|
delete where.id
|
||||||
|
|
||||||
|
return await this.queryGiftCards(q, where, rels)
|
||||||
|
}
|
||||||
|
return this.findWithRelations(rels, query)
|
||||||
}
|
}
|
||||||
|
|
||||||
public async findOneWithRelations(
|
public async findOneWithRelations(
|
||||||
|
|||||||
@@ -5,23 +5,23 @@ import GiftCardService from "../gift-card"
|
|||||||
describe("GiftCardService", () => {
|
describe("GiftCardService", () => {
|
||||||
const eventBusService = {
|
const eventBusService = {
|
||||||
emit: jest.fn(),
|
emit: jest.fn(),
|
||||||
withTransaction: function() {
|
withTransaction: function () {
|
||||||
return this
|
return this
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
describe("create", () => {
|
describe("create", () => {
|
||||||
const giftCardRepo = MockRepository({
|
const giftCardRepo = MockRepository({
|
||||||
create: s => {
|
create: (s) => {
|
||||||
return Promise.resolve(s)
|
return Promise.resolve(s)
|
||||||
},
|
},
|
||||||
save: s => {
|
save: (s) => {
|
||||||
return Promise.resolve(s)
|
return Promise.resolve(s)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const regionService = {
|
const regionService = {
|
||||||
withTransaction: function() {
|
withTransaction: function () {
|
||||||
return this
|
return this
|
||||||
},
|
},
|
||||||
retrieve: () => {
|
retrieve: () => {
|
||||||
@@ -59,18 +59,6 @@ describe("GiftCardService", () => {
|
|||||||
code: expect.any(String),
|
code: expect.any(String),
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it("fails to create giftcard if no region is provided", async () => {
|
|
||||||
const card = {
|
|
||||||
...giftCard,
|
|
||||||
}
|
|
||||||
|
|
||||||
card.region_id = undefined
|
|
||||||
|
|
||||||
await expect(giftCardService.create(card)).rejects.toThrow(
|
|
||||||
"Gift card is missing region_id"
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("retrieve", () => {
|
describe("retrieve", () => {
|
||||||
@@ -152,16 +140,16 @@ describe("GiftCardService", () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const giftCardRepo = MockRepository({
|
const giftCardRepo = MockRepository({
|
||||||
findOneWithRelations: s => {
|
findOneWithRelations: (s) => {
|
||||||
return Promise.resolve(giftCard)
|
return Promise.resolve(giftCard)
|
||||||
},
|
},
|
||||||
save: s => {
|
save: (s) => {
|
||||||
return Promise.resolve(s)
|
return Promise.resolve(s)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const regionService = {
|
const regionService = {
|
||||||
withTransaction: function() {
|
withTransaction: function () {
|
||||||
return this
|
return this
|
||||||
},
|
},
|
||||||
retrieve: () => {
|
retrieve: () => {
|
||||||
@@ -198,7 +186,7 @@ describe("GiftCardService", () => {
|
|||||||
|
|
||||||
it.each([[-100], [6000]])(
|
it.each([[-100], [6000]])(
|
||||||
"fails to update balance with illegal input '%s'",
|
"fails to update balance with illegal input '%s'",
|
||||||
async input => {
|
async (input) => {
|
||||||
await expect(
|
await expect(
|
||||||
giftCardService.update(IdMap.getId("giftcard-id"), {
|
giftCardService.update(IdMap.getId("giftcard-id"), {
|
||||||
balance: input,
|
balance: input,
|
||||||
@@ -215,7 +203,7 @@ describe("GiftCardService", () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const giftCardRepo = MockRepository({
|
const giftCardRepo = MockRepository({
|
||||||
findOne: s => {
|
findOne: (s) => {
|
||||||
switch (s.where.id) {
|
switch (s.where.id) {
|
||||||
case IdMap.getId("gift-card"):
|
case IdMap.getId("gift-card"):
|
||||||
return Promise.resolve(giftCard)
|
return Promise.resolve(giftCard)
|
||||||
@@ -223,7 +211,7 @@ describe("GiftCardService", () => {
|
|||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
softRemove: s => {
|
softRemove: (s) => {
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,305 +0,0 @@
|
|||||||
import { MedusaError } from "medusa-core-utils"
|
|
||||||
import { BaseService } from "medusa-interfaces"
|
|
||||||
import randomize from "randomatic"
|
|
||||||
import { Brackets } from "typeorm"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provides layer to manipulate gift cards.
|
|
||||||
* @extends BaseService
|
|
||||||
*/
|
|
||||||
class GiftCardService extends BaseService {
|
|
||||||
static Events = {
|
|
||||||
CREATED: "gift_card.created",
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor({
|
|
||||||
manager,
|
|
||||||
giftCardRepository,
|
|
||||||
giftCardTransactionRepository,
|
|
||||||
regionService,
|
|
||||||
eventBusService,
|
|
||||||
}) {
|
|
||||||
super()
|
|
||||||
|
|
||||||
/** @private @const {EntityManager} */
|
|
||||||
this.manager_ = manager
|
|
||||||
|
|
||||||
/** @private @const {GiftCardRepository} */
|
|
||||||
this.giftCardRepository_ = giftCardRepository
|
|
||||||
|
|
||||||
/** @private @const {GiftCardRepository} */
|
|
||||||
this.giftCardTransactionRepo_ = giftCardTransactionRepository
|
|
||||||
|
|
||||||
/** @private @const {RegionService} */
|
|
||||||
this.regionService_ = regionService
|
|
||||||
|
|
||||||
/** @private @const {EventBus} */
|
|
||||||
this.eventBus_ = eventBusService
|
|
||||||
}
|
|
||||||
|
|
||||||
withTransaction(transactionManager) {
|
|
||||||
if (!transactionManager) {
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
const cloned = new GiftCardService({
|
|
||||||
manager: transactionManager,
|
|
||||||
giftCardRepository: this.giftCardRepository_,
|
|
||||||
giftCardTransactionRepository: this.giftCardTransactionRepo_,
|
|
||||||
regionService: this.regionService_,
|
|
||||||
eventBusService: this.eventBus_,
|
|
||||||
})
|
|
||||||
|
|
||||||
cloned.transactionManager_ = transactionManager
|
|
||||||
|
|
||||||
return cloned
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates a 16 character gift card code
|
|
||||||
* @return {string} the generated gift card code
|
|
||||||
*/
|
|
||||||
generateCode_() {
|
|
||||||
const code = [
|
|
||||||
randomize("A0", 4),
|
|
||||||
randomize("A0", 4),
|
|
||||||
randomize("A0", 4),
|
|
||||||
randomize("A0", 4),
|
|
||||||
].join("-")
|
|
||||||
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Object} selector - the query object for find
|
|
||||||
* @param {Object} config - the configuration used to find the objects. contains relations, skip, and take.
|
|
||||||
* @return {Promise} the result of the find operation
|
|
||||||
*/
|
|
||||||
async list(selector = {}, config = { relations: [], skip: 0, take: 10 }) {
|
|
||||||
const giftCardRepo = this.manager_.getCustomRepository(
|
|
||||||
this.giftCardRepository_
|
|
||||||
)
|
|
||||||
|
|
||||||
let q
|
|
||||||
if ("q" in selector) {
|
|
||||||
q = selector.q
|
|
||||||
delete selector.q
|
|
||||||
}
|
|
||||||
|
|
||||||
const query = this.buildQuery_(selector, config)
|
|
||||||
|
|
||||||
const rels = query.relations
|
|
||||||
delete query.relations
|
|
||||||
|
|
||||||
if (q) {
|
|
||||||
const where = query.where
|
|
||||||
delete where.id
|
|
||||||
|
|
||||||
const raw = await giftCardRepo
|
|
||||||
.createQueryBuilder("gift_card")
|
|
||||||
.leftJoinAndSelect("gift_card.order", "order")
|
|
||||||
.select(["gift_card.id"])
|
|
||||||
.where(where)
|
|
||||||
.andWhere(
|
|
||||||
new Brackets((qb) => {
|
|
||||||
return qb
|
|
||||||
.where(`gift_card.code ILIKE :q`, { q: `%${q}%` })
|
|
||||||
.orWhere(`display_id::varchar(255) ILIKE :dId`, { dId: `${q}` })
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.getMany()
|
|
||||||
|
|
||||||
return giftCardRepo.findWithRelations(
|
|
||||||
rels,
|
|
||||||
raw.map((i) => i.id)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return giftCardRepo.findWithRelations(rels, query)
|
|
||||||
}
|
|
||||||
|
|
||||||
async createTransaction(data) {
|
|
||||||
return this.atomicPhase_(async (manager) => {
|
|
||||||
const gctRepo = manager.getCustomRepository(this.giftCardTransactionRepo_)
|
|
||||||
const created = gctRepo.create(data)
|
|
||||||
const saved = await gctRepo.save(created)
|
|
||||||
return saved.id
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a gift card with provided data given that the data is validated.
|
|
||||||
* @param {GiftCard} giftCard - the gift card data to create
|
|
||||||
* @return {Promise<GiftCard>} the result of the create operation
|
|
||||||
*/
|
|
||||||
async create(giftCard) {
|
|
||||||
return this.atomicPhase_(async (manager) => {
|
|
||||||
const giftCardRepo = manager.getCustomRepository(this.giftCardRepository_)
|
|
||||||
|
|
||||||
if (!giftCard.region_id) {
|
|
||||||
throw new MedusaError(
|
|
||||||
MedusaError.Types.NOT_FOUND,
|
|
||||||
`Gift card is missing region_id`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Will throw if region does not exist
|
|
||||||
const region = await this.regionService_.retrieve(giftCard.region_id)
|
|
||||||
|
|
||||||
const code = this.generateCode_()
|
|
||||||
|
|
||||||
const toCreate = {
|
|
||||||
code,
|
|
||||||
region_id: region.id,
|
|
||||||
...giftCard,
|
|
||||||
}
|
|
||||||
|
|
||||||
const created = await giftCardRepo.create(toCreate)
|
|
||||||
const result = await giftCardRepo.save(created)
|
|
||||||
|
|
||||||
await this.eventBus_
|
|
||||||
.withTransaction(manager)
|
|
||||||
.emit(GiftCardService.Events.CREATED, {
|
|
||||||
id: result.id,
|
|
||||||
})
|
|
||||||
|
|
||||||
return result
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets a gift card by id.
|
|
||||||
* @param {string} giftCardId - id of gift card to retrieve
|
|
||||||
* @param {object} config - optional values to include with gift card query
|
|
||||||
* @return {Promise<GiftCard>} the gift card
|
|
||||||
*/
|
|
||||||
async retrieve(giftCardId, config = {}) {
|
|
||||||
const giftCardRepo = this.manager_.getCustomRepository(
|
|
||||||
this.giftCardRepository_
|
|
||||||
)
|
|
||||||
|
|
||||||
const validatedId = this.validateId_(giftCardId)
|
|
||||||
|
|
||||||
const query = {
|
|
||||||
where: { id: validatedId },
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.select) {
|
|
||||||
query.select = config.select
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.relations) {
|
|
||||||
query.relations = config.relations
|
|
||||||
}
|
|
||||||
|
|
||||||
const rels = query.relations
|
|
||||||
delete query.relations
|
|
||||||
|
|
||||||
const giftCard = await giftCardRepo.findOneWithRelations(rels, query)
|
|
||||||
|
|
||||||
if (!giftCard) {
|
|
||||||
throw new MedusaError(
|
|
||||||
MedusaError.Types.NOT_FOUND,
|
|
||||||
`Gift card with ${giftCardId} was not found`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return giftCard
|
|
||||||
}
|
|
||||||
|
|
||||||
async retrieveByCode(code, config = {}) {
|
|
||||||
const giftCardRepo = this.manager_.getCustomRepository(
|
|
||||||
this.giftCardRepository_
|
|
||||||
)
|
|
||||||
|
|
||||||
const query = {
|
|
||||||
where: { code },
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.select) {
|
|
||||||
query.select = config.select
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.relations) {
|
|
||||||
query.relations = config.relations
|
|
||||||
}
|
|
||||||
|
|
||||||
const rels = query.relations
|
|
||||||
delete query.relations
|
|
||||||
|
|
||||||
const giftCard = await giftCardRepo.findOneWithRelations(rels, query)
|
|
||||||
|
|
||||||
if (!giftCard) {
|
|
||||||
throw new MedusaError(
|
|
||||||
MedusaError.Types.NOT_FOUND,
|
|
||||||
`Gift card with ${code} was not found`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return giftCard
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates a giftCard.
|
|
||||||
* @param {string} giftCardId - giftCard id of giftCard to update
|
|
||||||
* @param {GiftCard} update - the data to update the giftCard with
|
|
||||||
* @return {Promise} the result of the update operation
|
|
||||||
*/
|
|
||||||
async update(giftCardId, update) {
|
|
||||||
return this.atomicPhase_(async (manager) => {
|
|
||||||
const giftCardRepo = manager.getCustomRepository(this.giftCardRepository_)
|
|
||||||
|
|
||||||
const giftCard = await this.retrieve(giftCardId)
|
|
||||||
|
|
||||||
const { region_id, metadata, balance, ...rest } = update
|
|
||||||
|
|
||||||
if (region_id && region_id !== giftCard.region_id) {
|
|
||||||
const region = await this.regionService_.retrieve(region_id)
|
|
||||||
giftCard.region_id = region.id
|
|
||||||
}
|
|
||||||
|
|
||||||
if (metadata) {
|
|
||||||
giftCard.metadata = await this.setMetadata_(giftCard.id, metadata)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof balance !== "undefined") {
|
|
||||||
if (balance < 0 || giftCard.value < balance) {
|
|
||||||
throw new MedusaError(
|
|
||||||
MedusaError.Types.INVALID_ARGUMENT,
|
|
||||||
"new balance is invalid"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
giftCard.balance = balance
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const [key, value] of Object.entries(rest)) {
|
|
||||||
giftCard[key] = value
|
|
||||||
}
|
|
||||||
|
|
||||||
const updated = await giftCardRepo.save(giftCard)
|
|
||||||
return updated
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes a gift card idempotently
|
|
||||||
* @param {string} giftCardId - id of gift card to delete
|
|
||||||
* @return {Promise} the result of the delete operation
|
|
||||||
*/
|
|
||||||
async delete(giftCardId) {
|
|
||||||
return this.atomicPhase_(async (manager) => {
|
|
||||||
const giftCardRepo = manager.getCustomRepository(this.giftCardRepository_)
|
|
||||||
|
|
||||||
const giftCard = await giftCardRepo.findOne({ where: { id: giftCardId } })
|
|
||||||
|
|
||||||
if (!giftCard) {
|
|
||||||
return Promise.resolve()
|
|
||||||
}
|
|
||||||
|
|
||||||
await giftCardRepo.softRemove(giftCard)
|
|
||||||
|
|
||||||
return Promise.resolve()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default GiftCardService
|
|
||||||
@@ -0,0 +1,273 @@
|
|||||||
|
import { MedusaError } from "medusa-core-utils"
|
||||||
|
import randomize from "randomatic"
|
||||||
|
import { Brackets, EntityManager, FindOneOptions } from "typeorm"
|
||||||
|
import { EventBusService } from "."
|
||||||
|
import { TransactionBaseService } from "../interfaces"
|
||||||
|
import { GiftCard } from "../models"
|
||||||
|
import { GiftCardRepository } from "../repositories/gift-card"
|
||||||
|
import { GiftCardTransactionRepository } from "../repositories/gift-card-transaction"
|
||||||
|
import {
|
||||||
|
ExtendedFindConfig,
|
||||||
|
FindConfig,
|
||||||
|
QuerySelector,
|
||||||
|
Selector,
|
||||||
|
} from "../types/common"
|
||||||
|
import {
|
||||||
|
CreateGiftCardInput,
|
||||||
|
CreateGiftCardTransactionInput,
|
||||||
|
UpdateGiftCardInput,
|
||||||
|
} from "../types/gift-card"
|
||||||
|
import { buildQuery, setMetadata } from "../utils"
|
||||||
|
import RegionService from "./region"
|
||||||
|
|
||||||
|
type InjectedDependencies = {
|
||||||
|
manager: EntityManager
|
||||||
|
giftCardRepository: typeof GiftCardRepository
|
||||||
|
giftCardTransactionRepository: typeof GiftCardTransactionRepository
|
||||||
|
regionService: RegionService
|
||||||
|
eventBusService: EventBusService
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Provides layer to manipulate gift cards.
|
||||||
|
*/
|
||||||
|
class GiftCardService extends TransactionBaseService<GiftCardService> {
|
||||||
|
protected readonly giftCardRepository_: typeof GiftCardRepository
|
||||||
|
protected readonly giftCardTransactionRepo_: typeof GiftCardTransactionRepository
|
||||||
|
protected readonly regionService_: RegionService
|
||||||
|
protected readonly eventBus_: EventBusService
|
||||||
|
|
||||||
|
protected manager_: EntityManager
|
||||||
|
protected transactionManager_: EntityManager | undefined
|
||||||
|
|
||||||
|
static Events = {
|
||||||
|
CREATED: "gift_card.created",
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor({
|
||||||
|
manager,
|
||||||
|
giftCardRepository,
|
||||||
|
giftCardTransactionRepository,
|
||||||
|
regionService,
|
||||||
|
eventBusService,
|
||||||
|
}: InjectedDependencies) {
|
||||||
|
// eslint-disable-next-line prefer-rest-params
|
||||||
|
super(arguments[0])
|
||||||
|
|
||||||
|
this.manager_ = manager
|
||||||
|
|
||||||
|
this.giftCardRepository_ = giftCardRepository
|
||||||
|
this.giftCardTransactionRepo_ = giftCardTransactionRepository
|
||||||
|
this.regionService_ = regionService
|
||||||
|
this.eventBus_ = eventBusService
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a 16 character gift card code
|
||||||
|
* @return the generated gift card code
|
||||||
|
*/
|
||||||
|
static generateCode(): string {
|
||||||
|
const code = [
|
||||||
|
randomize("A0", 4),
|
||||||
|
randomize("A0", 4),
|
||||||
|
randomize("A0", 4),
|
||||||
|
randomize("A0", 4),
|
||||||
|
].join("-")
|
||||||
|
|
||||||
|
return code
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param selector - the query object for find
|
||||||
|
* @param config - the configuration used to find the objects. contains relations, skip, and take.
|
||||||
|
* @return the result of the find operation
|
||||||
|
*/
|
||||||
|
async list(
|
||||||
|
selector: QuerySelector<GiftCard> = {},
|
||||||
|
config: FindConfig<GiftCard> = { relations: [], skip: 0, take: 10 }
|
||||||
|
): Promise<GiftCard[]> {
|
||||||
|
return await this.atomicPhase_(async (manager) => {
|
||||||
|
const giftCardRepo = manager.getCustomRepository(this.giftCardRepository_)
|
||||||
|
|
||||||
|
let q
|
||||||
|
if ("q" in selector) {
|
||||||
|
q = selector.q
|
||||||
|
delete selector.q
|
||||||
|
}
|
||||||
|
|
||||||
|
const query: ExtendedFindConfig<
|
||||||
|
GiftCard,
|
||||||
|
QuerySelector<GiftCard>
|
||||||
|
> = buildQuery<QuerySelector<GiftCard>, GiftCard>(selector, config)
|
||||||
|
|
||||||
|
const rels = query.relations
|
||||||
|
delete query.relations
|
||||||
|
|
||||||
|
return await giftCardRepo.listGiftCards(query, rels, q)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async createTransaction(
|
||||||
|
data: CreateGiftCardTransactionInput
|
||||||
|
): Promise<string> {
|
||||||
|
return await this.atomicPhase_(async (manager) => {
|
||||||
|
const gctRepo = manager.getCustomRepository(this.giftCardTransactionRepo_)
|
||||||
|
const created = gctRepo.create(data)
|
||||||
|
const saved = await gctRepo.save(created)
|
||||||
|
return saved.id
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a gift card with provided data given that the data is validated.
|
||||||
|
* @param giftCard - the gift card data to create
|
||||||
|
* @return the result of the create operation
|
||||||
|
*/
|
||||||
|
async create(giftCard: CreateGiftCardInput): Promise<GiftCard> {
|
||||||
|
return await this.atomicPhase_(async (manager) => {
|
||||||
|
const giftCardRepo = manager.getCustomRepository(this.giftCardRepository_)
|
||||||
|
|
||||||
|
// Will throw if region does not exist
|
||||||
|
const region = await this.regionService_
|
||||||
|
.withTransaction(manager)
|
||||||
|
.retrieve(giftCard.region_id)
|
||||||
|
|
||||||
|
const code = GiftCardService.generateCode()
|
||||||
|
|
||||||
|
const toCreate = {
|
||||||
|
code,
|
||||||
|
...giftCard,
|
||||||
|
region_id: region.id,
|
||||||
|
}
|
||||||
|
|
||||||
|
const created = giftCardRepo.create(toCreate)
|
||||||
|
const result = await giftCardRepo.save(created)
|
||||||
|
|
||||||
|
await this.eventBus_
|
||||||
|
.withTransaction(manager)
|
||||||
|
.emit(GiftCardService.Events.CREATED, {
|
||||||
|
id: result.id,
|
||||||
|
})
|
||||||
|
|
||||||
|
return result
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async retrieve_(
|
||||||
|
selector: Selector<GiftCard>,
|
||||||
|
config: FindConfig<GiftCard> = {}
|
||||||
|
): Promise<GiftCard> {
|
||||||
|
return await this.atomicPhase_(async (manager) => {
|
||||||
|
const giftCardRepo = manager.getCustomRepository(this.giftCardRepository_)
|
||||||
|
|
||||||
|
const { relations, ...query } = buildQuery(selector, config)
|
||||||
|
|
||||||
|
const giftCard = await giftCardRepo.findOneWithRelations(
|
||||||
|
relations as (keyof GiftCard)[],
|
||||||
|
query
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!giftCard) {
|
||||||
|
const selectorConstraints = Object.entries(selector)
|
||||||
|
.map((key, value) => `${key}: ${value}`)
|
||||||
|
.join(", ")
|
||||||
|
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.NOT_FOUND,
|
||||||
|
`Gift card with ${selectorConstraints} was not found`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return giftCard
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a gift card by id.
|
||||||
|
* @param giftCardId - id of gift card to retrieve
|
||||||
|
* @param config - optional values to include with gift card query
|
||||||
|
* @return the gift card
|
||||||
|
*/
|
||||||
|
async retrieve(
|
||||||
|
giftCardId: string,
|
||||||
|
config: FindConfig<GiftCard> = {}
|
||||||
|
): Promise<GiftCard> {
|
||||||
|
return await this.atomicPhase_(async () => {
|
||||||
|
return await this.retrieve_({ id: giftCardId }, config)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async retrieveByCode(
|
||||||
|
code: string,
|
||||||
|
config: FindConfig<GiftCard> = {}
|
||||||
|
): Promise<GiftCard> {
|
||||||
|
return await this.atomicPhase_(async () => {
|
||||||
|
return await this.retrieve_({ code }, config)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates a giftCard.
|
||||||
|
* @param giftCardId - giftCard id of giftCard to update
|
||||||
|
* @param update - the data to update the giftCard with
|
||||||
|
* @return the result of the update operation
|
||||||
|
*/
|
||||||
|
async update(
|
||||||
|
giftCardId: string,
|
||||||
|
update: UpdateGiftCardInput
|
||||||
|
): Promise<GiftCard> {
|
||||||
|
return await this.atomicPhase_(async (manager) => {
|
||||||
|
const giftCardRepo = manager.getCustomRepository(this.giftCardRepository_)
|
||||||
|
|
||||||
|
const giftCard = await this.retrieve(giftCardId)
|
||||||
|
|
||||||
|
const { region_id, metadata, balance, ...rest } = update
|
||||||
|
|
||||||
|
if (region_id && region_id !== giftCard.region_id) {
|
||||||
|
const region = await this.regionService_.retrieve(region_id)
|
||||||
|
giftCard.region_id = region.id
|
||||||
|
}
|
||||||
|
|
||||||
|
if (metadata) {
|
||||||
|
giftCard.metadata = setMetadata(giftCard, metadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof balance !== "undefined") {
|
||||||
|
if (balance < 0 || giftCard.value < balance) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.INVALID_ARGUMENT,
|
||||||
|
"new balance is invalid"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
giftCard.balance = balance
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [key, value] of Object.entries(rest)) {
|
||||||
|
giftCard[key] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
return await giftCardRepo.save(giftCard)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes a gift card idempotently
|
||||||
|
* @param giftCardId - id of gift card to delete
|
||||||
|
* @return the result of the delete operation
|
||||||
|
*/
|
||||||
|
async delete(giftCardId: string): Promise<GiftCard | void> {
|
||||||
|
return await this.atomicPhase_(async (manager) => {
|
||||||
|
const giftCardRepo = manager.getCustomRepository(this.giftCardRepository_)
|
||||||
|
|
||||||
|
const giftCard = await giftCardRepo.findOne({ where: { id: giftCardId } })
|
||||||
|
|
||||||
|
if (!giftCard) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return await giftCardRepo.softRemove(giftCard)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default GiftCardService
|
||||||
@@ -46,6 +46,8 @@ export type ExtendedFindConfig<
|
|||||||
relations?: string[]
|
relations?: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type QuerySelector<TEntity> = Selector<TEntity> & { q?: string }
|
||||||
|
|
||||||
export type Selector<TEntity> = {
|
export type Selector<TEntity> = {
|
||||||
[key in keyof TEntity]?:
|
[key in keyof TEntity]?:
|
||||||
| TEntity[key]
|
| TEntity[key]
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
export type CreateGiftCardInput = {
|
||||||
|
value?: number
|
||||||
|
balance?: number
|
||||||
|
ends_at?: Date
|
||||||
|
is_disabled?: boolean
|
||||||
|
region_id: string
|
||||||
|
metadata?: Record<string, unknown>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type UpdateGiftCardInput = {
|
||||||
|
balance?: number
|
||||||
|
ends_at?: Date
|
||||||
|
is_disabled?: boolean
|
||||||
|
region_id?: string
|
||||||
|
metadata?: Record<string, unknown>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CreateGiftCardTransactionInput = {
|
||||||
|
gift_card_id: string
|
||||||
|
order_id: string
|
||||||
|
amount: number
|
||||||
|
created_at: Date
|
||||||
|
}
|
||||||
Reference in new issue
Block a user