Feat/improve overview of gift cards (#297)
* modified list-gift-card * added search with relations to gift cards to allow for proper lookup * removed new line * created skeleton for testing * began impl. tests * added more test * finished tests * reverted failed changes * updated tests to accomodate re-introduced additions * removed VSCode settings file * updated according to comments * corrected use of findOneWithRelations for gift-card * corrected parsing to be postgresql instead of an if-statement for gift-card * changed bits and pieces
This commit is contained in:
1 parent
0c96813e2f
commit
92caff845e
6 files changed
+385
-8
No files matched your search
@@ -33,7 +33,10 @@ export const defaultFields = [
|
|||||||
"metadata",
|
"metadata",
|
||||||
]
|
]
|
||||||
|
|
||||||
export const defaultRelations = ["region"]
|
export const defaultRelations = [
|
||||||
|
"region",
|
||||||
|
"order",
|
||||||
|
]
|
||||||
|
|
||||||
export const allowedFields = [
|
export const allowedFields = [
|
||||||
"id",
|
"id",
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { MedusaError, Validator } from "medusa-core-utils"
|
||||||
import { defaultFields, defaultRelations } from "./"
|
import { defaultFields, defaultRelations } from "./"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -21,14 +22,23 @@ import { defaultFields, defaultRelations } from "./"
|
|||||||
*/
|
*/
|
||||||
export default async (req, res) => {
|
export default async (req, res) => {
|
||||||
try {
|
try {
|
||||||
|
const limit = parseInt(req.query.limit) || 50
|
||||||
|
const offset = parseInt(req.query.offset) || 0
|
||||||
|
|
||||||
const selector = {}
|
const selector = {}
|
||||||
|
|
||||||
|
if ("q" in req.query) {
|
||||||
|
selector.q = req.query.q
|
||||||
|
}
|
||||||
|
|
||||||
const giftCardService = req.scope.resolve("giftCardService")
|
const giftCardService = req.scope.resolve("giftCardService")
|
||||||
|
|
||||||
const giftCards = await giftCardService.list(selector, {
|
const giftCards = await giftCardService.list(selector, {
|
||||||
select: defaultFields,
|
select: defaultFields,
|
||||||
relations: defaultRelations,
|
relations: defaultRelations,
|
||||||
order: { created_at: "DESC" },
|
order: { created_at: "DESC" },
|
||||||
|
limit: limit,
|
||||||
|
skip: offset,
|
||||||
})
|
})
|
||||||
|
|
||||||
res.status(200).json({ gift_cards: giftCards })
|
res.status(200).json({ gift_cards: giftCards })
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { MedusaError, Validator } from "medusa-core-utils"
|
import { MedusaError, Validator } from "medusa-core-utils"
|
||||||
|
import { defaultFields, defaultRelations } from "./"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @oas [post] /gift-cards/{id}
|
* @oas [post] /gift-cards/{id}
|
||||||
|
|||||||
@@ -1,5 +1,61 @@
|
|||||||
import { EntityRepository, Repository } from "typeorm"
|
import { flatten, groupBy, map, merge } from "lodash"
|
||||||
|
import { EntityRepository, FindManyOptions, Repository } from "typeorm"
|
||||||
import { GiftCard } from "../models/gift-card"
|
import { GiftCard } from "../models/gift-card"
|
||||||
|
|
||||||
@EntityRepository(GiftCard)
|
@EntityRepository(GiftCard)
|
||||||
export class GiftCardRepository extends Repository<GiftCard> {}
|
export class GiftCardRepository extends Repository<GiftCard> {
|
||||||
|
public async findWithRelations(
|
||||||
|
relations: Array<keyof GiftCard> = [],
|
||||||
|
idsOrOptionsWithoutRelations: Omit<
|
||||||
|
FindManyOptions<GiftCard>,
|
||||||
|
"relations"
|
||||||
|
> = {}
|
||||||
|
): Promise<GiftCard[]> {
|
||||||
|
let entities
|
||||||
|
if (Array.isArray(idsOrOptionsWithoutRelations)) {
|
||||||
|
entities = await this.findByIds(idsOrOptionsWithoutRelations)
|
||||||
|
} else {
|
||||||
|
entities = await this.find(idsOrOptionsWithoutRelations)
|
||||||
|
}
|
||||||
|
const entitiesIds = entities.map(({ id }) => id)
|
||||||
|
|
||||||
|
const groupedRelations = {}
|
||||||
|
for (const rel of relations) {
|
||||||
|
const [topLevel] = rel.split(".")
|
||||||
|
if (groupedRelations[topLevel]) {
|
||||||
|
groupedRelations[topLevel].push(rel)
|
||||||
|
} else {
|
||||||
|
groupedRelations[topLevel] = [rel]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const entitiesIdsWithRelations = await Promise.all(
|
||||||
|
Object.entries(groupedRelations).map(([_, rels]) => {
|
||||||
|
return this.findByIds(entitiesIds, {
|
||||||
|
select: ["id"],
|
||||||
|
relations: rels as string[],
|
||||||
|
})
|
||||||
|
})
|
||||||
|
).then(flatten)
|
||||||
|
const entitiesAndRelations = entitiesIdsWithRelations.concat(entities)
|
||||||
|
|
||||||
|
const entitiesAndRelationsById = groupBy(entitiesAndRelations, "id")
|
||||||
|
return map(entitiesAndRelationsById, entityAndRelations =>
|
||||||
|
merge({}, ...entityAndRelations)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
public async findOneWithRelations(
|
||||||
|
relations: Array<keyof GiftCard> = [],
|
||||||
|
optionsWithoutRelations: Omit<FindManyOptions<GiftCard>, "relations"> = {}
|
||||||
|
): Promise<GiftCard> {
|
||||||
|
// Limit 1
|
||||||
|
optionsWithoutRelations.take = 1
|
||||||
|
|
||||||
|
const result = await this.findWithRelations(
|
||||||
|
relations,
|
||||||
|
optionsWithoutRelations
|
||||||
|
)
|
||||||
|
return result[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,256 @@
|
|||||||
|
import { IdMap, MockManager, MockRepository } from "medusa-test-utils"
|
||||||
|
|
||||||
|
import GiftCardService from "../gift-card"
|
||||||
|
|
||||||
|
describe("GiftCardService", () => {
|
||||||
|
const eventBusService = {
|
||||||
|
emit: jest.fn(),
|
||||||
|
withTransaction: function() {
|
||||||
|
return this
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("create", () => {
|
||||||
|
const giftCardRepo = MockRepository({
|
||||||
|
create: s => {
|
||||||
|
return Promise.resolve(s)
|
||||||
|
},
|
||||||
|
save: s => {
|
||||||
|
return Promise.resolve(s)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const regionService = {
|
||||||
|
withTransaction: function() {
|
||||||
|
return this
|
||||||
|
},
|
||||||
|
retrieve: () => {
|
||||||
|
return Promise.resolve({
|
||||||
|
id: IdMap.getId("region-id"),
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const giftCardService = new GiftCardService({
|
||||||
|
manager: MockManager,
|
||||||
|
giftCardRepository: giftCardRepo,
|
||||||
|
regionService: regionService,
|
||||||
|
eventBusService: eventBusService,
|
||||||
|
})
|
||||||
|
|
||||||
|
const giftCard = {
|
||||||
|
region_id: IdMap.getId("region-id"),
|
||||||
|
order_id: IdMap.getId("order-id"),
|
||||||
|
is_disabled: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("correctly creates a giftcard", async () => {
|
||||||
|
await giftCardService.create(giftCard)
|
||||||
|
|
||||||
|
expect(giftCardRepo.create).toHaveBeenCalledTimes(1)
|
||||||
|
expect(giftCardRepo.create).toHaveBeenCalledWith({
|
||||||
|
region_id: IdMap.getId("region-id"),
|
||||||
|
order_id: IdMap.getId("order-id"),
|
||||||
|
is_disabled: true,
|
||||||
|
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", () => {
|
||||||
|
const giftCardRepo = MockRepository({
|
||||||
|
findOneWithRelations: () => {
|
||||||
|
return Promise.resolve({})
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
const giftCardService = new GiftCardService({
|
||||||
|
manager: MockManager,
|
||||||
|
giftCardRepository: giftCardRepo,
|
||||||
|
})
|
||||||
|
|
||||||
|
it("it calls order model functions", async () => {
|
||||||
|
await giftCardService.retrieve(IdMap.getId("gift-card"), {
|
||||||
|
relations: ["region"],
|
||||||
|
select: ["id"],
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(giftCardRepo.findOneWithRelations).toHaveBeenCalledTimes(1)
|
||||||
|
expect(giftCardRepo.findOneWithRelations).toHaveBeenCalledWith(
|
||||||
|
["region"],
|
||||||
|
{
|
||||||
|
where: {
|
||||||
|
id: IdMap.getId("gift-card"),
|
||||||
|
},
|
||||||
|
select: ["id"],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("retrieveByCode", () => {
|
||||||
|
const giftCardRepo = MockRepository({
|
||||||
|
findOneWithRelations: () => {
|
||||||
|
return Promise.resolve({})
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
const giftCardService = new GiftCardService({
|
||||||
|
manager: MockManager,
|
||||||
|
giftCardRepository: giftCardRepo,
|
||||||
|
})
|
||||||
|
|
||||||
|
it("it calls order model functions", async () => {
|
||||||
|
await giftCardService.retrieveByCode("1234-1234-1234-1234", {
|
||||||
|
relations: ["region"],
|
||||||
|
select: ["id"],
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(giftCardRepo.findOneWithRelations).toHaveBeenCalledTimes(1)
|
||||||
|
expect(giftCardRepo.findOneWithRelations).toHaveBeenCalledWith(
|
||||||
|
["region"],
|
||||||
|
{
|
||||||
|
where: {
|
||||||
|
code: "1234-1234-1234-1234",
|
||||||
|
},
|
||||||
|
select: ["id"],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("update", () => {
|
||||||
|
const giftCard = {
|
||||||
|
region_id: IdMap.getId("region-id"),
|
||||||
|
order_id: IdMap.getId("order-id"),
|
||||||
|
is_disabled: true,
|
||||||
|
value: 5000,
|
||||||
|
}
|
||||||
|
|
||||||
|
const giftCardRepo = MockRepository({
|
||||||
|
findOneWithRelations: s => {
|
||||||
|
return Promise.resolve(giftCard)
|
||||||
|
},
|
||||||
|
save: s => {
|
||||||
|
return Promise.resolve(s)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const regionService = {
|
||||||
|
withTransaction: function() {
|
||||||
|
return this
|
||||||
|
},
|
||||||
|
retrieve: () => {
|
||||||
|
return Promise.resolve({
|
||||||
|
id: IdMap.getId("other-region"),
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const giftCardService = new GiftCardService({
|
||||||
|
manager: MockManager,
|
||||||
|
giftCardRepository: giftCardRepo,
|
||||||
|
regionService: regionService,
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("calls order model functions", async () => {
|
||||||
|
await giftCardService.update(IdMap.getId("giftcard-id"), {
|
||||||
|
is_disabled: false,
|
||||||
|
region_id: IdMap.getId("other-region"),
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(giftCardRepo.save).toHaveBeenCalledTimes(1)
|
||||||
|
expect(giftCardRepo.save).toHaveBeenCalledWith({
|
||||||
|
region_id: IdMap.getId("other-region"),
|
||||||
|
order_id: IdMap.getId("order-id"),
|
||||||
|
is_disabled: false,
|
||||||
|
value: 5000,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it.each([[-100], [6000]])(
|
||||||
|
"fails to update balance with illegal input '%s'",
|
||||||
|
async input => {
|
||||||
|
await expect(
|
||||||
|
giftCardService.update(IdMap.getId("giftcard-id"), {
|
||||||
|
balance: input,
|
||||||
|
})
|
||||||
|
).rejects.toThrow("new balance is invalid")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("delete", () => {
|
||||||
|
const giftCard = {
|
||||||
|
region_id: IdMap.getId("region-id"),
|
||||||
|
order_id: IdMap.getId("order-id"),
|
||||||
|
}
|
||||||
|
|
||||||
|
const giftCardRepo = MockRepository({
|
||||||
|
findOne: s => {
|
||||||
|
switch (s.where.id) {
|
||||||
|
case IdMap.getId("gift-card"):
|
||||||
|
return Promise.resolve(giftCard)
|
||||||
|
default:
|
||||||
|
return Promise.resolve()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
softRemove: s => {
|
||||||
|
return Promise.resolve()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const giftCardService = new GiftCardService({
|
||||||
|
manager: MockManager,
|
||||||
|
giftCardRepository: giftCardRepo,
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("successfully deletes existing gift-card", async () => {
|
||||||
|
await giftCardService.delete(IdMap.getId("gift-card"))
|
||||||
|
|
||||||
|
expect(giftCardRepo.softRemove).toHaveBeenCalledTimes(1)
|
||||||
|
expect(giftCardRepo.softRemove).toHaveBeenCalledWith({
|
||||||
|
region_id: IdMap.getId("region-id"),
|
||||||
|
order_id: IdMap.getId("order-id"),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns if no gift-card found", async () => {
|
||||||
|
await giftCardService.delete(IdMap.getId("other"))
|
||||||
|
|
||||||
|
expect(giftCardRepo.softRemove).toHaveBeenCalledTimes(0)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
import _ from "lodash"
|
import _ from "lodash"
|
||||||
import randomize from "randomatic"
|
import randomize from "randomatic"
|
||||||
import { BaseService } from "medusa-interfaces"
|
import { BaseService } from "medusa-interfaces"
|
||||||
import { Validator, MedusaError } from "medusa-core-utils"
|
import { Brackets } from "typeorm"
|
||||||
|
import { MedusaError } from "medusa-core-utils"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides layer to manipulate gift cards.
|
* Provides layer to manipulate gift cards.
|
||||||
@@ -72,6 +73,7 @@ class GiftCardService extends BaseService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Object} selector - the query object for find
|
* @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
|
* @return {Promise} the result of the find operation
|
||||||
*/
|
*/
|
||||||
async list(selector = {}, config = { relations: [], skip: 0, take: 10 }) {
|
async list(selector = {}, config = { relations: [], skip: 0, take: 10 }) {
|
||||||
@@ -79,8 +81,41 @@ class GiftCardService extends BaseService {
|
|||||||
this.giftCardRepository_
|
this.giftCardRepository_
|
||||||
)
|
)
|
||||||
|
|
||||||
|
let q
|
||||||
|
if ("q" in selector) {
|
||||||
|
q = selector.q
|
||||||
|
delete selector.q
|
||||||
|
}
|
||||||
|
|
||||||
const query = this.buildQuery_(selector, config)
|
const query = this.buildQuery_(selector, config)
|
||||||
return giftCardRepo.find(query)
|
|
||||||
|
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) {
|
async createTransaction(data) {
|
||||||
@@ -156,7 +191,10 @@ class GiftCardService extends BaseService {
|
|||||||
query.relations = config.relations
|
query.relations = config.relations
|
||||||
}
|
}
|
||||||
|
|
||||||
const giftCard = await giftCardRepo.findOne(query)
|
const rels = query.relations
|
||||||
|
delete query.relations
|
||||||
|
|
||||||
|
const giftCard = await giftCardRepo.findOneWithRelations(rels, query)
|
||||||
|
|
||||||
if (!giftCard) {
|
if (!giftCard) {
|
||||||
throw new MedusaError(
|
throw new MedusaError(
|
||||||
@@ -185,7 +223,10 @@ class GiftCardService extends BaseService {
|
|||||||
query.relations = config.relations
|
query.relations = config.relations
|
||||||
}
|
}
|
||||||
|
|
||||||
const giftCard = await giftCardRepo.findOne(query)
|
const rels = query.relations
|
||||||
|
delete query.relations
|
||||||
|
|
||||||
|
const giftCard = await giftCardRepo.findOneWithRelations(rels, query)
|
||||||
|
|
||||||
if (!giftCard) {
|
if (!giftCard) {
|
||||||
throw new MedusaError(
|
throw new MedusaError(
|
||||||
@@ -209,7 +250,7 @@ class GiftCardService extends BaseService {
|
|||||||
|
|
||||||
const giftCard = await this.retrieve(giftCardId)
|
const giftCard = await this.retrieve(giftCardId)
|
||||||
|
|
||||||
const { region_id, metadata, ...rest } = update
|
const { region_id, metadata, balance, ...rest } = update
|
||||||
|
|
||||||
if (region_id && region_id !== giftCard.region_id) {
|
if (region_id && region_id !== giftCard.region_id) {
|
||||||
const region = await this.regionService_.retrieve(region_id)
|
const region = await this.regionService_.retrieve(region_id)
|
||||||
@@ -220,6 +261,16 @@ class GiftCardService extends BaseService {
|
|||||||
giftCard.metadata = await this.setMetadata_(giftCard.id, metadata)
|
giftCard.metadata = await this.setMetadata_(giftCard.id, metadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (balance) {
|
||||||
|
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)) {
|
for (const [key, value] of Object.entries(rest)) {
|
||||||
giftCard[key] = value
|
giftCard[key] = value
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user