Skip to content

Commit

Permalink
feat(medusa,types): add promotion list/get endpoint (#6110)
Browse files Browse the repository at this point in the history
what:

- adds get promotion endpoint (RESOLVES CORE-1677)
- adds list promotions endpoint (RESOLVES CORE-1676)
- uses new API routes
  • Loading branch information
riqwan authored Jan 18, 2024
1 parent 6941627 commit a12c28b
Show file tree
Hide file tree
Showing 20 changed files with 809 additions and 23 deletions.
6 changes: 6 additions & 0 deletions .changeset/little-cobras-doubt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@medusajs/medusa": patch
"@medusajs/types": patch
---

feat(medusa,types): add promotion list and get endpoint
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { IPromotionModuleService } from "@medusajs/types"
import { PromotionType } from "@medusajs/utils"
import path from "path"
import { startBootstrapApp } from "../../../../environment-helpers/bootstrap-app"
import { useApi } from "../../../../environment-helpers/use-api"
import { getContainer } from "../../../../environment-helpers/use-container"
import { initDb, useDb } from "../../../../environment-helpers/use-db"
import adminSeeder from "../../../../helpers/admin-seeder"

const env = { MEDUSA_FF_MEDUSA_V2: true }
const adminHeaders = {
headers: { "x-medusa-access-token": "test_token" },
}

describe("GET /admin/promotions", () => {
let dbConnection
let appContainer
let shutdownServer
let promotionModuleService: IPromotionModuleService

beforeAll(async () => {
const cwd = path.resolve(path.join(__dirname, "..", "..", ".."))
dbConnection = await initDb({ cwd, env } as any)
shutdownServer = await startBootstrapApp({ cwd, env })
appContainer = getContainer()
promotionModuleService = appContainer.resolve(
ModuleRegistrationName.PROMOTION
)
})

afterAll(async () => {
const db = useDb()
await db.shutdown()
await shutdownServer()
})

beforeEach(async () => {
await adminSeeder(dbConnection)
})

afterEach(async () => {
const db = useDb()
await db.teardown()
})

it("should get all promotions and its count", async () => {
await promotionModuleService.create([
{
code: "TEST",
type: PromotionType.STANDARD,
application_method: {
type: "fixed",
target_type: "order",
value: "100",
},
},
])

const api = useApi() as any
const response = await api.get(`/admin/promotions`, adminHeaders)

expect(response.status).toEqual(200)
expect(response.data.count).toEqual(1)
expect(response.data.promotions).toEqual([
expect.objectContaining({
id: expect.any(String),
code: "TEST",
campaign: null,
is_automatic: false,
type: "standard",
created_at: expect.any(String),
updated_at: expect.any(String),
deleted_at: null,
application_method: expect.objectContaining({
id: expect.any(String),
value: 100,
type: "fixed",
target_type: "order",
allocation: null,
created_at: expect.any(String),
updated_at: expect.any(String),
deleted_at: null,
}),
}),
])
})

it("should get all promotions and its count filtered", async () => {
const [createdPromotion] = await promotionModuleService.create([
{
code: "TEST",
type: PromotionType.STANDARD,
application_method: {
type: "fixed",
target_type: "order",
value: "100",
},
},
])

const api = useApi() as any
const response = await api.get(
`/admin/promotions?fields=code,created_at,application_method.id`,
adminHeaders
)

expect(response.status).toEqual(200)
expect(response.data.count).toEqual(1)
expect(response.data.promotions).toEqual([
{
id: expect.any(String),
code: "TEST",
created_at: expect.any(String),
application_method: {
id: expect.any(String),
promotion: expect.any(Object),
},
},
])
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { IPromotionModuleService } from "@medusajs/types"
import { PromotionType } from "@medusajs/utils"
import path from "path"
import { startBootstrapApp } from "../../../../environment-helpers/bootstrap-app"
import { useApi } from "../../../../environment-helpers/use-api"
import { getContainer } from "../../../../environment-helpers/use-container"
import { initDb, useDb } from "../../../../environment-helpers/use-db"
import adminSeeder from "../../../../helpers/admin-seeder"

const env = { MEDUSA_FF_MEDUSA_V2: true }
const adminHeaders = {
headers: { "x-medusa-access-token": "test_token" },
}

describe("GET /admin/promotions", () => {
let dbConnection
let appContainer
let shutdownServer
let promotionModuleService: IPromotionModuleService

beforeAll(async () => {
const cwd = path.resolve(path.join(__dirname, "..", "..", ".."))
dbConnection = await initDb({ cwd, env } as any)
shutdownServer = await startBootstrapApp({ cwd, env })
appContainer = getContainer()
promotionModuleService = appContainer.resolve(
ModuleRegistrationName.PROMOTION
)
})

afterAll(async () => {
const db = useDb()
await db.shutdown()
await shutdownServer()
})

beforeEach(async () => {
await adminSeeder(dbConnection)
})

afterEach(async () => {
const db = useDb()
await db.teardown()
})

it("should throw an error if id does not exist", async () => {
const api = useApi() as any
const { response } = await api
.get(`/admin/promotions/does-not-exist`, adminHeaders)
.catch((e) => e)

expect(response.status).toEqual(404)
expect(response.data.message).toEqual(
"Promotion with id: does-not-exist was not found"
)
})

it("should get the requested promotion", async () => {
const createdPromotion = await promotionModuleService.create({
code: "TEST",
type: PromotionType.STANDARD,
application_method: {
type: "fixed",
target_type: "order",
value: "100",
},
})

const api = useApi() as any
const response = await api.get(
`/admin/promotions/${createdPromotion.id}`,
adminHeaders
)

expect(response.status).toEqual(200)
expect(response.data.promotion).toEqual({
id: expect.any(String),
code: "TEST",
campaign: null,
is_automatic: false,
type: "standard",
created_at: expect.any(String),
updated_at: expect.any(String),
deleted_at: null,
application_method: {
id: expect.any(String),
promotion: expect.any(Object),
value: 100,
type: "fixed",
target_type: "order",
max_quantity: 0,
allocation: null,
created_at: expect.any(String),
updated_at: expect.any(String),
deleted_at: null,
},
})
})

it("should get the requested promotion with filtered fields and relations", async () => {
const createdPromotion = await promotionModuleService.create({
code: "TEST",
type: PromotionType.STANDARD,
application_method: {
type: "fixed",
target_type: "order",
value: "100",
},
})

const api = useApi() as any
const response = await api.get(
`/admin/promotions/${createdPromotion.id}?fields=id,code&expand=`,
adminHeaders
)

expect(response.status).toEqual(200)
expect(response.data.promotion).toEqual({
id: expect.any(String),
code: "TEST",
})
})
})
5 changes: 5 additions & 0 deletions integration-tests/plugins/medusa-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,5 +66,10 @@ module.exports = {
resources: "shared",
resolve: "@medusajs/pricing",
},
[Modules.PROMOTION]: {
scope: "internal",
resources: "shared",
resolve: "@medusajs/promotion",
},
},
}
3 changes: 3 additions & 0 deletions integration-tests/plugins/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
"@medusajs/modules-sdk": "workspace:^",
"@medusajs/pricing": "workspace:^",
"@medusajs/product": "workspace:^",
"@medusajs/promotion": "workspace:^",
"@medusajs/utils": "workspace:^",
"faker": "^5.5.3",
"medusa-fulfillment-webshipper": "workspace:*",
"medusa-interfaces": "workspace:*",
Expand All @@ -27,6 +29,7 @@
"@babel/cli": "^7.12.10",
"@babel/core": "^7.12.10",
"@babel/node": "^7.12.10",
"@medusajs/types": "workspace:^",
"babel-preset-medusa-package": "*",
"jest": "^26.6.3",
"jest-environment-node": "26.6.2"
Expand Down
16 changes: 16 additions & 0 deletions packages/medusa/src/api-v2/admin/promotions/[id]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { IPromotionModuleService } from "@medusajs/types"
import { MedusaRequest, MedusaResponse } from "../../../../types/routing"

export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
const promotionModuleService: IPromotionModuleService = req.scope.resolve(
ModuleRegistrationName.PROMOTION
)

const promotion = await promotionModuleService.retrieve(req.params.id, {
select: req.retrieveConfig.select,
relations: req.retrieveConfig.relations,
})

res.status(200).json({ promotion })
}
35 changes: 35 additions & 0 deletions packages/medusa/src/api-v2/admin/promotions/middlewares.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { MedusaV2Flag } from "@medusajs/utils"
import { isFeatureFlagEnabled, transformQuery } from "../../../api/middlewares"
import { MiddlewareRoute } from "../../../loaders/helpers/routing/types"
import * as QueryConfig from "./query-config"
import {
AdminGetPromotionsParams,
AdminGetPromotionsPromotionParams,
} from "./validators"

export const adminPromotionRoutesMiddlewares: MiddlewareRoute[] = [
{
matcher: "/admin/promotions*",
middlewares: [isFeatureFlagEnabled(MedusaV2Flag.key)],
},
{
method: ["GET"],
matcher: "/admin/promotions",
middlewares: [
transformQuery(
AdminGetPromotionsParams,
QueryConfig.listTransformQueryConfig
),
],
},
{
method: ["GET"],
matcher: "/admin/promotions/:id",
middlewares: [
transformQuery(
AdminGetPromotionsPromotionParams,
QueryConfig.retrieveTransformQueryConfig
),
],
},
]
26 changes: 26 additions & 0 deletions packages/medusa/src/api-v2/admin/promotions/query-config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
export const defaultAdminPromotionRelations = ["campaign", "application_method"]
export const allowedAdminPromotionRelations = [
...defaultAdminPromotionRelations,
]
export const defaultAdminPromotionFields = [
"id",
"code",
"campaign",
"is_automatic",
"type",
"created_at",
"updated_at",
"deleted_at",
]

export const retrieveTransformQueryConfig = {
defaultFields: defaultAdminPromotionFields,
defaultRelations: defaultAdminPromotionRelations,
allowedRelations: allowedAdminPromotionRelations,
isList: false,
}

export const listTransformQueryConfig = {
...retrieveTransformQueryConfig,
isList: true,
}
23 changes: 23 additions & 0 deletions packages/medusa/src/api-v2/admin/promotions/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { IPromotionModuleService } from "@medusajs/types"
import { MedusaRequest, MedusaResponse } from "../../../types/routing"

export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
const promotionModuleService: IPromotionModuleService = req.scope.resolve(
ModuleRegistrationName.PROMOTION
)

const [promotions, count] = await promotionModuleService.listAndCount(
req.filterableFields,
req.listConfig
)

const { limit, offset } = req.validatedQuery

res.json({
count,
promotions,
offset,
limit,
})
}
Loading

0 comments on commit a12c28b

Please sign in to comment.