feat: Typescript for API layer (#817)

Co-authored-by: Philip Korsholm <88927411+pKorsholm@users.noreply.github.com>
Co-authored-by: Zakaria El Asri <33696020+zakariaelas@users.noreply.github.com>
Co-authored-by: Kasper Fabricius Kristensen <45367945+kasperkristensen@users.noreply.github.com>
Co-authored-by: Philip Korsholm <philip.korsholm@hotmail.com>
Co-authored-by: Sebastian Rindom <seb@medusa-commerce.com>
This commit is contained in:
Oliver Windall Juhl
2021-11-18 15:19:17 +01:00
committed by GitHub
co-authored by Philip Korsholm Zakaria El Asri Kasper Fabricius Kristensen Philip Korsholm Sebastian Rindom
parent 55e200bf68
commit 373532ecbc
413 changed files with 20957 additions and 10349 deletions
+15
View File
@@ -24,6 +24,21 @@
/packages/medusa/src/api/routes/store/return-reasons
/packages/medusa/src/api/routes/store/returns
# JS Client
/packages/medusa-js/src/resources/auth.ts
/packages/medusa-js/src/resources/gift-cards.ts
/packages/medusa-js/src/resources/line-items.ts
/packages/medusa-js/src/resources/payment-methods.ts
/packages/medusa-js/src/resources/product-variants.ts
/packages/medusa-js/src/resources/regions.ts
/packages/medusa-js/src/resources/return-reasons.ts
/packages/medusa-js/src/resources/returns.ts
/packages/medusa-js/src/resources/shipping-options.ts
/packages/medusa-js/src/resources/swaps.ts
/packages/medusa-js/src/resources/collections.ts
/packages/medusa-js/src/types/
/packages/medusa-js/src/error.ts
# END OF FILES TODO
+14 -1
View File
@@ -23,18 +23,31 @@ module.exports = {
node: true,
jest: true,
},
ignorePatterns: [`**/models`, `**/repositories`],
overrides: [
{
files: [`*.ts`],
parser: `@typescript-eslint/parser`,
plugins: [`@typescript-eslint/eslint-plugin`],
extends: [`plugin:@typescript-eslint/recommended`],
rules: {
"@typescript-eslint/explicit-function-return-type": ["error"],
"@typescript-eslint/no-non-null-assertion": ["off"],
},
},
{
files: ["**/api/**/*.js"],
files: ["**/api/**/*.js", "**/api/**/*.ts"],
rules: {
"valid-jsdoc": ["off"],
},
},
{
files: ["**/api/**/*.ts"],
rules: {
"valid-jsdoc": ["off"],
"@typescript-eslint/explicit-function-return-type": ["off"],
"@typescript-eslint/no-var-requires": ["off"],
},
},
],
}
@@ -0,0 +1,204 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`/admin/collections /admin/collections creates a collection 1`] = `
Object {
"collection": Object {
"created_at": Any<String>,
"deleted_at": null,
"handle": "test-handle-creation",
"id": StringMatching /\\^pcol_\\*/,
"metadata": null,
"title": "test collection creation",
"updated_at": Any<String>,
},
}
`;
exports[`/admin/collections /admin/collections lists collections 1`] = `
Object {
"collections": Array [
Object {
"handle": "test-collection",
"id": "test-collection",
"products": Array [
Object {
"collection_id": "test-collection",
"created_at": Any<String>,
"deleted_at": null,
"description": "test-product-description",
"discountable": true,
"handle": "test-product",
"height": null,
"hs_code": null,
"id": "test-product",
"is_giftcard": false,
"length": null,
"material": null,
"metadata": null,
"mid_code": null,
"origin_country": null,
"profile_id": StringMatching /\\^sp_\\*/,
"status": "draft",
"subtitle": null,
"thumbnail": null,
"title": "Test product",
"type_id": "test-type",
"updated_at": Any<String>,
"weight": null,
"width": null,
},
Object {
"collection_id": "test-collection",
"created_at": Any<String>,
"deleted_at": null,
"description": "test-product-description1",
"discountable": true,
"handle": "test-product1",
"height": null,
"hs_code": null,
"id": "test-product1",
"is_giftcard": false,
"length": null,
"material": null,
"metadata": null,
"mid_code": null,
"origin_country": null,
"profile_id": StringMatching /\\^sp_\\*/,
"status": "draft",
"subtitle": null,
"thumbnail": null,
"title": "Test product1",
"type_id": "test-type",
"updated_at": Any<String>,
"weight": null,
"width": null,
},
],
"title": "Test collection",
},
Object {
"handle": "test-collection1",
"id": "test-collection1",
"products": Array [
Object {
"collection_id": "test-collection1",
"created_at": Any<String>,
"deleted_at": null,
"description": "test-product-description",
"discountable": true,
"handle": "test-product_filtering_1",
"height": null,
"hs_code": null,
"id": "test-product_filtering_1",
"is_giftcard": false,
"length": null,
"material": null,
"metadata": null,
"mid_code": null,
"origin_country": null,
"profile_id": StringMatching /\\^sp_\\*/,
"status": "proposed",
"subtitle": null,
"thumbnail": null,
"title": "Test product filtering 1",
"type_id": "test-type",
"updated_at": Any<String>,
"weight": null,
"width": null,
},
Object {
"collection_id": "test-collection1",
"created_at": Any<String>,
"deleted_at": null,
"description": "test-product-description",
"discountable": true,
"handle": "test-product_filtering_3",
"height": null,
"hs_code": null,
"id": "test-product_filtering_3",
"is_giftcard": false,
"length": null,
"material": null,
"metadata": null,
"mid_code": null,
"origin_country": null,
"profile_id": StringMatching /\\^sp_\\*/,
"status": "draft",
"subtitle": null,
"thumbnail": null,
"title": "Test product filtering 3",
"type_id": "test-type",
"updated_at": Any<String>,
"weight": null,
"width": null,
},
],
"title": "Test collection 1",
},
Object {
"handle": "test-collection2",
"id": "test-collection2",
"products": Array [
Object {
"collection_id": "test-collection2",
"created_at": Any<String>,
"deleted_at": null,
"description": "test-product-description",
"discountable": true,
"handle": "test-product_filtering_2",
"height": null,
"hs_code": null,
"id": "test-product_filtering_2",
"is_giftcard": false,
"length": null,
"material": null,
"metadata": null,
"mid_code": null,
"origin_country": null,
"profile_id": StringMatching /\\^sp_\\*/,
"status": "published",
"subtitle": null,
"thumbnail": null,
"title": "Test product filtering 2",
"type_id": "test-type",
"updated_at": Any<String>,
"weight": null,
"width": null,
},
],
"title": "Test collection 2",
},
],
"count": 3,
"limit": 10,
"offset": 0,
}
`;
exports[`/admin/collections /admin/collections/:id gets collection 1`] = `
Object {
"collection": Object {
"created_at": Any<String>,
"deleted_at": null,
"handle": "test-collection",
"id": "test-collection",
"metadata": null,
"title": "Test collection",
"updated_at": Any<String>,
},
}
`;
exports[`/admin/collections /admin/collections/:id updates a collection 1`] = `
Object {
"collection": Object {
"created_at": Any<String>,
"deleted_at": null,
"handle": "test-handle-creation",
"id": StringMatching /\\^pcol_\\*/,
"metadata": null,
"title": "test collection creation",
"updated_at": Any<String>,
},
}
`;
@@ -479,7 +479,7 @@ Array [
]
`;
exports[`/admin/products GET /admin/products returns a list of products with giftcard in list 1`] = `
exports[`/admin/products GET /admin/products returns a list of products with only giftcard in list 1`] = `
Array [
Object {
"collection": null,
@@ -0,0 +1,220 @@
const path = require("path")
const setupServer = require("../../../helpers/setup-server")
const { useApi } = require("../../../helpers/use-api")
const { initDb, useDb } = require("../../../helpers/use-db")
const productSeeder = require("../../helpers/product-seeder")
const adminSeeder = require("../../helpers/admin-seeder")
jest.setTimeout(30000)
describe("/admin/collections", () => {
let medusaProcess
let dbConnection
beforeAll(async () => {
const cwd = path.resolve(path.join(__dirname, "..", ".."))
dbConnection = await initDb({ cwd })
medusaProcess = await setupServer({ cwd })
})
afterAll(async () => {
const db = useDb()
await db.shutdown()
medusaProcess.kill()
})
describe("/admin/collections/:id", () => {
beforeEach(async () => {
try {
await adminSeeder(dbConnection)
await productSeeder(dbConnection)
} catch (err) {
console.log(err)
throw err
}
})
afterEach(async () => {
const db = useDb()
await db.teardown()
})
it("updates a collection", async () => {
const api = useApi()
const creationResponse = await api.post(
"/admin/collections",
{
title: "test",
},
{ headers: { Authorization: "Bearer test_token" } }
)
const response = await api.post(
`/admin/collections/${creationResponse.data.collection.id}`,
{
title: "test collection creation",
handle: "test-handle-creation",
},
{ headers: { Authorization: "Bearer test_token" } }
)
expect(response.status).toEqual(200)
expect(response.data).toMatchSnapshot({
collection: {
id: expect.stringMatching(/^pcol_*/),
title: "test collection creation",
handle: "test-handle-creation",
created_at: expect.any(String),
updated_at: expect.any(String),
},
})
})
it("deletes a collection", async () => {
const api = useApi()
const creationResponse = await api.post(
"/admin/collections",
{
title: "test",
},
{ headers: { Authorization: "Bearer test_token" } }
)
const response = await api.delete(
`/admin/collections/${creationResponse.data.collection.id}`,
{ headers: { Authorization: "Bearer test_token" } }
)
expect(response.status).toEqual(200)
expect(response.data).toEqual({
id: creationResponse.data.collection.id,
object: "product-collection",
deleted: true,
})
})
it("gets collection", async () => {
const api = useApi()
const response = await api.get("/admin/collections/test-collection", {
headers: { Authorization: "Bearer test_token" },
})
expect(response.data).toMatchSnapshot({
collection: {
id: "test-collection",
created_at: expect.any(String),
updated_at: expect.any(String),
},
})
})
})
describe("/admin/collections", () => {
beforeEach(async () => {
try {
await adminSeeder(dbConnection)
await productSeeder(dbConnection)
} catch (err) {
console.log(err)
throw err
}
})
afterEach(async () => {
const db = useDb()
await db.teardown()
})
it("creates a collection", async () => {
const api = useApi()
const response = await api.post(
"/admin/collections",
{
title: "test collection creation",
handle: "test-handle-creation",
},
{ headers: { Authorization: "Bearer test_token" } }
)
expect(response.status).toEqual(200)
expect(response.data).toMatchSnapshot({
collection: {
id: expect.stringMatching(/^pcol_*/),
title: "test collection creation",
handle: "test-handle-creation",
created_at: expect.any(String),
updated_at: expect.any(String),
},
})
})
it("lists collections", async () => {
const api = useApi()
const response = await api.get("/admin/collections", {
headers: { Authorization: "Bearer test_token" },
})
expect(response.data).toMatchSnapshot({
collections: [
{
id: "test-collection",
handle: "test-collection",
title: "Test collection",
products: [
{
collection_id: "test-collection",
created_at: expect.any(String),
updated_at: expect.any(String),
profile_id: expect.stringMatching(/^sp_*/),
},
{
collection_id: "test-collection",
created_at: expect.any(String),
updated_at: expect.any(String),
profile_id: expect.stringMatching(/^sp_*/),
},
],
},
{
id: "test-collection1",
handle: "test-collection1",
title: "Test collection 1",
products: [
{
collection_id: "test-collection1",
created_at: expect.any(String),
updated_at: expect.any(String),
profile_id: expect.stringMatching(/^sp_*/),
},
{
collection_id: "test-collection1",
created_at: expect.any(String),
updated_at: expect.any(String),
profile_id: expect.stringMatching(/^sp_*/),
},
],
},
{
id: "test-collection2",
handle: "test-collection2",
title: "Test collection 2",
products: [
{
collection_id: "test-collection2",
created_at: expect.any(String),
updated_at: expect.any(String),
profile_id: expect.stringMatching(/^sp_*/),
},
],
},
],
count: 3,
})
})
})
})
@@ -1,4 +1,3 @@
const { dropDatabase } = require("pg-god")
const path = require("path")
const setupServer = require("../../../helpers/setup-server")
+153 -49
View File
@@ -29,32 +29,42 @@ describe("/admin/discounts", () => {
describe("GET /admin/discounts", () => {
beforeEach(async () => {
const manager = dbConnection.manager
try {
await adminSeeder(dbConnection)
await manager.insert(DiscountRule, {
id: "test-discount-rule",
description: "Test discount rule",
type: "percentage",
value: 10,
allocation: "total",
})
await manager.insert(Discount, {
id: "test-discount",
code: "TESTING",
rule_id: "test-discount-rule",
is_dynamic: false,
is_disabled: false,
})
await manager.insert(Discount, {
id: "messi-discount",
code: "BARCA100",
rule_id: "test-discount-rule",
is_dynamic: false,
is_disabled: false,
})
} catch (err) {
throw err
}
await adminSeeder(dbConnection)
await manager.insert(DiscountRule, {
id: "test-discount-rule",
description: "Test discount rule",
type: "percentage",
value: 10,
allocation: "total",
})
await manager.insert(Discount, {
id: "test-discount",
code: "TESTING",
rule_id: "test-discount-rule",
is_dynamic: false,
is_disabled: false,
})
await manager.insert(Discount, {
id: "messi-discount",
code: "BARCA100",
rule_id: "test-discount-rule",
is_dynamic: false,
is_disabled: false,
})
await manager.insert(Discount, {
id: "dynamic-discount",
code: "Dyn100",
rule_id: "test-discount-rule",
is_dynamic: true,
is_disabled: false,
})
await manager.insert(Discount, {
id: "disabled-discount",
code: "Dis100",
rule_id: "test-discount-rule",
is_dynamic: false,
is_disabled: true,
})
})
afterEach(async () => {
@@ -85,6 +95,50 @@ describe("/admin/discounts", () => {
])
)
})
it("lists dynamic discounts ", async () => {
const api = useApi()
const response = await api
.get("/admin/discounts?is_dynamic=true", {
headers: {
Authorization: "Bearer test_token",
},
})
.catch((err) => {
console.log(err)
})
expect(response.status).toEqual(200)
expect(response.data.count).toEqual(1)
expect(response.data.discounts).toEqual([
expect.objectContaining({
id: "dynamic-discount",
code: "Dyn100",
}),
])
})
it("lists disabled discounts ", async () => {
const api = useApi()
const response = await api
.get("/admin/discounts?is_disabled=true", {
headers: {
Authorization: "Bearer test_token",
},
})
.catch((err) => {
console.log(err)
})
expect(response.status).toEqual(200)
expect(response.data.count).toEqual(1)
expect(response.data.discounts).toEqual([
expect.objectContaining({
id: "disabled-discount",
code: "Dis100",
}),
])
})
})
describe("POST /admin/discounts", () => {
@@ -103,6 +157,60 @@ describe("/admin/discounts", () => {
await db.teardown()
})
it("creates a discount with a rule", async () => {
const api = useApi()
const response = await api
.post(
"/admin/discounts",
{
code: "HELLOWORLD",
rule: {
description: "test",
type: "percentage",
value: 10,
allocation: "total",
},
usage_limit: 10,
},
{
headers: {
Authorization: "Bearer test_token",
},
}
)
.catch((err) => {
console.log(err)
})
expect(response.status).toEqual(200)
expect(response.data.discount).toEqual(
expect.objectContaining({
code: "HELLOWORLD",
usage_limit: 10,
})
)
const test = await api.get(
`/admin/discounts/${response.data.discount.id}`,
{ headers: { Authorization: "Bearer test_token" } }
)
expect(test.status).toEqual(200)
expect(test.data.discount).toEqual(
expect.objectContaining({
code: "HELLOWORLD",
usage_limit: 10,
rule: expect.objectContaining({
value: 10,
type: "percentage",
description: "test",
allocation: "total",
}),
})
)
})
it("creates a discount and updates it", async () => {
const api = useApi()
@@ -591,11 +699,11 @@ describe("/admin/discounts", () => {
)
.catch((err) => {
expect(err.response.status).toEqual(400)
expect(err.response.data.message).toEqual([
expect(err.response.data).toEqual(
expect.objectContaining({
message: `"ends_at" must be greater than "ref:starts_at"`,
}),
])
message: `"ends_at" must be greater than "starts_at"`,
})
)
})
})
})
@@ -604,25 +712,21 @@ describe("/admin/discounts", () => {
let manager
beforeEach(async () => {
manager = dbConnection.manager
try {
await adminSeeder(dbConnection)
await manager.insert(DiscountRule, {
id: "test-discount-rule",
description: "Test discount rule",
type: "percentage",
value: 10,
allocation: "total",
})
await manager.insert(Discount, {
id: "test-discount",
code: "TESTING",
rule_id: "test-discount-rule",
is_dynamic: false,
is_disabled: false,
})
} catch (err) {
throw err
}
await adminSeeder(dbConnection)
await manager.insert(DiscountRule, {
id: "test-discount-rule",
description: "Test discount rule",
type: "percentage",
value: 10,
allocation: "total",
})
await manager.insert(Discount, {
id: "test-discount",
code: "TESTING",
rule_id: "test-discount-rule",
is_dynamic: false,
is_disabled: false,
})
})
afterEach(async () => {
@@ -158,12 +158,15 @@ describe("/admin/gift-cards", () => {
it("creates a gift card", async () => {
const api = useApi()
const dateString = new Date().toISOString()
const response = await api
.post(
"/admin/gift-cards",
{
value: 1000,
region_id: "region",
ends_at: dateString,
},
{
headers: {
+43 -11
View File
@@ -3,7 +3,6 @@ const {
ReturnReason,
Order,
LineItem,
ProductVariant,
CustomShippingOption,
} = require("@medusajs/medusa")
@@ -76,12 +75,8 @@ describe("/admin/orders", () => {
describe("POST /admin/orders/:id", () => {
beforeEach(async () => {
try {
await adminSeeder(dbConnection)
await orderSeeder(dbConnection)
} catch (err) {
throw err
}
await adminSeeder(dbConnection)
await orderSeeder(dbConnection)
})
afterEach(async () => {
@@ -220,7 +215,6 @@ describe("/admin/orders", () => {
it("cancels an order and increments inventory_quantity", async () => {
const api = useApi()
const manager = dbConnection.manager
const initialInventoryRes = await api.get("/store/variants/test-variant")
@@ -1111,7 +1105,7 @@ describe("/admin/orders", () => {
}
)
//Find variant that should have its inventory_quantity updated
// Find variant that should have its inventory_quantity updated
const toTest = returned.data.order.items.find((i) => i.id === "test-item")
expect(returned.status).toEqual(200)
@@ -1144,7 +1138,7 @@ describe("/admin/orders", () => {
}
)
//Find variant that should have its inventory_quantity updated
// Find variant that should have its inventory_quantity updated
const toTest = returned.data.order.items.find((i) => i.id === "test-item")
expect(returned.status).toEqual(200)
@@ -1203,6 +1197,44 @@ describe("/admin/orders", () => {
])
})
it("lists all orders with a fulfillment status = fulfilled", async () => {
const api = useApi()
const response = await api
.get("/admin/orders?fulfillment_status[]=fulfilled", {
headers: {
authorization: "Bearer test_token",
},
})
.catch((err) => console.log(err))
expect(response.status).toEqual(200)
expect(response.data.orders).toEqual([
expect.objectContaining({
id: "test-order",
}),
])
})
it("fails to lists all orders with an invalid status", async () => {
expect.assertions(3)
const api = useApi()
await api
.get("/admin/orders?status[]=test", {
headers: {
authorization: "Bearer test_token",
},
})
.catch((err) => {
expect(err.response.status).toEqual(400)
expect(err.response.data.type).toEqual("invalid_data")
expect(err.response.data.message).toEqual(
"each value in status must be a valid enum value"
)
})
})
it("list all orders with matching order email", async () => {
const api = useApi()
@@ -1696,7 +1728,7 @@ describe("/admin/orders", () => {
expect(returnOnOrder.status).toEqual(200)
const captured = await api.post(
await api.post(
"/admin/orders/test-order/capture",
{},
{
@@ -340,7 +340,7 @@ describe("/admin/products", () => {
}
})
it("returns a list of products with giftcard in list", async () => {
it("returns a list of products with only giftcard in list", async () => {
const api = useApi()
const payload = {
@@ -641,7 +641,6 @@ describe("/admin/products", () => {
options: expect.any(Array),
tags: expect.any(Array),
variants: expect.any(Array),
created_at: expect.any(String),
updated_at: expect.any(String),
},
{
@@ -653,7 +652,6 @@ describe("/admin/products", () => {
options: expect.any(Array),
tags: expect.any(Array),
variants: expect.any(Array),
created_at: expect.any(String),
updated_at: expect.any(String),
},
{
@@ -665,7 +663,6 @@ describe("/admin/products", () => {
options: expect.any(Array),
tags: expect.any(Array),
variants: expect.any(Array),
created_at: expect.any(String),
updated_at: expect.any(String),
},
])
@@ -782,6 +779,46 @@ describe("/admin/products", () => {
)
})
it("creates a product that is not discountable", async () => {
const api = useApi()
const payload = {
title: "Test",
discountable: false,
description: "test-product-description",
type: { value: "test-type" },
images: ["test-image.png", "test-image-2.png"],
collection_id: "test-collection",
tags: [{ value: "123" }, { value: "456" }],
options: [{ title: "size" }, { title: "color" }],
variants: [
{
title: "Test variant",
inventory_quantity: 10,
prices: [{ currency_code: "usd", amount: 100 }],
options: [{ value: "large" }, { value: "green" }],
},
],
}
const response = await api
.post("/admin/products", payload, {
headers: {
Authorization: "Bearer test_token",
},
})
.catch((err) => {
console.log(err)
})
expect(response.status).toEqual(200)
expect(response.data.product).toEqual(
expect.objectContaining({
discountable: false,
})
)
})
it("Sets variant ranks when creating a product", async () => {
const api = useApi()
@@ -890,7 +927,6 @@ describe("/admin/products", () => {
const payload = {
collection_id: null,
type: null,
variants: [
{
id: "test-variant",
@@ -944,7 +980,6 @@ describe("/admin/products", () => {
],
}),
],
type: null,
status: "published",
collection: null,
type: expect.objectContaining({
@@ -67,6 +67,11 @@ describe("/admin/discounts", () => {
console.log(err)
})
expect(response.data).toEqual({
id: "test-region",
object: "region",
deleted: true,
})
expect(response.status).toEqual(200)
})
})
@@ -12,7 +12,7 @@ Object {
"last_name": "testesen",
"metadata": null,
"orders": Array [],
"phone": "12345678",
"phone": null,
"updated_at": Any<String>,
}
`;
@@ -32,6 +32,8 @@ Object {
},
],
"count": 3,
"limit": 10,
"offset": 0,
}
`;
@@ -1,11 +1,8 @@
const path = require("path")
const { Region, DiscountRule, Discount } = require("@medusajs/medusa")
const setupServer = require("../../../helpers/setup-server")
const { useApi } = require("../../../helpers/use-api")
const { initDb, useDb } = require("../../../helpers/use-db")
const adminSeeder = require("../../helpers/admin-seeder")
const { exportAllDeclaration } = require("@babel/types")
jest.setTimeout(30000)
@@ -34,7 +31,6 @@ describe("/admin/auth", () => {
password: "secret_password",
first_name: "test",
last_name: "testesen",
phone: "12345678",
})
.catch((err) => {
console.log(err)
@@ -57,7 +53,7 @@ describe("/admin/auth", () => {
updated_at: expect.any(String),
first_name: "test",
last_name: "testesen",
phone: "12345678",
phone: null,
email: "test@testesen.dk",
})
})
@@ -378,7 +378,7 @@ describe("/store/carts", () => {
expect(e.response.status).toBe(409)
}
//check to see if payment has been cancelled and cart is not completed
// check to see if payment has been cancelled and cart is not completed
const res = await api.get(`/store/carts/test-cart-2`)
expect(res.data.cart.payment.canceled_at).not.toBe(null)
expect(res.data.cart.completed_at).toBe(null)
@@ -414,7 +414,7 @@ describe("/store/carts", () => {
expect(e.response.status).toBe(409)
}
//check to see if payment has been cancelled and cart is not completed
// check to see if payment has been cancelled and cart is not completed
const res = await api.get(`/store/carts/swap-cart`)
expect(res.data.cart.payment_authorized_at).toBe(null)
expect(res.data.cart.payment.canceled_at).not.toBe(null)
@@ -450,7 +450,7 @@ describe("/store/carts", () => {
console.log(error)
}
//check to see if payment is authorized and cart is completed
// check to see if payment is authorized and cart is completed
const res = await api.get(`/store/carts/swap-cart`)
expect(res.data.cart.payment_authorized_at).not.toBe(null)
expect(res.data.cart.completed_at).not.toBe(null)
@@ -477,7 +477,7 @@ describe("/store/carts", () => {
type: "swap",
})
let cartWithCustomSo = await manager.save(_cart)
const cartWithCustomSo = await manager.save(_cart)
await manager.insert(CustomShippingOption, {
id: "another-cso-test",
@@ -683,7 +683,7 @@ describe("/store/carts", () => {
it("updates empty cart.customer_id on cart retrieval", async () => {
const api = useApi()
let customer = await api.post(
const customer = await api.post(
"/store/customers",
{
email: "oli@test.dk",
@@ -712,7 +712,7 @@ describe("/store/carts", () => {
it("updates cart.customer_id on cart retrieval if cart.customer_id differ from session customer", async () => {
const api = useApi()
let customer = await api.post(
const customer = await api.post(
"/store/customers",
{
email: "oli@test.dk",
@@ -92,6 +92,8 @@ describe("/store/collections", () => {
},
],
count: 3,
limit: 10,
offset: 0,
})
})
})
@@ -5,8 +5,6 @@ const setupServer = require("../../../helpers/setup-server")
const { useApi } = require("../../../helpers/use-api")
const { initDb, useDb } = require("../../../helpers/use-db")
const customerSeeder = require("../../helpers/customer-seeder")
jest.setTimeout(30000)
describe("/store/customers", () => {
@@ -113,21 +111,22 @@ describe("/store/customers", () => {
password: "test",
})
const customerId = authResponse.data.customer.id
const [authCookie] = authResponse.headers["set-cookie"][0].split(";")
const response = await api.post(
`/store/customers/me`,
{
password: "test",
metadata: { key: "value" },
},
{
headers: {
Cookie: authCookie,
const response = await api
.post(
`/store/customers/me`,
{
password: "test",
metadata: { key: "value" },
},
}
)
{
headers: {
Cookie: authCookie,
},
}
)
.catch((e) => console.log("err", e))
expect(response.status).toEqual(200)
expect(response.data.customer).not.toHaveProperty("password_hash")
@@ -146,7 +145,6 @@ describe("/store/customers", () => {
password: "test",
})
const customerId = authResponse.data.customer.id
const [authCookie] = authResponse.headers["set-cookie"][0].split(";")
const response = await api.post(
@@ -192,7 +190,6 @@ describe("/store/customers", () => {
password: "test",
})
const customerId = authResponse.data.customer.id
const [authCookie] = authResponse.headers["set-cookie"][0].split(";")
const response = await api.post(
@@ -230,7 +227,6 @@ describe("/store/customers", () => {
password: "test",
})
const customerId = authResponse.data.customer.id
const [authCookie] = authResponse.headers["set-cookie"][0].split(";")
const check = await api.post(
@@ -24,6 +24,49 @@ describe("/store/return-reasons", () => {
medusaProcess.kill()
})
describe("GET /store/return-reasons/:id", () => {
let rrId
beforeEach(async () => {
try {
const created = dbConnection.manager.create(ReturnReason, {
value: "wrong_size",
label: "Wrong size",
})
const result = await dbConnection.manager.save(created)
rrId = result.id
} catch (err) {
console.log(err)
throw err
}
})
afterEach(async () => {
const db = useDb()
await db.teardown()
})
it("get a return reason", async () => {
const api = useApi()
const response = await api
.get(`/store/return-reasons/${rrId}`)
.catch((err) => {
console.log(err)
})
expect(response.status).toEqual(200)
expect(response.data.return_reason).toEqual(
expect.objectContaining({
id: rrId,
value: "wrong_size",
})
)
})
})
describe("GET /store/return-reasons", () => {
let rrId
let rrId_1
@@ -249,6 +249,29 @@ describe("/store/carts", () => {
])
})
it("failes to create a return with an invalid quantity (less than 1)", async () => {
const api = useApi()
const response = await api
.post("/store/returns", {
order_id: "order_test",
items: [
{
reason_id: rrId,
note: "TOO small",
item_id: "test-item",
quantity: 0,
},
],
})
.catch((err) => {
return err.response
})
expect(response.status).toEqual(400)
expect(response.data.type).toEqual("invalid_data")
})
it("creates a return with discount and non-discountable item", async () => {
const api = useApi()
+4 -4
View File
@@ -8,15 +8,15 @@
"build": "babel src -d dist --extensions \".ts,.js\""
},
"dependencies": {
"@medusajs/medusa": "1.1.51-dev-1636035082111",
"medusa-interfaces": "1.1.27-dev-1636035082111",
"@medusajs/medusa": "1.1.51",
"medusa-interfaces": "1.1.27",
"typeorm": "^0.2.31"
},
"devDependencies": {
"@babel/cli": "^7.12.10",
"@babel/core": "^7.12.10",
"@babel/node": "^7.12.10",
"babel-preset-medusa-package": "1.1.17-dev-1636035082111",
"babel-preset-medusa-package": "1.1.17",
"jest": "^26.6.3"
}
}
}
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -19,7 +19,7 @@
"babel-jest": "^26.6.3",
"babel-preset-medusa-package": "^1.0.0",
"cross-env": "^7.0.2",
"eslint": "^8.0.0",
"eslint": "^8.2.0",
"eslint-config-google": "^0.14.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^4.0.0",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"plugins": ["@babel/plugin-proposal-class-properties"],
"presets": ["@babel/preset-env"],
"presets": ["@babel/preset-env", "@babel/preset-typescript"],
"env": {
"test": {
"plugins": ["@babel/plugin-transform-runtime"]
+7 -3
View File
@@ -10,7 +10,7 @@
},
"scripts": {
"test": "jest",
"build": "babel src --out-dir dist/ --ignore **/__tests__",
"build": "tsc --build",
"prepare": "cross-env NODE_ENV=production npm run build",
"watch": "babel -w src --out-dir dist/ --ignore **/__tests__"
},
@@ -23,15 +23,19 @@
"@babel/plugin-transform-classes": "^7.9.5",
"@babel/plugin-transform-runtime": "^7.7.6",
"@babel/preset-env": "^7.13.9",
"@babel/preset-typescript": "^7.16.0",
"@babel/runtime": "^7.13.9",
"class-transformer": "^0.4.0",
"class-validator": "^0.13.1",
"cross-env": "^5.2.1",
"eslint": "^6.8.0",
"jest": "^25.5.2",
"prettier": "^1.19.1"
"prettier": "^1.19.1",
"typescript": "^4.4.4"
},
"dependencies": {
"joi": "^17.3.0",
"joi-objectid": "^3.0.1"
},
"gitHead": "41a5425405aea5045a26def95c0dc00cf4a5a44d"
}
}
@@ -22,12 +22,21 @@ export const MedusaErrorCodes = {
* @extends Error
*/
class MedusaError extends Error {
public type: string
public message: string
public code?: string
public date: Date
public static Types = MedusaErrorTypes
public static Codes = MedusaErrorCodes
/**
* Creates a standardized error to be used across Medusa project.
* @param type {MedusaErrorType} - the type of error.
* @param params {Array} - Error params.
* @param {string} type - type of error
* @param {string} message - message to go along with error
* @param {string} code - code of error
* @param {Array} params - params
*/
constructor(type, message, code, ...params) {
constructor(type: string, message: string, code?: string, ...params: any) {
super(...params)
if (Error.captureStackTrace) {
@@ -35,14 +44,10 @@ class MedusaError extends Error {
}
this.type = type
this.name = type
this.code = code
this.message = message
this.date = new Date()
}
}
MedusaError.Types = MedusaErrorTypes
MedusaError.Codes = MedusaErrorCodes
export default MedusaError
+8 -8
View File
@@ -1,11 +1,11 @@
export { countries } from "./countries"
export { isoCountryLookup } from "./countries"
export { transformIdableFields } from "./transform-idable-fields"
export { indexTypes } from "./index-types"
export { default as Validator } from "./validator"
export { default as compareObjectsByProp } from "./compare-objects"
export { countries, isoCountryLookup } from "./countries"
export { default as createRequireFromPath } from "./create-require-from-path"
export { default as MedusaError } from "./errors"
export { default as getConfigFile } from "./get-config-file"
export { default as createRequireFromPath } from "./create-require-from-path"
export { default as compareObjectsByProp } from "./compare-objects"
export { default as zeroDecimalCurrencies } from "./zero-decimal-currencies"
export { default as humanizeAmount } from "./humanize-amount"
export { indexTypes } from "./index-types"
export { transformIdableFields } from "./transform-idable-fields"
export { default as Validator } from "./validator"
export { default as zeroDecimalCurrencies } from "./zero-decimal-currencies"
+22
View File
@@ -0,0 +1,22 @@
{
"compilerOptions": {
"lib": ["es5", "es6"],
"target": "es5",
"outDir": "./dist",
"esModuleInterop": true,
"declaration": true,
"module": "commonjs",
"moduleResolution": "node",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"sourceMap": true,
"noImplicitReturns": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"noImplicitThis": true,
"allowJs": true,
"skipLibCheck": true
},
"include": ["./src/**/*"],
"exclude": ["./dist/**/*", "./src/__tests__", "node_modules"]
}
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -38,4 +38,4 @@
"medusa-core-utils": "^1.1.26"
},
"gitHead": "41a5425405aea5045a26def95c0dc00cf4a5a44d"
}
}
@@ -21,6 +21,11 @@ class BaseService {
buildQuery_(selector, config = {}) {
const build = (obj) => {
const where = Object.entries(obj).reduce((acc, [key, value]) => {
// Undefined values indicate that they have no significance to the query.
// If the query is looking for rows where a column is not set it should use null instead of undefined
if (typeof value === "undefined") {
return acc
}
switch (true) {
case value instanceof FindOperator:
acc[key] = value
+2
View File
@@ -0,0 +1,2 @@
node_modules
/dist
+1
View File
@@ -0,0 +1 @@
# Medusa Commerce JS Client
+7
View File
@@ -0,0 +1,7 @@
{
"transform": {
"^.+\\.(t|j)sx?$": "ts-jest"
},
"testRegex": "(/tests/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$",
"moduleFileExtensions": ["ts", "tsx", "js", "jsx", "json", "node"]
}
+34
View File
@@ -0,0 +1,34 @@
{
"name": "@medusajs/medusa-js",
"version": "1.0.1-canary.1",
"description": "Client for Medusa Commerce Rest API",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"files": [
"dist/**/*"
],
"scripts": {
"build": "tsc --build",
"test": "jest --config jestconfig.json"
},
"author": "Oliver Juhl",
"license": "MIT",
"dependencies": {
"axios": "^0.21.0",
"retry-axios": "^2.6.0"
},
"repository": {
"type": "git",
"url": "git+https://github.com/medusajs/medusa-js.git"
},
"bugs": {
"url": "https://github.com/medusajs/medusa-js/issues"
},
"devDependencies": {
"@types/jest": "^26.0.19",
"eslint": "^8.2.0",
"jest": "^26.6.3",
"prettier": "^2.2.1",
"typescript": "^4.1.3"
}
}
+57
View File
@@ -0,0 +1,57 @@
"use strict"
/**
* MedusaError is the base error for every other MedusaError
*/
export default class MedusaError extends Error {
constructor() {
super()
}
public static factory(type: ErrorType): MedusaError {
switch (type) {
case ErrorType.INVALID_REQUEST:
return new MedusaInvalidRequestError()
case ErrorType.AUTHENTICATION:
return new MedusaAuthenticationError()
case ErrorType.API:
return new MedusaAPIError()
case ErrorType.PERMISSION:
return new MedusaPermissionError()
case ErrorType.CONNECTION:
return new MedusaConnectionError()
}
}
}
enum ErrorType {
"INVALID_REQUEST",
"API",
"AUTHENTICATION",
"PERMISSION",
"CONNECTION",
}
/**
* MedusaInvalidRequestError is raised when a request as invalid parameters.
*/
export class MedusaInvalidRequestError extends MedusaError {}
/**
* MedusaAPIError is raised in case no other type cover the problem
*/
export class MedusaAPIError extends MedusaError {}
/**
* MedusaAuthenticationError is raised when invalid credentials is used to connect to Medusa
*/
export class MedusaAuthenticationError extends MedusaError {}
/**
* MedusaPermissionErorr is raised when attempting to access a resource without permissions
*/
export class MedusaPermissionError extends MedusaError {}
/**
* MedusaConnectionError is raised when the Medusa servers can't be reached.
*/
export class MedusaConnectionError extends MedusaError {}
+51
View File
@@ -0,0 +1,51 @@
import MedusaError from "./error"
import Client, { Config } from "./request"
import AuthResource from "./resources/auth"
import CartsResource from "./resources/carts"
import CollectionsResource from "./resources/collections"
import CustomersResource from "./resources/customers"
import GiftCardsResource from "./resources/gift-cards"
import OrdersResource from "./resources/orders"
import ProductsResource from "./resources/products"
import RegionsResource from "./resources/regions"
import ReturnReasonsResource from "./resources/return-reasons"
import ReturnsResource from "./resources/returns"
import ShippingOptionsResource from "./resources/shipping-options"
import SwapsResource from "./resources/swaps"
class Medusa {
private client: Client
public auth: AuthResource
public carts: CartsResource
public customers: CustomersResource
public errors: MedusaError
public orders: OrdersResource
public products: ProductsResource
public regions: RegionsResource
public returnReasons: ReturnReasonsResource
public returns: ReturnsResource
public shippingOptions: ShippingOptionsResource
public swaps: SwapsResource
public collections: CollectionsResource
public giftCards: GiftCardsResource
constructor(config: Config) {
this.client = new Client(config)
this.auth = new AuthResource(this.client)
this.carts = new CartsResource(this.client)
this.customers = new CustomersResource(this.client)
this.errors = new MedusaError()
this.orders = new OrdersResource(this.client)
this.products = new ProductsResource(this.client)
this.regions = new RegionsResource(this.client)
this.returnReasons = new ReturnReasonsResource(this.client)
this.returns = new ReturnsResource(this.client)
this.shippingOptions = new ShippingOptionsResource(this.client)
this.swaps = new SwapsResource(this.client)
this.collections = new CollectionsResource(this.client)
this.giftCards = new GiftCardsResource(this.client)
}
}
export default Medusa
+199
View File
@@ -0,0 +1,199 @@
import axios, { AxiosError, AxiosInstance } from "axios"
import * as rax from "retry-axios"
import { v4 as uuidv4 } from "uuid"
export interface Config {
baseUrl: string
maxRetries: number
}
export interface RequestOptions {
apiKey?: string
timeout?: number
numberOfRetries?: number
}
export type RequestMethod = "DELETE" | "POST" | "GET"
const defaultConfig = {
maxRetries: 0,
baseUrl: "http://localhost:9000",
}
class Client {
private axiosClient: AxiosInstance
private config: Config
constructor(config: Config) {
/** @private @constant {AxiosInstance} */
this.axiosClient = this.createClient({ ...defaultConfig, ...config })
/** @private @constant {Config} */
this.config = { ...defaultConfig, ...config }
}
shouldRetryCondition(
err: AxiosError,
numRetries: number,
maxRetries: number
): boolean {
// Obviously, if we have reached max. retries we stop
if (numRetries >= maxRetries) {
return false
}
// If no response, we assume a connection error and retry
if (!err.response) {
return true
}
// Retry on conflicts
if (err.response.status === 409) {
return true
}
// All 5xx errors are retried
// OBS: We are currently not retrying 500 requests, since our core needs proper error handling.
// At the moment, 500 will be returned on all errors, that are not of type MedusaError.
if (err.response.status > 500 && err.response.status <= 599) {
return true
}
return false
}
// Stolen from https://github.com/stripe/stripe-node/blob/fd0a597064289b8c82f374f4747d634050739043/lib/utils.js#L282
normalizeHeaders(obj: object): object {
if (!(obj && typeof obj === "object")) {
return obj
}
return Object.keys(obj).reduce((result, header) => {
result[this.normalizeHeader(header)] = obj[header]
return result
}, {})
}
// Stolen from https://github.com/marten-de-vries/header-case-normalizer/blob/master/index.js#L36-L41
normalizeHeader(header: string): string {
return header
.split("-")
.map(
(text) => text.charAt(0).toUpperCase() + text.substr(1).toLowerCase()
)
.join("-")
}
/**
* Creates all the initial headers.
* We add the idempotency key, if the request is configured to retry.
* @param {object} userHeaders user supplied headers
* @param {Types.RequestMethod} method request method
* @param {string} path request path
* @return {object}
*/
setHeaders(
userHeaders: RequestOptions,
method: RequestMethod,
path: string
): object {
let defaultHeaders: object = {
Accept: "application/json",
"Content-Type": "application/json",
}
// TODO: if route is an authenticated route, add api key
if (path.startsWith("/admin")) {
defaultHeaders = {
...defaultHeaders,
}
}
// only add idempotency key, if we want to retry
if (this.config.maxRetries > 0 && method === "POST") {
defaultHeaders["Idempotency-Key"] = uuidv4()
}
return Object.assign({}, defaultHeaders, this.normalizeHeaders(userHeaders))
}
/**
* Creates the axios client used for requests
* As part of the creation, we configure the retry conditions
* and the exponential backoff approach.
* @param {Config} config user supplied configurations
* @return {AxiosInstance}
*/
createClient(config: Config): AxiosInstance {
const client = axios.create({
baseURL: config.baseUrl,
})
rax.attach(client)
client.defaults.raxConfig = {
instance: client,
retry: config.maxRetries,
backoffType: "exponential",
shouldRetry: (err: AxiosError): boolean => {
const cfg = rax.getConfig(err)
if (cfg) {
return this.shouldRetryCondition(
err,
cfg.currentRetryAttempt || 1,
cfg.retry || 3
)
} else {
return false
}
},
}
return client
}
/**
* Format the response data as:
* { cart: { id: "some_cart", ... } }
* @param {object} data Axios response data
* @param {number} status Axios response status code
* @return {object}
*/
createRawResponse(data: object, status: number): object {
const res = { status }
Object.entries(data).map(([key, value]) => {
res[key] = value
})
return res as any // eslint-disable-line
}
/**
* Axios request
* @param {Types.RequestMethod} method request method
* @param {string} path request path
* @param {object} payload request payload
* @param {RequestOptions} options axios configuration
* @return {object}
*/
async request(
method: RequestMethod,
path: string,
payload: object = {},
options: RequestOptions = {}
): Promise<any> {
const reqOpts = {
method,
withCredentials: true,
url: path,
data: payload,
json: true,
headers: this.setHeaders(options, method, path),
}
const { data, status } = await this.axiosClient(reqOpts)
return this.createRawResponse(data, status)
}
}
export default Client
@@ -0,0 +1,47 @@
import {
StoreCustomersRes,
StorePostCustomersCustomerAddressesAddressReq,
StorePostCustomersCustomerAddressesReq,
} from "@medusajs/medusa"
import { AxiosPromise } from "axios"
import BaseResource from "./base"
class AddressesResource extends BaseResource {
/**
* Adds an address to a customers saved addresses
* @param {StorePostCustomersCustomerAddressesReq} payload contains information to create an address
* @return {AxiosPromise<StoreCustomerResponse>}
*/
addAddress(
payload: StorePostCustomersCustomerAddressesReq
): AxiosPromise<StoreCustomersRes> {
const path = `/store/customers/me/addresses`
return this.client.request("POST", path, payload)
}
/**
* Deletes an address of a customer
* @param {string} address_id id of the address to delete
* @return {AxiosPromise<StoreCustomersResponse>}
*/
deleteAddress(address_id: string): AxiosPromise<StoreCustomersRes> {
const path = `/store/customers/me/addresses/${address_id}`
return this.client.request("DELETE", path)
}
/**
* Update an address of a customer
* @param {string} address_id id of customer
* @param {StorePostCustomersCustomerAddressesAddressReq} payload address update
* @return {StoreCustomersResponse}
*/
updateAddress(
address_id: string,
payload: StorePostCustomersCustomerAddressesAddressReq
): AxiosPromise<StoreCustomersRes> {
const path = `/store/customers/me/addresses/${address_id}`
return this.client.request("POST", path, payload)
}
}
export default AddressesResource
+41
View File
@@ -0,0 +1,41 @@
import { AxiosPromise } from "axios"
import {
StoreGetAuthEmailRes,
StorePostAuthReq,
StoreAuthRes,
} from "@medusajs/medusa"
import BaseResource from "./base"
class AuthResource extends BaseResource {
/**
* @description Authenticates a customer using email and password combination
* @param {StorePostAuthReq} payload authentication payload
* @return {AxiosPromise<StoreAuthRes>}
*/
authenticate(payload: StorePostAuthReq): AxiosPromise<StoreAuthRes> {
const path = `/store/auth`
return this.client.request("POST", path, payload)
}
/**
* @description Retrieves an authenticated session
* Usually used to check if authenticated session is alive.
* @return {AxiosPromise<StoreAuthRes>}
*/
getSession(): AxiosPromise<StoreAuthRes> {
const path = `/store/auth`
return this.client.request("GET", path)
}
/**
* @description Check if email exists
* @param {string} email is required
* @return {AxiosPromise<StoreGetAuthEmailRes>}
*/
exists(email: string): AxiosPromise<StoreGetAuthEmailRes> {
const path = `/store/auth/${email}`
return this.client.request("GET", path)
}
}
export default AuthResource
+9
View File
@@ -0,0 +1,9 @@
import Client from "../request"
export default class BaseResource {
public client: Client
constructor(client: Client) {
this.client = client
}
}
+160
View File
@@ -0,0 +1,160 @@
import {
StoreCartsRes,
StorePostCartReq,
StorePostCartsCartPaymentSessionReq,
StorePostCartsCartPaymentSessionUpdateReq,
StorePostCartsCartReq,
StorePostCartsCartShippingMethodReq,
} from "@medusajs/medusa"
import { AxiosPromise } from "axios"
import BaseResource from "./base"
import LineItemsResource from "./line-items"
class CartsResource extends BaseResource {
public lineItems = new LineItemsResource(this.client)
/**
* Adds a shipping method to cart
* @param {string} cart_id Id of cart
* @param {StorePostCartsCartShippingMethodReq} payload Containg id of shipping option and optional data
* @return {AxiosPromise<StoreCartsRes>}
*/
addShippingMethod(
cart_id: string,
payload: StorePostCartsCartShippingMethodReq
): AxiosPromise<StoreCartsRes> {
const path = `/store/carts/${cart_id}/shipping-methods`
return this.client.request("POST", path, payload)
}
/**
* Completes a cart.
* Payment authorization is attempted and if more work is required, we simply return the cart for further updates.
* If payment is authorized and order is not yet created, we make sure to do so.
* The completion of a cart can be performed idempotently with a provided header Idempotency-Key.
* If not provuided, we will generate one for the request.
* @param {string} cart_id is required
* @return {AxiosPromise<StoreCartsRes>}
*/
complete(cart_id: string): AxiosPromise<StoreCartsRes> {
const path = `/store/carts/${cart_id}/complete`
return this.client.request("POST", path)
}
/**
* Creates a cart
* @param {StorePostCartReq} payload is optional and can contain a region_id and items.
* The cart will contain the payload, if provided. Otherwise it will be empty
* @return {AxiosPromise<StoreCartsRes>}
*/
create(payload?: StorePostCartReq): AxiosPromise<StoreCartsRes> {
const path = `/store/carts`
return this.client.request("POST", path, payload)
}
/**
* Creates payment sessions.
* Initializes the payment sessions that can be used to pay for the items of the cart.
* This is usually called when a customer proceeds to checkout.
* @param {string} cart_id is required
* @return {AxiosPromise<StoreCartsRes>}
*/
createPaymentSessions(cart_id: string): AxiosPromise<StoreCartsRes> {
const path = `/store/carts/${cart_id}/payment-sessions`
return this.client.request("POST", path)
}
/**
* Removes a discount from cart.
* @param {string} cart_id is required
* @param {string} code discount code to remove
* @return {AxiosPromise<StoreCartsRes>}
*/
deleteDiscount(cart_id: string, code: string): AxiosPromise<StoreCartsRes> {
const path = `/store/carts/${cart_id}/discounts/${code}`
return this.client.request("DELETE", path)
}
/**
* Removes a payment session from a cart.
* Can be useful in case a payment has failed
* @param {string} cart_id is required
* @param {string} provider_id the provider id of the session e.g. "stripe"
* @return {AxiosPromise<StoreCartsRes>}
*/
deletePaymentSession(
cart_id: string,
provider_id: string
): AxiosPromise<StoreCartsRes> {
const path = `/store/carts/${cart_id}/payment-sessions/${provider_id}`
return this.client.request("DELETE", path)
}
/**
* Refreshes a payment session.
* @param {string} cart_id is required
* @param {string} provider_id the provider id of the session e.g. "stripe"
* @return {AxiosPromise<StoreCartsRes>}
*/
refreshPaymentSession(
cart_id: string,
provider_id: string
): AxiosPromise<StoreCartsRes> {
const path = `/store/carts/${cart_id}/payment-sessions/${provider_id}/refresh`
return this.client.request("POST", path)
}
/**
* Retrieves a cart
* @param {string} cart_id is required
* @return {AxiosPromise<StoreCartsRes>}
*/
retrieve(cart_id: string): AxiosPromise<StoreCartsRes> {
const path = `/store/carts/${cart_id}`
return this.client.request("GET", path)
}
/**
* Refreshes a payment session.
* @param {string} cart_id is required
* @param {StorePostCartsCartPaymentSessionReq} payload the provider id of the session e.g. "stripe"
* @return {AxiosPromise<StoreCartsRes>}
*/
setPaymentSession(
cart_id: string,
payload: StorePostCartsCartPaymentSessionReq
): AxiosPromise<StoreCartsRes> {
const path = `/store/carts/${cart_id}/payment-session`
return this.client.request("POST", path, payload)
}
/**
* Updates a cart
* @param {string} cart_id is required
* @param {StorePostCartsCartReq} payload is required and can contain region_id, email, billing and shipping address
* @return {AxiosPromise<StoreCartsRes>}
*/
update(
cart_id: string,
payload: StorePostCartsCartReq
): AxiosPromise<StoreCartsRes> {
const path = `/store/carts/${cart_id}`
return this.client.request("POST", path, payload)
}
/**
* Updates the payment method
* @param {string} cart_id is required
* @param {StorePostCartsCartPaymentSessionUpdateReq} payload is required
* @return {AxiosPromise<StoreCartsRes>}
*/
updatePaymentSession(
cart_id: string,
payload: StorePostCartsCartPaymentSessionUpdateReq
): AxiosPromise<StoreCartsRes> {
const path = `/store/carts/${cart_id}/payment-session/update`
return this.client.request("POST", path, payload)
}
}
export default CartsResource
@@ -0,0 +1,42 @@
import { AxiosPromise } from "axios"
import {
StoreCollectionsRes,
StoreCollectionsListRes,
StoreGetCollectionsParams,
} from "@medusajs/medusa"
import BaseResource from "./base"
class CollectionsResource extends BaseResource {
/**
* @description Retrieves a single collection
* @param {string} id id of the collection
* @return {AxiosPromise<StoreCollectionsRes>}
*/
retrieve(id: string): AxiosPromise<StoreCollectionsRes> {
const path = `/store/collections/${id}`
return this.client.request("GET", path)
}
/**
* @description Retrieves a list of collections
* @param {string} query is optional. Can contain a limit and offset for the returned list
* @return {AxiosPromise<StoreCollectionsListRes>}
*/
list(
query?: StoreGetCollectionsParams
): AxiosPromise<StoreCollectionsListRes> {
let path = `/store/collections`
if (query) {
const queryString = Object.entries(query).map(([key, value]) => {
return `${key}=${value}`
})
path = `/store/collections?${queryString.join("&")}`
}
return this.client.request("GET", path)
}
}
export default CollectionsResource
@@ -0,0 +1,102 @@
import {
StoreCustomersListOrdersRes,
StoreCustomersRes,
StoreGetCustomersCustomerOrdersParams,
StorePostCustomersCustomerPasswordTokenReq,
StorePostCustomersCustomerReq,
StorePostCustomersReq,
} from "@medusajs/medusa"
import { AxiosPromise } from "axios"
import AddressesResource from "./addresses"
import BaseResource from "./base"
import PaymentMethodsResource from "./payment-methods"
class CustomerResource extends BaseResource {
public paymentMethods = new PaymentMethodsResource(this.client)
public addresses = new AddressesResource(this.client)
/**
* Creates a customer
* @param {StorePostCustomersReq} payload information of customer
* @return { AxiosPromise<StoreCustomersRes>}
*/
create(payload: StorePostCustomersReq): AxiosPromise<StoreCustomersRes> {
const path = `/store/customers`
return this.client.request("POST", path, payload)
}
/**
* Retrieves the customer that is currently logged
* @return {AxiosPromise<StoreCustomersRes>}
*/
retrieve(): AxiosPromise<StoreCustomersRes> {
const path = `/store/customers/me`
return this.client.request("GET", path)
}
/**
* Updates a customer
* @param {StorePostCustomersCustomerReq} payload information to update customer with
* @return {AxiosPromise<StoreCustomersRes>}
*/
update(
payload: StorePostCustomersCustomerReq
): AxiosPromise<StoreCustomersRes> {
const path = `/store/customers/me`
return this.client.request("POST", path, payload)
}
/**
* Retrieve customer orders
* @param {StoreGetCustomersCustomerOrdersParams} params optional params to retrieve orders
* @return {AxiosPromise<StoreCustomersListOrdersRes>}
*/
listOrders(
params?: StoreGetCustomersCustomerOrdersParams
): AxiosPromise<StoreCustomersListOrdersRes> {
let path = `/store/customers/me/orders`
if (params) {
let query: string | undefined
for (const key of Object.keys(params)) {
if (query) {
query += `&${key}=${params[key]}`
} else {
query = `?${key}=${params[key]}`
}
}
if (query) {
path += query
}
}
return this.client.request("GET", path)
}
/**
* Resets customer password
* @param {StorePostCustomersCustomerPasswordTokenReq} payload info used to reset customer password
* @return {AxiosPromise<StoreCustomersRes>}
*/
resetPassword(
payload: StorePostCustomersCustomerPasswordTokenReq
): AxiosPromise<StoreCustomersRes> {
const path = `/store/customers/password-reset`
return this.client.request("POST", path, payload)
}
/**
* Generates a reset password token, which can be used to reset the password.
* The token is not returned but should be sent out to the customer in an email.
* @param {StorePostCustomersCustomerPasswordTokenReq} payload info used to generate token
* @return {AxiosPromise}
*/
generatePasswordToken(
payload: StorePostCustomersCustomerPasswordTokenReq
): AxiosPromise {
const path = `/store/customers/password-token`
return this.client.request("POST", path, payload)
}
}
export default CustomerResource
@@ -0,0 +1,17 @@
import { StoreGiftCardsRes } from "@medusajs/medusa"
import { AxiosPromise } from "axios"
import BaseResource from "./base"
class GiftCardsResource extends BaseResource {
/**
* @description Retrieves a single GiftCard
* @param {string} code code of the gift card
* @return {AxiosPromise<StoreGiftCardsRes>}
*/
retrieve(code: string): AxiosPromise<StoreGiftCardsRes> {
const path = `/store/gift-cards/${code}`
return this.client.request("GET", path)
}
}
export default GiftCardsResource
@@ -0,0 +1,54 @@
import {
StoreCartsCartRes,
StoreCartsDeleteRes,
StorePostCartsCartLineItemsItemReq,
StorePostCartsCartLineItemsReq,
} from "@medusajs/medusa"
import { AxiosPromise } from "axios"
import BaseResource from "./base"
class LineItemsResource extends BaseResource {
/**
* Creates a line-item for a cart
* @param {string} cart_id id of cart
* @param {StorePostCartsCartLineItemsReq} payload details needed to create a line-item
* @return {AxiosPromise<StoreCartsCartRes>}
*/
create(
cart_id: string,
payload: StorePostCartsCartLineItemsReq
): AxiosPromise<StoreCartsCartRes> {
const path = `/store/carts/${cart_id}/line-items`
return this.client.request("POST", path, payload)
}
/**
* Updates a line-item.
* Only quantity updates are allowed
* @param {string} cart_id id of cart
* @param {string} line_id id of item to update
* @param {StorePostCartsCartLineItemsItemReq} payload details needed to update a line-item
* @return {AxiosPromise<StoreCartsCartRes>}
*/
update(
cart_id: string,
line_id: string,
payload: StorePostCartsCartLineItemsItemReq
): AxiosPromise<StoreCartsCartRes> {
const path = `/store/carts/${cart_id}/line-items/${line_id}`
return this.client.request("POST", path, payload)
}
/**
* Remove a line-item from a cart
* @param {string} cart_id id of cart
* @param {string} line_id id of item to remove
* @return {AxiosPromise<StoreCartsDeleteRes>}
*/
delete(cart_id: string, line_id: string): AxiosPromise<StoreCartsDeleteRes> {
const path = `/store/carts/${cart_id}/line-items/${line_id}`
return this.client.request("DELETE", path)
}
}
export default LineItemsResource
@@ -0,0 +1,48 @@
import { StoreGetOrdersParams, StoreOrdersRes } from "@medusajs/medusa"
import { AxiosPromise } from "axios"
import BaseResource from "./base"
class OrdersResource extends BaseResource {
/**
* @description Retrieves an order
* @param {string} id is required
* @return {AxiosPromise<StoreOrdersRes>}
*/
retrieve(id: string): AxiosPromise<StoreOrdersRes> {
const path = `/store/orders/${id}`
return this.client.request("GET", path)
}
/**
* @description Retrieves an order by cart id
* @param {string} cart_id is required
* @return {AxiosPromise<StoreOrdersRes>}
*/
retrieveByCartId(cart_id: string): AxiosPromise<StoreOrdersRes> {
const path = `/store/orders/cart/${cart_id}`
return this.client.request("GET", path)
}
/**
* @description Look up an order using order details
* @param {StoreGetOrdersParams} payload details used to look up the order
* @return {AxiosPromise<StoreOrdersRes>}
*/
lookupOrder(payload: StoreGetOrdersParams): AxiosPromise<StoreOrdersRes> {
let path = `/store/orders?`
const queryString = Object.entries(payload).map(([key, value]) => {
let val = value
if (Array.isArray(value)) {
val = value.join(",")
}
return `${key}=${encodeURIComponent(val)}`
})
path = `/store/orders?${queryString.join("&")}`
return this.client.request("GET", path, payload)
}
}
export default OrdersResource
@@ -0,0 +1,16 @@
import BaseResource from "./base"
import { AxiosPromise } from "axios"
class PaymentMethodsResource extends BaseResource {
/**
* Lists customer payment methods
* @param {string} id id of cart
* @return {AxiosPromise<{ payment_methods: object[] }>}
*/
list(id: string): AxiosPromise<{ payment_methods: object[] }> {
const path = `/store/carts/${id}/payment-methods`
return this.client.request("GET", path)
}
}
export default PaymentMethodsResource
@@ -0,0 +1,43 @@
import {
StoreGetVariantsParams,
StoreVariantsListRes,
StoreVariantsRes,
} from "@medusajs/medusa"
import { AxiosPromise } from "axios"
import BaseResource from "./base"
class ProductVariantsResource extends BaseResource {
/**
* @description Retrieves a single product variant
* @param {string} id is required
* @return {AxiosPromise<StoreVariantsRes>}
*/
retrieve(id: string): AxiosPromise<StoreVariantsRes> {
const path = `/store/variants/${id}`
return this.client.request("GET", path)
}
/**
* @description Retrieves a list of of Product Variants
* @param {StoreVariantsListParamsObject} query
* @return {AxiosPromise<StoreVariantsListRes>}
*/
list(query?: StoreGetVariantsParams): AxiosPromise<StoreVariantsListRes> {
const path = `/store/variants`
const search = Object.entries(query || {}).map(([key, value]) => {
if (Array.isArray(value)) {
return `${key}=${value.join(",")}`
}
return `${key}=${value}`
})
return this.client.request(
"GET",
`${path}${search.length > 0 && `?${search.join("&")}`}`
)
}
}
export default ProductVariantsResource
@@ -0,0 +1,55 @@
import {
StoreGetProductsParams,
StorePostSearchReq,
StorePostSearchRes,
StoreProductsListRes,
StoreProductsRes,
} from "@medusajs/medusa"
import { AxiosPromise } from "axios"
import BaseResource from "./base"
import ProductVariantsResource from "./product-variants"
class ProductsResource extends BaseResource {
public variants = new ProductVariantsResource(this.client)
/**
* @description Retrieves a single Product
* @param {string} id is required
* @return {AxiosPromise<StoreProductsRes>}
*/
retrieve(id: string): AxiosPromise<StoreProductsRes> {
const path = `/store/products/${id}`
return this.client.request("GET", path)
}
/**
* @description Searches for products
* @param {StorePostSearchReq} searchOptions is required
* @return {AxiosPromise<StorePostSearchRes>}
*/
search(searchOptions: StorePostSearchReq): AxiosPromise<StorePostSearchRes> {
const path = `/store/products/search`
return this.client.request("POST", path, searchOptions)
}
/**
* @description Retrieves a list of products
* @param {StoreGetProductsParams} query is optional. Can contain a limit and offset for the returned list
* @return {AxiosPromise<StoreProductsListRes>}
*/
list(query?: StoreGetProductsParams): AxiosPromise<StoreProductsListRes> {
let path = `/store/products`
if (query) {
const queryString = Object.entries(query).map(([key, value]) => {
return `${key}=${value}`
})
path = `/store/products?${queryString.join("&")}`
}
return this.client.request("GET", path)
}
}
export default ProductsResource
@@ -0,0 +1,26 @@
import { AxiosPromise } from "axios"
import { StoreRegionsListRes, StoreRegionsRes } from "@medusajs/medusa"
import BaseResource from "./base"
class RegionsResource extends BaseResource {
/**
* @description Retrieves a list of regions
* @return {AxiosPromise<StoreRegionsListRes>}
*/
list(): AxiosPromise<StoreRegionsListRes> {
const path = `/store/regions`
return this.client.request("GET", path)
}
/**
* @description Retrieves a region
* @param {string} id is required
* @return {AxiosPromise<StoreRegionsRes>}
*/
retrieve(id: string): AxiosPromise<StoreRegionsRes> {
const path = `/store/regions/${id}`
return this.client.request("GET", path)
}
}
export default RegionsResource
@@ -0,0 +1,29 @@
import BaseResource from "./base"
import {
StoreReturnReasonsListRes,
StoreReturnReasonsRes,
} from "@medusajs/medusa"
import { AxiosPromise } from "axios"
class ReturnReasonsResource extends BaseResource {
/**
* @description Retrieves a single Return Reason
* @param {string} id is required
* @return {AxiosPromise<StoreReturnReasonsRes>}
*/
retrieve(id: string): AxiosPromise<StoreReturnReasonsRes> {
const path = `/store/return-reasons/${id}`
return this.client.request("GET", path)
}
/**
* Lists return reasons defined in Medusa Admin
* @return {AxiosPromise<StoreReturnReasonsListRes>}
*/
list(): AxiosPromise<StoreReturnReasonsListRes> {
const path = `/store/return-reasons`
return this.client.request("GET", path)
}
}
export default ReturnReasonsResource
@@ -0,0 +1,17 @@
import BaseResource from "./base"
import { AxiosPromise } from "axios"
import { StoreReturnsRes, StorePostReturnsReq } from "@medusajs/medusa"
class ReturnsResource extends BaseResource {
/**
* Creates a return request
* @param {StorePostReturnsReq} payload details needed to create a return
* @return {AxiosPromise<StoreReturnsRes>}
*/
create(payload: StorePostReturnsReq): AxiosPromise<StoreReturnsRes> {
const path = `/store/returns`
return this.client.request("POST", path, payload)
}
}
export default ReturnsResource
@@ -0,0 +1,44 @@
import {
StoreGetShippingOptionsParams,
StoreShippingOptionsListRes,
} from "@medusajs/medusa"
import { AxiosPromise } from "axios"
import BaseResource from "./base"
class ShippingOptionsResource extends BaseResource {
/**
* @description Lists shiping options available for a cart
* @param {string} cart_id
* @return {AxiosPromise<StoreShippingOptionsListRes>}
*/
listCartOptions(cart_id: string): AxiosPromise<StoreShippingOptionsListRes> {
const path = `/store/shipping-options/${cart_id}`
return this.client.request("GET", path)
}
/**
* @description Lists shiping options available
* @param {StoreGetShippingOptionsParamsObject} query
* @return {AxiosPromise<StoreShippingOptionsListRes>}
*/
list(
query?: StoreGetShippingOptionsParams
): AxiosPromise<StoreShippingOptionsListRes> {
let path = `/store/shipping-options`
const queryString = Object.entries(query || {}).map(([key, value]) => {
let val = value
if (Array.isArray(value)) {
val = value.join(",")
}
return `${key}=${val}`
})
path = `/store/shipping-options?${queryString.join("&")}`
return this.client.request("GET", path)
}
}
export default ShippingOptionsResource
+27
View File
@@ -0,0 +1,27 @@
import { AxiosPromise } from "axios"
import { StoreSwapsRes, StorePostSwapsReq } from "@medusajs/medusa"
import BaseResource from "./base"
class SwapsResource extends BaseResource {
/**
* @description Creates a swap from a cart
* @param {StorePostSwapsReq} payload
* @return {AxiosPromise<StoreSwapsRes>}
*/
create(payload: StorePostSwapsReq): AxiosPromise<StoreSwapsRes> {
const path = `/store/swaps`
return this.client.request("POST", path, payload)
}
/**
* @description Retrieves a swap by cart id
* @param {string} cart_id id of cart
* @return {AxiosPromise<StoreSwapsRes>}
*/
retrieveByCartId(cart_id: string): AxiosPromise<StoreSwapsRes> {
const path = `/store/swaps/${cart_id}`
return this.client.request("GET", path)
}
}
export default SwapsResource
+22
View File
@@ -0,0 +1,22 @@
{
"compilerOptions": {
"lib": ["es5", "es6"],
"target": "es5",
"outDir": "./dist",
"esModuleInterop": true,
"declaration": true,
"module": "commonjs",
"moduleResolution": "node",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"sourceMap": true,
"noImplicitReturns": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"noImplicitThis": true,
"allowJs": true,
"skipLibCheck": true
},
"include": ["src"],
"exclude": ["node_modules", " **/tests/*"]
}
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -17,4 +17,4 @@ algolia Plugin for Medusa to search for products.
}
}
}
```
```
@@ -107,13 +107,13 @@ class AlgoliaService extends SearchService {
const { paginationOptions, filter, additionalOptions } = options
if ("limit" in paginationOptions) {
paginationOptions["length"] = paginationOptions.limit
delete paginationOptions.limit
delete paginationOptions.limit
}
return this.client_.initIndex(indexName).search(query, {
filters: filter,
...paginationOptions,
...additionalOptions
...additionalOptions,
})
}
-60
View File
@@ -1065,18 +1065,6 @@
exec-sh "^0.3.2"
minimist "^1.2.0"
"@hapi/hoek@^9.0.0":
version "9.2.1"
resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.2.1.tgz#9551142a1980503752536b5050fd99f4a7f13b17"
integrity sha512-gfta+H8aziZsm8pZa0vj04KO6biEiisppNgA1kbJvFrrWu9Vm7eaUEy76DIxsuTaWvti5fkJVhllWc6ZTE+Mdw==
"@hapi/topo@^5.0.0":
version "5.1.0"
resolved "https://registry.yarnpkg.com/@hapi/topo/-/topo-5.1.0.tgz#dc448e332c6c6e37a4dc02fd84ba8d44b9afb012"
integrity sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==
dependencies:
"@hapi/hoek" "^9.0.0"
"@istanbuljs/load-nyc-config@^1.0.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced"
@@ -1266,23 +1254,6 @@
resolved "https://registry.yarnpkg.com/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.3.tgz#323d72dd25103d0c4fbdce89dadf574a787b1f9b"
integrity sha512-s88O1aVtXftvp5bCPB7WnmXc5IwOZZ7YPuwNPt+GtOOXpPvad1LfbmjYv+qII7zP6RU2QGnqve27dnLycEnyEQ==
"@sideway/address@^4.1.0":
version "4.1.2"
resolved "https://registry.yarnpkg.com/@sideway/address/-/address-4.1.2.tgz#811b84333a335739d3969cfc434736268170cad1"
integrity sha512-idTz8ibqWFrPU8kMirL0CoPH/A29XOzzAzpyN3zQ4kAWnzmNfFmRaoMNN6VI8ske5M73HZyhIaW4OuSFIdM4oA==
dependencies:
"@hapi/hoek" "^9.0.0"
"@sideway/formula@^3.0.0":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@sideway/formula/-/formula-3.0.0.tgz#fe158aee32e6bd5de85044be615bc08478a0a13c"
integrity sha512-vHe7wZ4NOXVfkoRb8T5otiENVlT7a3IAiw7H5M2+GO+9CDgcVUUsX1zalAztCmwyOr2RUTGJdgB+ZvSVqmdHmg==
"@sideway/pinpoint@^2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@sideway/pinpoint/-/pinpoint-2.0.0.tgz#cff8ffadc372ad29fd3f78277aeb29e632cc70df"
integrity sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==
"@sinonjs/commons@^1.7.0":
version "1.8.3"
resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.3.tgz#3802ddd21a50a949b6721ddd72da36e67e7f1b2d"
@@ -3651,22 +3622,6 @@ jest@^25.5.2:
import-local "^3.0.2"
jest-cli "^25.5.4"
joi-objectid@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/joi-objectid/-/joi-objectid-3.0.1.tgz#63ace7860f8e1a993a28d40c40ffd8eff01a3668"
integrity sha512-V/3hbTlGpvJ03Me6DJbdBI08hBTasFOmipsauOsxOSnsF1blxV537WTl1zPwbfcKle4AK0Ma4OPnzMH4LlvTpQ==
joi@^17.3.0:
version "17.4.2"
resolved "https://registry.yarnpkg.com/joi/-/joi-17.4.2.tgz#02f4eb5cf88e515e614830239379dcbbe28ce7f7"
integrity sha512-Lm56PP+n0+Z2A2rfRvsfWVDXGEWjXxatPopkQ8qQ5mxCEhwHG+Ettgg5o98FFaxilOxozoa14cFhrE/hOzh/Nw==
dependencies:
"@hapi/hoek" "^9.0.0"
"@hapi/topo" "^5.0.0"
"@sideway/address" "^4.1.0"
"@sideway/formula" "^3.0.0"
"@sideway/pinpoint" "^2.0.0"
js-tokens@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
@@ -3897,21 +3852,6 @@ media-typer@0.3.0:
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=
medusa-core-utils@^1.1.20, medusa-core-utils@^1.1.26:
version "1.1.26"
resolved "https://registry.yarnpkg.com/medusa-core-utils/-/medusa-core-utils-1.1.26.tgz#fa1f54402c76e6a3991b9d22ae1e9d7609c86e8f"
integrity sha512-9bwy5aIdKPNxzlJAGrUEt7I1ExQjFS7zcWEDg3D5xjUzcpvP0OeVvV3joP9gUNHFjfEp983jzsmVlt1/bz31UA==
dependencies:
joi "^17.3.0"
joi-objectid "^3.0.1"
medusa-interfaces@1.x:
version "1.1.27"
resolved "https://registry.yarnpkg.com/medusa-interfaces/-/medusa-interfaces-1.1.27.tgz#fd5f5220dd752bcfd174e6eb5d072d731bd80966"
integrity sha512-dsQnsCdP2TQITOrHIZzyYyf4Lu3rW3AgeOzl6NmRWjf5A1KKP5cJcyIg4Te9xOtLUKeP70BvF+fAk7LechtE+Q==
dependencies:
medusa-core-utils "^1.1.26"
merge-stream@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60"
@@ -1,15 +0,0 @@
import { IdMap } from "medusa-test-utils"
export const PermissionServiceMock = {
hasPermission: jest.fn().mockImplementation((user, method, endpoint) => {
if (user._id === IdMap.getId("test-user")) {
return Promise.resolve(true)
}
}),
}
const mock = jest.fn().mockImplementation(() => {
return PermissionServiceMock
})
export default mock
@@ -1,354 +0,0 @@
import mongoose from "mongoose"
import { IdMap } from "medusa-test-utils"
import PermissionService from "../permission"
import { permissions, RoleModelMock } from "../../models/__mocks__/role"
describe("PermissionService", () => {
describe("hasPermission", () => {
let result
let user = {
_id: IdMap.getId("test-user"),
email: "oliver@medusa.test",
passwordHash: "123456789",
metadata: {
roles: ["product_editor"],
},
}
const permissionService = new PermissionService({
roleModel: RoleModelMock,
})
beforeAll(async () => {
jest.clearAllMocks()
result = await permissionService.hasPermission(user, "POST", "/products")
})
it("calls permission model functions", () => {
expect(RoleModelMock.findOne).toHaveBeenCalledTimes(
user.metadata.roles.length
)
})
it("successfully grants access to user", () => {
expect(result).toEqual(true)
})
it("succesfully denies access to user", async () => {
const accessDenied = await permissionService.hasPermission(
user,
"CREATE",
"/orders"
)
expect(accessDenied).toEqual(false)
})
})
describe("retrieveRole", () => {
let result
const permissionService = new PermissionService({
roleModel: RoleModelMock,
})
beforeAll(async () => {
jest.clearAllMocks()
result = await permissionService.retrieveRole("product_editor")
})
it("calls permission model functions", () => {
expect(RoleModelMock.findOne).toHaveBeenCalledTimes(1)
})
it("successfully fetches product editor permissions", () => {
expect(result).toEqual(permissions.productEditorPermission)
})
it("throws if role with name does not exist", async () => {
try {
await permissionService.retrieveRole("product_editor")
} catch (error) {
expect(error.message).toEqual(
"test_editor does not exist. Use method createRole to create it."
)
}
})
})
describe("createRole", () => {
const permissionService = new PermissionService({
roleModel: RoleModelMock,
})
beforeAll(async () => {
jest.clearAllMocks()
const contentEditorPermissions = [
{
method: "POST",
endpoint: "/contents",
},
{
method: "GET",
endpoint: "/contents",
},
{
method: "PUT",
endpoint: "/contents",
},
]
await permissionService.createRole(
"content_editor",
contentEditorPermissions
)
})
it("calls permission model functions", () => {
expect(RoleModelMock.create).toHaveBeenCalledTimes(1)
expect(RoleModelMock.create).toHaveBeenCalledWith({
name: "content_editor",
permissions: [
{
method: "POST",
endpoint: "/contents",
},
{
method: "GET",
endpoint: "/contents",
},
{
method: "PUT",
endpoint: "/contents",
},
],
})
})
it("throws if any permission is invalid", async () => {
try {
await permissionService.createRole("content_editor", [
{
method: "POST",
endpoint: "/products",
},
{
// Should fail since this is not a valid http request
method: "FETCH",
endpoint: "/products",
},
])
} catch (err) {
expect(err.message).toEqual("Permission is not valid")
}
})
it("throws if role with name already exists", async () => {
try {
await permissionService.createRole("product_editor", [
{
method: "POST",
endpoint: "/order",
},
])
} catch (err) {
expect(err.message).toEqual("product_editor already exists")
}
})
})
describe("grantRole", () => {
const setMetadataMock = jest.fn().mockReturnValue(Promise.resolve())
const userRetrieveMock = jest.fn().mockImplementation((data) => {
if (data === IdMap.getId("permission-user")) {
return Promise.resolve({
_id: IdMap.getId("permission-user"),
email: "oliver@test.dk",
metadata: {
roles: ["content_editor"],
},
})
}
if (data === IdMap.getId("user-without-roles")) {
return Promise.resolve({
_id: IdMap.getId("user-without-roles"),
email: "oliver@test.dk",
metadata: {},
})
}
return Promise.resolve(undefined)
})
const permissionService = new PermissionService({
roleModel: RoleModelMock,
userService: {
setMetadata: setMetadataMock,
retrieve: userRetrieveMock,
},
})
beforeEach(async () => {
jest.clearAllMocks()
})
it("successfully grants role to user", async () => {
await permissionService.grantRole(
IdMap.getId("permission-user"),
"product_editor"
)
expect(setMetadataMock).toHaveBeenCalledTimes(1)
expect(setMetadataMock).toHaveBeenCalledWith(
IdMap.getId("permission-user"),
"roles",
["content_editor", "product_editor"]
)
})
it("sets user metadata.roles to user that does not have metadata.roles", async () => {
await permissionService.grantRole(
IdMap.getId("user-without-roles"),
"product_editor"
)
expect(setMetadataMock).toHaveBeenCalledTimes(1)
expect(setMetadataMock).toHaveBeenCalledWith(
IdMap.getId("user-without-roles"),
"roles",
["product_editor"]
)
})
it("throws if user already has role", async () => {
try {
await permissionService.grantRole(
IdMap.getId("permission-user"),
"product_editor"
)
} catch (err) {
expect(err.message).toEqual("User already has role: product_editor")
}
})
})
describe("addPermission", () => {
const permissionService = new PermissionService({
roleModel: RoleModelMock,
})
beforeAll(async () => {
jest.clearAllMocks()
})
it("successfully adds permission", async () => {
const toAdd = {
method: "POST",
endpoint: "/products",
}
await permissionService.addPermission("product_editor", toAdd)
expect(RoleModelMock.updateOne).toHaveBeenCalledTimes(1)
expect(RoleModelMock.updateOne).toHaveBeenCalledWith(
{ _id: IdMap.getId("product_editor") },
{
$push: { permissions: { method: "POST", endpoint: "/products" } },
}
)
})
it("throws if permission is not valid", async () => {
const toAdd = {
method: "POST",
endpoint: 1234,
}
try {
await permissionService.addPermission("product_editor", toAdd)
} catch (err) {
expect(err.message).toEqual("Permission is not valid")
}
})
})
describe("removePermission", () => {
const permissionService = new PermissionService({
roleModel: RoleModelMock,
})
beforeAll(async () => {
jest.clearAllMocks()
})
it("successfully removes permission", async () => {
const toRemove = {
method: "POST",
endpoint: "/products",
}
await permissionService.removePermission("product_editor", toRemove)
expect(RoleModelMock.updateOne).toHaveBeenCalledTimes(1)
expect(RoleModelMock.updateOne).toHaveBeenCalledWith(
{ _id: IdMap.getId("product_editor") },
{
$pull: { permissions: { method: "POST", endpoint: "/products" } },
}
)
})
it("throws if permission is not valid", async () => {
const update = {
method: "FETCH",
endpoint: "/cart",
}
try {
await permissionService.addPermission("product_editor", update)
} catch (err) {
expect(err.message).toEqual("Permission is not valid")
}
})
})
describe("revokeRole", () => {
const setMetadataMock = jest.fn().mockReturnValue(Promise.resolve())
const userRetrieveMock = jest.fn().mockReturnValue(
Promise.resolve({
_id: IdMap.getId("product_editor"),
email: "oliver@test.dk",
metadata: {
roles: ["product_editor"],
},
})
)
const permissionService = new PermissionService({
roleModel: RoleModelMock,
userService: {
setMetadata: setMetadataMock,
retrieve: userRetrieveMock,
},
})
beforeEach(async () => {
jest.clearAllMocks()
})
it("successfully revokes a role from user", async () => {
await permissionService.revokeRole(
IdMap.getId("product_editor"),
"product_editor"
)
expect(setMetadataMock).toHaveBeenCalledTimes(1)
expect(setMetadataMock).toHaveBeenCalledWith(
IdMap.getId("product_editor"),
"roles",
[]
)
})
it("succeeds idempotently if user does not have the role to delete", async () => {
await permissionService.revokeRole(
IdMap.getId("product_editor"),
"content_editor"
)
expect(setMetadataMock).toHaveBeenCalledTimes(0)
})
})
})
+1
View File
@@ -0,0 +1 @@
declare module "medusa-interfaces"
+10 -5
View File
@@ -20,21 +20,24 @@
"@babel/cli": "^7.14.3",
"@babel/core": "^7.14.3",
"@babel/preset-typescript": "^7.13.0",
"@types/express": "^4.17.13",
"@types/jsonwebtoken": "^8.5.5",
"babel-preset-medusa-package": "^1.1.17",
"cross-env": "^5.2.1",
"eslint": "^6.8.0",
"eslint": "^7.32.0",
"jest": "^25.5.2",
"medusa-interfaces": "^1.1.27",
"nodemon": "^2.0.1",
"prettier": "^1.19.1",
"sqlite3": "^5.0.2",
"supertest": "^4.0.2"
"supertest": "^4.0.2",
"typescript": "^4.4.4"
},
"scripts": {
"start": "nodemon --watch plugins/ --watch src/ --exec babel-node src/app.js",
"watch": "babel -w src --out-dir dist/ --ignore **/__tests__ --extensions \".ts,.js\"",
"watch": "tsc --build --watch",
"prepare": "cross-env NODE_ENV=production npm run build",
"build": "babel src -d dist --ignore **/__tests__ --extensions \".ts,.js\"",
"build": "tsc --build",
"serve": "node dist/app.js",
"test": "jest",
"test:unit": "jest"
@@ -51,6 +54,8 @@
"body-parser": "^1.19.0",
"bull": "^3.12.1",
"chokidar": "^3.4.2",
"class-transformer": "^0.4.0",
"class-validator": "^0.13.1",
"connect-redis": "^5.0.0",
"cookie-parser": "^1.4.4",
"core-js": "^3.6.5",
@@ -88,4 +93,4 @@
"winston": "^3.2.1"
},
"gitHead": "5afd0dfbabaebfb6b09f0e2f055386ac76059372"
}
}
+18 -1
View File
@@ -1,7 +1,7 @@
import { Router } from "express"
import errorHandler from "./middlewares/error-handler"
import admin from "./routes/admin"
import store from "./routes/store"
import errorHandler from "./middlewares/error-handler"
// guaranteed to get dependencies
export default (container, config) => {
@@ -14,3 +14,20 @@ export default (container, config) => {
return app
}
export * from "./routes/admin/notifications"
export * from "./routes/admin/store"
export * from "./routes/admin/variants"
export * from "./routes/store/auth"
export * from "./routes/store/carts"
export * from "./routes/store/collections"
export * from "./routes/store/customers"
export * from "./routes/store/gift-cards"
export * from "./routes/store/orders"
export * from "./routes/store/products"
export * from "./routes/store/regions"
export * from "./routes/store/return-reasons"
export * from "./routes/store/returns"
export * from "./routes/store/shipping-options"
export * from "./routes/store/swaps"
export * from "./routes/store/variants"
@@ -1,21 +0,0 @@
import { MedusaError, Validator } from "medusa-core-utils"
export default async (req, res) => {
const schema = Validator.object().keys({
application_name: Validator.string().required(),
state: Validator.string().required(),
code: Validator.string().required(),
})
const { value, error } = schema.validate(req.body)
if (error) {
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
}
const oauthService = req.scope.resolve("oauthService")
const data = await oauthService.generateToken(
value.application_name,
value.code,
value.state
)
res.status(200).json({ apps: data })
}
@@ -0,0 +1,63 @@
import { IsNotEmpty, IsString } from "class-validator"
import { OauthService } from "../../../../services"
import { validator } from "../../../../utils/validator"
/**
* @oas [post] /apps
* operationId: "PostApps"
* summary: "Generates a token for an application."
* description: "Generates a token for an application."
* x-authenticated: true
* requestBody:
* content:
* application/json:
* schema:
* required:
* - application_name
* - state
* - code
* properties:
* application_name:
* type: string
* description: Name of the application for the token to be generated for.
* state:
* type: string
* description: State of the application.
* code:
* type: string
* description: The code for the generated token.
* tags:
* - Apps
* responses:
* "200":
* description: OK
* content:
* application/json:
* schema:
* properties:
* apps:
* $ref: "#/components/schemas/OAuth"
*/
export default async (req, res) => {
const validated = await validator(AdminPostAppsReq, req.body)
const oauthService: OauthService = req.scope.resolve("oauthService")
const data = await oauthService.generateToken(
validated.application_name,
validated.code,
validated.state
)
res.status(200).json({ apps: data })
}
export class AdminPostAppsReq {
@IsString()
@IsNotEmpty()
application_name: string
@IsString()
@IsNotEmpty()
state: string
@IsString()
@IsNotEmpty()
code: string
}
@@ -1,4 +1,5 @@
import { Router } from "express"
import { Oauth } from "../../../.."
import middlewares from "../../../middlewares"
const route = Router()
@@ -14,3 +15,11 @@ export default (app) => {
return app
}
export type AdminAppsRes = {
apps: Oauth
}
export type AdminAppsListRes = {
apps: Oauth[]
}
@@ -1,6 +0,0 @@
export default async (req, res) => {
const oauthService = req.scope.resolve("oauthService")
const data = await oauthService.list({})
res.status(200).json({ apps: data })
}
@@ -0,0 +1,26 @@
import { OauthService } from "../../../../services"
/**
* @oas [get] /apps
* operationId: "GetApps"
* summary: "List applications"
* description: "Retrieve a list of applications."
* x-authenticated: true
* tags:
* - Apps
* responses:
* "200":
* description: OK
* content:
* application/json:
* schema:
* properties:
* collection:
* $ref: "#/components/schemas/OAuth"
*/
export default async (req, res) => {
const oauthService: OauthService = req.scope.resolve("oauthService")
const data = await oauthService.list({})
res.status(200).json({ apps: data })
}
@@ -1,53 +0,0 @@
import _ from "lodash"
import jwt from "jsonwebtoken"
import { Validator } from "medusa-core-utils"
import config from "../../../../config"
/**
* @oas [post] /auth
* operationId: "PostAuth"
* summary: "Authenticate a User"
* description: "Logs a User in and authorizes them to manage Store settings."
* parameters:
* - (body) email=* {string} The User's email.
* - (body) password=* {string} The User's password.
* tags:
* - Auth
* responses:
* "200":
* description: OK
* content:
* application/json:
* schema:
* properties:
* user:
* $ref: "#/components/schemas/user"
*/
export default async (req, res) => {
const { body } = req
const schema = Validator.object().keys({
email: Validator.string().required(),
password: Validator.string().required(),
})
const { value, error } = schema.validate(body)
if (error) {
throw error
}
const authService = req.scope.resolve("authService")
const result = await authService.authenticate(value.email, value.password)
if (!result.success) {
res.sendStatus(401)
return
}
// Add JWT to cookie
req.session.jwt = jwt.sign({ userId: result.user.id }, config.jwtSecret, {
expiresIn: "24h",
})
const cleanRes = _.omit(result.user, ["password_hash"])
res.json({ user: cleanRes })
}
@@ -0,0 +1,67 @@
import _ from "lodash"
import jwt from "jsonwebtoken"
import config from "../../../../config"
import { validator } from "../../../../utils/validator"
import { IsEmail, IsNotEmpty, IsString } from "class-validator"
import AuthService from "../../../../services/auth"
import { MedusaError } from "medusa-core-utils"
/**
* @oas [post] /auth
* operationId: "PostAuth"
* summary: "Authenticate a User"
* x-authenticated: false
* description: "Logs a User in and authorizes them to manage Store settings."
* parameters:
* - (body) email=* {string} The User's email.
* - (body) password=* {string} The User's password.
* tags:
* - Auth
* responses:
* "200":
* description: OK
* content:
* application/json:
* schema:
* properties:
* user:
* $ref: "#/components/schemas/user"
*/
export default async (req, res) => {
if (!config.jwtSecret) {
throw new MedusaError(
MedusaError.Types.NOT_FOUND,
"Please configure jwtSecret in your environment"
)
}
const validated = await validator(AdminPostAuthReq, req.body)
const authService: AuthService = req.scope.resolve("authService")
const result = await authService.authenticate(
validated.email,
validated.password
)
if (result.success && result.user) {
// Add JWT to cookie
req.session.jwt = jwt.sign({ userId: result.user.id }, config.jwtSecret, {
expiresIn: "24h",
})
const cleanRes = _.omit(result.user, ["password_hash"])
res.json({ user: cleanRes })
} else {
res.sendStatus(401)
}
}
export class AdminPostAuthReq {
@IsEmail()
@IsNotEmpty()
email: string
@IsString()
@IsNotEmpty()
password: string
}
@@ -1,9 +1,8 @@
import _ from "lodash"
/**
* @oas [get] /auth
* operationId: "DeleteAuth"
* summary: "Delete Session"
* x-authenticated: true
* description: "Deletes the current session for the logged in user."
* tags:
* - Auth
@@ -1,9 +1,11 @@
import _ from "lodash"
import UserService from "../../../../services/user"
/**
* @oas [get] /auth
* operationId: "GetAuth"
* summary: "Get Session"
* x-authenticated: true
* description: "Gets the currently logged in User."
* tags:
* - Auth
@@ -18,9 +20,13 @@ import _ from "lodash"
* $ref: "#/components/schemas/user"
*/
export default async (req, res) => {
const userService = req.scope.resolve("userService")
const user = await userService.retrieve(req.user.userId)
try {
const userService: UserService = req.scope.resolve("userService")
const user = await userService.retrieve(req.user.userId)
const cleanRes = _.omit(user, ["password_hash"])
res.status(200).json({ user: cleanRes })
const cleanRes = _.omit(user, ["password_hash"])
res.status(200).json({ user: cleanRes })
} catch (err) {
res.sendStatus(400)
}
}
@@ -1,9 +1,10 @@
import { Router } from "express"
import { User } from "../../../.."
import middlewares from "../../../middlewares"
const route = Router()
export default app => {
export default (app) => {
app.use("/auth", route)
route.get(
@@ -21,3 +22,11 @@ export default app => {
return app
}
export type AdminAuthRes = {
user: Omit<User, "password_hash">
}
export * from "./create-session"
export * from "./delete-session"
export * from "./get-session"
@@ -59,7 +59,9 @@ describe("POST /admin/collections", () => {
it("returns error details", () => {
expect(subject.body.type).toEqual("invalid_data")
expect(subject.body.message[0].message).toEqual(`"title" is required`)
expect(subject.body.message).toEqual(
"title should not be empty, title must be a string"
)
})
})
})
@@ -1,10 +1,12 @@
import { MedusaError, Validator } from "medusa-core-utils"
import { IsNotEmpty, IsObject, IsOptional, IsString } from "class-validator"
import ProductCollectionService from "../../../../services/product-collection"
import { validator } from "../../../../utils/validator"
/**
* @oas [post] /collections
* operationId: "PostCollections"
* summary: "Create a Product Collection"
* description: "Creates a Product Collection."
* x-authenticated: true
* requestBody:
* content:
* application/json:
@@ -34,29 +36,28 @@ import { MedusaError, Validator } from "medusa-core-utils"
* $ref: "#/components/schemas/product_collection"
*/
export default async (req, res) => {
const schema = Validator.object().keys({
title: Validator.string().required(),
handle: Validator.string()
.optional()
.allow(""),
metadata: Validator.object().optional(),
})
const validated = await validator(AdminPostCollectionsReq, req.body)
const { value, error } = schema.validate(req.body)
if (error) {
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
}
const productCollectionService: ProductCollectionService = req.scope.resolve(
"productCollectionService"
)
try {
const productCollectionService = req.scope.resolve(
"productCollectionService"
)
const created = await productCollectionService.create(validated)
const collection = await productCollectionService.retrieve(created.id)
const created = await productCollectionService.create(value)
const collection = await productCollectionService.retrieve(created.id)
res.status(200).json({ collection })
}
res.status(200).json({ collection })
} catch (err) {
throw err
}
export class AdminPostCollectionsReq {
@IsString()
@IsNotEmpty()
title: string
@IsString()
@IsOptional()
handle?: string
@IsObject()
@IsOptional()
metadata?: object
}
@@ -1,8 +1,11 @@
import ProductCollectionService from "../../../../services/product-collection"
/**
* @oas [delete] /collections/{id}
* operationId: "DeleteCollectionsCollection"
* summary: "Delete a Product Collection"
* description: "Deletes a Product Collection."
* x-authenticated: true
* parameters:
* - (path) id=* {string} The id of the Collection.
* tags:
@@ -26,18 +29,14 @@
export default async (req, res) => {
const { id } = req.params
try {
const productCollectionService = req.scope.resolve(
"productCollectionService"
)
await productCollectionService.delete(id)
const productCollectionService: ProductCollectionService = req.scope.resolve(
"productCollectionService"
)
await productCollectionService.delete(id)
res.json({
id,
object: "product-collection",
deleted: true,
})
} catch (err) {
throw err
}
res.json({
id,
object: "product-collection",
deleted: true,
})
}
@@ -1,8 +1,10 @@
import ProductCollectionService from "../../../../services/product-collection"
/**
* @oas [get] /collections/{id}
* operationId: "GetCollectionsCollection"
* summary: "Retrieve a Product Collection"
* description: "Retrieves a Product Collection."
* x-authenticated: true
* parameters:
* - (path) id=* {string} The id of the Product Collection
* tags:
@@ -17,17 +19,13 @@
* collection:
* $ref: "#/components/schemas/product_collection"
*/
export default async (req, res) => {
const { id } = req.params
try {
const productCollectionService = req.scope.resolve(
"productCollectionService"
)
const collection = await productCollectionService.retrieve(id)
res.status(200).json({ collection })
} catch (err) {
throw err
}
const productCollectionService: ProductCollectionService = req.scope.resolve(
"productCollectionService"
)
const collection = await productCollectionService.retrieve(id)
res.status(200).json({ collection })
}
@@ -1,21 +0,0 @@
import { Router } from "express"
import middlewares from "../../../middlewares"
const route = Router()
export default app => {
app.use("/collections", route)
route.post("/", middlewares.wrap(require("./create-collection").default))
route.post("/:id", middlewares.wrap(require("./update-collection").default))
route.delete("/:id", middlewares.wrap(require("./delete-collection").default))
route.get("/:id", middlewares.wrap(require("./get-collection").default))
route.get("/", middlewares.wrap(require("./list-collections").default))
return app
}
export const defaultFields = ["id", "title", "handle"]
export const defaultRelations = ["products"]
@@ -0,0 +1,40 @@
import { Router } from "express"
import { ProductCollection } from "../../../.."
import { DeleteResponse, PaginatedResponse } from "../../../../types/common"
import middlewares from "../../../middlewares"
import "reflect-metadata"
const route = Router()
export default (app) => {
app.use("/collections", route)
route.post("/", middlewares.wrap(require("./create-collection").default))
route.post("/:id", middlewares.wrap(require("./update-collection").default))
route.delete("/:id", middlewares.wrap(require("./delete-collection").default))
route.get("/:id", middlewares.wrap(require("./get-collection").default))
route.get("/", middlewares.wrap(require("./list-collections").default))
return app
}
export const defaultAdminCollectionsFields = ["id", "title", "handle"]
export const defaultAdminCollectionsRelations = ["products"]
export type AdminCollectionsListRes = PaginatedResponse & {
collections: ProductCollection[]
}
export type AdminCollectionsDeleteRes = DeleteResponse
export type AdminCollectionsRes = {
collection: ProductCollection
}
export * from "./create-collection"
export * from "./delete-collection"
export * from "./get-collection"
export * from "./list-collections"
export * from "./update-collection"
@@ -1,41 +0,0 @@
import { defaultFields, defaultRelations } from "."
/**
* @oas [get] /collections
* operationId: "GetCollections"
* summary: "List Product Collections"
* description: "Retrieve a list of Product Collection."
* tags:
* - Collection
* responses:
* "200":
* description: OK
* content:
* application/json:
* schema:
* properties:
* collection:
* $ref: "#/components/schemas/product_collection"
*/
export default async (req, res) => {
const selector = {}
const limit = parseInt(req.query.limit) || 10
const offset = parseInt(req.query.offset) || 0
const productCollectionService = req.scope.resolve("productCollectionService")
const listConfig = {
select: defaultFields,
relations: defaultRelations,
skip: offset,
take: limit,
}
const [collections, count] = await productCollectionService.listAndCount(
selector,
listConfig
)
res.status(200).json({ collections, count })
}
@@ -0,0 +1,67 @@
import { Type } from "class-transformer"
import { IsNumber, IsOptional } from "class-validator"
import {
defaultAdminCollectionsFields,
defaultAdminCollectionsRelations,
} from "."
import ProductCollectionService from "../../../../services/product-collection"
import { validator } from "../../../../utils/validator"
/**
* @oas [get] /collections
* operationId: "GetCollections"
* summary: "List Product Collections"
* description: "Retrieve a list of Product Collection."
* x-authenticated: true
* parameters:
* - (path) limit {string} The number of collections to return.
* - (path) offset {string} The offset of collections to return.
* tags:
* - Collection
* responses:
* "200":
* description: OK
* content:
* application/json:
* schema:
* properties:
* collection:
* $ref: "#/components/schemas/product_collection"
*/
export default async (req, res) => {
const validated = await validator(AdminGetCollectionsParams, req.query)
const productCollectionService: ProductCollectionService = req.scope.resolve(
"productCollectionService"
)
const listConfig = {
select: defaultAdminCollectionsFields,
relations: defaultAdminCollectionsRelations,
skip: validated.offset,
take: validated.limit,
}
const [collections, count] = await productCollectionService.listAndCount(
{},
listConfig
)
res.status(200).json({
collections,
count,
offset: validated.offset,
limit: validated.limit,
})
}
export class AdminGetCollectionsParams {
@IsNumber()
@IsOptional()
@Type(() => Number)
limit = 10
@IsNumber()
@IsOptional()
@Type(() => Number)
offset = 0
}
@@ -1,10 +1,12 @@
import { MedusaError, Validator } from "medusa-core-utils"
import { IsObject, IsOptional, IsString } from "class-validator"
import ProductCollectionService from "../../../../services/product-collection"
import { validator } from "../../../../utils/validator"
/**
* @oas [post] /collections/{id}
* operationId: "PostCollectionsCollection"
* summary: "Update a Product Collection"
* description: "Updates a Product Collection."
* x-authenticated: true
* parameters:
* - (path) id=* {string} The id of the Collection.
* requestBody:
@@ -36,27 +38,27 @@ import { MedusaError, Validator } from "medusa-core-utils"
export default async (req, res) => {
const { id } = req.params
const schema = Validator.object().keys({
title: Validator.string().optional(),
handle: Validator.string().optional(),
metadata: Validator.object().optional(),
})
const validated = await validator(AdminPostCollectionsCollectionReq, req.body)
const productCollectionService: ProductCollectionService = req.scope.resolve(
"productCollectionService"
)
const { value, error } = schema.validate(req.body)
if (error) {
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
}
const updated = await productCollectionService.update(id, validated)
const collection = await productCollectionService.retrieve(updated.id)
try {
const productCollectionService = req.scope.resolve(
"productCollectionService"
)
res.status(200).json({ collection })
}
const updated = await productCollectionService.update(id, value)
const collection = await productCollectionService.retrieve(updated.id)
export class AdminPostCollectionsCollectionReq {
@IsString()
@IsOptional()
title?: string
res.status(200).json({ collection })
} catch (err) {
throw err
}
@IsString()
@IsOptional()
handle?: string
@IsObject()
@IsOptional()
metadata?: object
}
@@ -1,10 +1,12 @@
import { Validator, MedusaError } from "medusa-core-utils"
import { IsEmail, IsOptional, IsString } from "class-validator"
import { CustomerService } from "../../../../services"
import { validator } from "../../../../utils/validator"
/**
* @oas [post] /customers
* operationId: "PostCustomers"
* summary: "Create a Customer"
* description: "Creates a Customer."
* x-authenticated: true
* parameters:
* - (body) email=* {string} The Customer's email address.
* - (body) first_name=* {string} The Customer's first name.
@@ -23,20 +25,27 @@ import { Validator, MedusaError } from "medusa-core-utils"
* $ref: "#/components/schemas/customer"
*/
export default async (req, res) => {
const schema = Validator.object().keys({
email: Validator.string().email().required(),
first_name: Validator.string().required(),
last_name: Validator.string().required(),
password: Validator.string().required(),
phone: Validator.string().optional(),
})
const validated = await validator(AdminPostCustomersReq, req.bodyn)
const { value, error } = schema.validate(req.body)
if (error) {
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
}
const customerService = req.scope.resolve("customerService")
const customer = await customerService.create(value)
const customerService: CustomerService = req.scope.resolve("customerService")
const customer = await customerService.create(validated)
res.status(201).json({ customer })
}
export class AdminPostCustomersReq {
@IsEmail()
email: string
@IsString()
first_name: string
@IsString()
last_name: string
@IsString()
password: string
@IsString()
@IsOptional()
phone?: string
}
@@ -1,8 +1,11 @@
import CustomerService from "../../../../services/customer"
/**
* @oas [get] /customers/{id}
* operationId: "GetCustomersCustomer"
* summary: "Retrieve a Customer"
* description: "Retrieves a Customer."
* x-authenticated: true
* parameters:
* - (path) id=* {string} The id of the Customer.
* tags:
@@ -19,7 +22,7 @@
*/
export default async (req, res) => {
const { id } = req.params
const customerService = req.scope.resolve("customerService")
const customerService: CustomerService = req.scope.resolve("customerService")
const customer = await customerService.retrieve(id, {
relations: ["orders", "shipping_addresses"],
})
@@ -1,4 +1,6 @@
import { Router } from "express"
import { Customer } from "../../../.."
import { DeleteResponse, PaginatedResponse } from "../../../../types/common"
import middlewares from "../../../middlewares"
const route = Router()
@@ -13,3 +15,18 @@ export default (app) => {
route.post("/:id", middlewares.wrap(require("./update-customer").default))
return app
}
export type AdminCustomersRes = {
customer: Customer
}
export type AdminCustomersDeleteRes = DeleteResponse
export type AdminCustomersListRes = PaginatedResponse & {
customers: Customer[]
}
export * from "./create-customer"
export * from "./get-customer"
export * from "./list-customers"
export * from "./update-customer"
@@ -1,47 +0,0 @@
/**
* @oas [get] /customers
* operationId: "GetCustomers"
* summary: "List Customers"
* description: "Retrieves a list of Customers."
* tags:
* - Customer
* responses:
* 200:
* description: OK
* content:
* application/json:
* schema:
* properties:
* customer:
* $ref: "#/components/schemas/customer"
*/
export default async (req, res) => {
const customerService = req.scope.resolve("customerService")
const limit = parseInt(req.query.limit) || 50
const offset = parseInt(req.query.offset) || 0
const selector = {}
if ("q" in req.query) {
selector.q = req.query.q
}
let expandFields = []
if ("expand" in req.query) {
expandFields = req.query.expand.split(",")
}
const listConfig = {
relations: expandFields.length ? expandFields : [],
skip: offset,
take: limit,
}
const [customers, count] = await customerService.listAndCount(
selector,
listConfig
)
res.json({ customers, count, offset, limit })
}
@@ -0,0 +1,75 @@
import { Type } from "class-transformer"
import { IsNumber, IsOptional, IsString } from "class-validator"
import { Customer } from "../../../.."
import CustomerService from "../../../../services/customer"
import { FindConfig } from "../../../../types/common"
import { AdminListCustomerSelector } from "../../../../types/customers"
import { validator } from "../../../../utils/validator"
/**
* @oas [get] /customers
* operationId: "GetCustomers"
* summary: "List Customers"
* description: "Retrieves a list of Customers."
* x-authenticated: true
* tags:
* - Customer
* responses:
* 200:
* description: OK
* content:
* application/json:
* schema:
* properties:
* customer:
* $ref: "#/components/schemas/customer"
*/
export default async (req, res) => {
const validated = await validator(AdminGetCustomersParams, req.query)
const customerService: CustomerService = req.scope.resolve("customerService")
const selector: AdminListCustomerSelector = {}
if (validated.q) {
selector.q = validated.q
}
let expandFields: string[] = []
if (validated.expand) {
expandFields = validated.expand.split(",")
}
const listConfig: FindConfig<Customer> = {
relations: expandFields,
skip: validated.offset,
take: validated.limit,
}
const [customers, count] = await customerService.listAndCount(
selector,
listConfig
)
res.json({
customers,
count,
offset: validated.offset,
limit: validated.limit,
})
}
export class AdminGetCustomersParams extends AdminListCustomerSelector {
@IsNumber()
@IsOptional()
@Type(() => Number)
limit = 50
@IsNumber()
@IsOptional()
@Type(() => Number)
offset = 0
@IsString()
@IsOptional()
expand?: string
}
@@ -1,10 +1,13 @@
import { Validator, MedusaError } from "medusa-core-utils"
import { IsEmail, IsOptional, IsString } from "class-validator"
import CustomerService from "../../../../services/customer"
import { MedusaError } from "medusa-core-utils"
import { validator } from "../../../../utils/validator"
/**
* @oas [post] /customers/{id}
* operationId: "PostCustomersCustomer"
* summary: "Update a Customer"
* description: "Updates a Customer."
* x-authenticated: true
* parameters:
* - (path) id=* {string} The id of the Customer.
* requestBody:
@@ -22,8 +25,11 @@ import { Validator, MedusaError } from "medusa-core-utils"
* type: string
* description: The Customer's last name.
* phone:
* type: string
* description: The Customer's phone number.
* type: object
* password:
* type: string
* description: The Customer's password.
* tags:
* - Customer
* responses:
@@ -39,34 +45,45 @@ import { Validator, MedusaError } from "medusa-core-utils"
export default async (req, res) => {
const { id } = req.params
const schema = Validator.object().keys({
email: Validator.string().optional(),
first_name: Validator.string().optional(),
last_name: Validator.string().optional(),
password: Validator.string().optional(),
phone: Validator.string().optional(),
})
const validated = await validator(AdminPostCustomersCustomerReq, req.body)
const { value, error } = schema.validate(req.body)
if (error) {
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
}
const customerService = req.scope.resolve("customerService")
const customerService: CustomerService = req.scope.resolve("customerService")
let customer = await customerService.retrieve(id)
if (value.email && customer.has_account) {
if (validated.email && customer.has_account) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
"Email cannot be changed when the user has registered their account"
)
}
await customerService.update(id, value)
await customerService.update(id, validated)
customer = await customerService.retrieve(id, {
relations: ["orders"],
})
res.status(200).json({ customer })
}
export class AdminPostCustomersCustomerReq {
@IsEmail()
@IsOptional()
email?: string
@IsString()
@IsOptional()
first_name?: string
@IsString()
@IsOptional()
last_name?: string
@IsString()
@IsOptional()
password?: string
@IsString()
@IsOptional()
phone?: string
}
@@ -80,7 +80,7 @@ describe("POST /admin/discounts", () => {
})
it("returns error", () => {
expect(subject.body.message[0].message).toEqual(
expect(subject.body.message).toEqual(
`"valid_duration" must be a valid ISO 8601 duration`
)
})
@@ -160,7 +160,9 @@ describe("POST /admin/discounts", () => {
})
it("returns error", () => {
expect(subject.body.message[0].message).toEqual(`"rule.type" is required`)
expect(subject.body.message).toEqual(
`type should not be empty, type must be a string`
)
})
})
@@ -193,8 +195,8 @@ describe("POST /admin/discounts", () => {
})
it("returns error", () => {
expect(subject.body.message[0].message).toEqual(
`"ends_at" must be greater than "ref:starts_at"`
expect(subject.body.message).toEqual(
`"ends_at" must be greater than "starts_at"`
)
})
})
@@ -231,8 +233,8 @@ describe("POST /admin/discounts", () => {
it("returns error", () => {
expect(DiscountServiceMock.create).toHaveBeenCalledWith({
code: "TEST",
is_dynamic: true,
is_disabled: false,
is_dynamic: true,
is_disabled: false,
rule: {
description: "Test",
type: "fixed",
@@ -1,5 +1,5 @@
import { IdMap } from "medusa-test-utils"
import { defaultFields, defaultRelations } from ".."
import { defaultAdminDiscountsFields, defaultAdminDiscountsRelations } from ".."
import { request } from "../../../../../helpers/test-request"
import { DiscountServiceMock } from "../../../../../services/__mocks__/discount"
@@ -27,8 +27,8 @@ describe("GET /admin/discounts", () => {
expect(DiscountServiceMock.listAndCount).toHaveBeenCalledWith(
{},
{
select: defaultFields,
relations: defaultRelations,
select: defaultAdminDiscountsFields,
relations: defaultAdminDiscountsRelations,
skip: 0,
take: 20,
order: { created_at: "DESC" },
@@ -64,8 +64,8 @@ describe("GET /admin/discounts", () => {
expect(DiscountServiceMock.listAndCount).toHaveBeenCalledWith(
{ q: "OLI", is_dynamic: false, is_disabled: false },
{
select: defaultFields,
relations: defaultRelations,
select: defaultAdminDiscountsFields,
relations: defaultAdminDiscountsRelations,
skip: 20,
take: 40,
order: { created_at: "DESC" },
@@ -97,8 +97,8 @@ describe("GET /admin/discounts", () => {
expect(DiscountServiceMock.listAndCount).toHaveBeenCalledWith(
{ is_dynamic: true },
{
select: defaultFields,
relations: defaultRelations,
select: defaultAdminDiscountsFields,
relations: defaultAdminDiscountsRelations,
skip: 0,
take: 20,
order: { created_at: "DESC" },
@@ -130,8 +130,8 @@ describe("GET /admin/discounts", () => {
expect(DiscountServiceMock.listAndCount).toHaveBeenCalledWith(
{ is_disabled: true },
{
select: defaultFields,
relations: defaultRelations,
select: defaultAdminDiscountsFields,
relations: defaultAdminDiscountsRelations,
skip: 0,
take: 20,
order: { created_at: "DESC" },
@@ -86,7 +86,7 @@ describe("POST /admin/discounts", () => {
})
it("returns error", () => {
expect(subject.body.message[0].message).toEqual(
expect(subject.body.message).toEqual(
`"valid_duration" must be a valid ISO 8601 duration`
)
})
@@ -180,8 +180,8 @@ describe("POST /admin/discounts", () => {
})
it("returns error", () => {
expect(subject.body.message[0].message).toEqual(
`"ends_at" must be greater than "ref:starts_at"`
expect(subject.body.message).toEqual(
`"ends_at" must be greater than "starts_at"`
)
})
})
@@ -1,10 +1,12 @@
import { defaultFields, defaultRelations } from "./"
import { defaultAdminDiscountsFields, defaultAdminDiscountsRelations } from "."
import { Discount } from "../../../.."
import DiscountService from "../../../../services/discount"
/**
* @oas [post] /discounts/{id}/regions/{region_id}
* operationId: "PostDiscountsDiscountRegionsRegion"
* summary: "Adds Region availability"
* description: "Adds a Region to the list of Regions that a Discount can be used in."
* x-authenticated: true
* parameters:
* - (path) id=* {string} The id of the Discount.
* - (path) region_id=* {string} The id of the Region.
@@ -22,12 +24,13 @@ import { defaultFields, defaultRelations } from "./"
*/
export default async (req, res) => {
const { discount_id, region_id } = req.params
const discountService = req.scope.resolve("discountService")
const discountService: DiscountService = req.scope.resolve("discountService")
await discountService.addRegion(discount_id, region_id)
const discount = await discountService.retrieve(discount_id, {
select: defaultFields,
relations: defaultRelations,
const discount: Discount = await discountService.retrieve(discount_id, {
select: defaultAdminDiscountsFields,
relations: defaultAdminDiscountsRelations,
})
res.status(200).json({ discount })
@@ -1,13 +1,15 @@
import { defaultFields, defaultRelations } from "./"
import { defaultAdminDiscountsFields, defaultAdminDiscountsRelations } from "."
import { Discount } from "../../../.."
import DiscountService from "../../../../services/discount"
/**
* @oas [post] /discounts/{id}/products/{product_id}
* @oas [post] /discounts/{id}/products/{variant_id}
* operationId: "PostDiscountsDiscountProductsProduct"
* summary: "Adds Product availability"
* description: "Adds a Product to the list of Products that a Discount can be used for."
* x-authenticated: true
* parameters:
* - (path) id=* {string} The id of the Discount.
* - (path) product_id=* {string} The id of the Product.
* - (path) variant_id=* {string} The id of the Product.
* tags:
* - Discount
* responses:
@@ -22,12 +24,13 @@ import { defaultFields, defaultRelations } from "./"
*/
export default async (req, res) => {
const { discount_id, variant_id } = req.params
const discountService = req.scope.resolve("discountService")
const discountService: DiscountService = req.scope.resolve("discountService")
await discountService.addValidProduct(discount_id, variant_id)
const discount = await discountService.retrieve(discount_id, {
select: defaultFields,
relations: defaultRelations,
const discount: Discount = await discountService.retrieve(discount_id, {
select: defaultAdminDiscountsFields,
relations: defaultAdminDiscountsRelations,
})
res.status(200).json({ discount })
@@ -1,14 +1,33 @@
import { MedusaError, Validator } from "medusa-core-utils"
import { defaultRelations } from "."
import { Type } from "class-transformer"
import {
IsArray,
IsBoolean,
IsDate,
IsNotEmpty,
IsNumber,
IsObject,
IsOptional,
IsPositive,
IsString,
ValidateNested,
} from "class-validator"
import { defaultAdminDiscountsRelations } from "."
import DiscountService from "../../../../services/discount"
import { IsGreaterThan } from "../../../../utils/validators/greater-than"
import { validator } from "../../../../utils/validator"
import { IsISO8601Duration } from "../../../../utils/validators/iso8601-duration"
/**
* @oas [post] /discounts
* operationId: "PostDiscounts"
* summary: "Creates a Discount"
* x-authenticated: true
* description: "Creates a Discount with a given set of rules that define how the Discount behaves."
* requestBody:
* content:
* application/json:
* required:
* - code
* - rule
* schema:
* properties:
* code:
@@ -56,36 +75,84 @@ import { defaultRelations } from "."
* $ref: "#/components/schemas/discount"
*/
export default async (req, res) => {
const schema = Validator.object().keys({
code: Validator.string().required(),
is_dynamic: Validator.boolean().default(false),
rule: Validator.object()
.keys({
description: Validator.string().optional(),
type: Validator.string().required(),
value: Validator.number().positive().required(),
allocation: Validator.string().required(),
valid_for: Validator.array().items(Validator.string()),
})
.required(),
is_disabled: Validator.boolean().default(false),
starts_at: Validator.date().optional(),
ends_at: Validator.date().greater(Validator.ref("starts_at")).optional(),
valid_duration: Validator.string().isoDuration().allow(null).optional(),
usage_limit: Validator.number().positive().optional(),
regions: Validator.array().items(Validator.string()).optional(),
metadata: Validator.object().optional(),
})
const validated = await validator(AdminPostDiscountsReq, req.body)
const { value, error } = schema.validate(req.body)
if (error) {
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
}
const discountService = req.scope.resolve("discountService")
const created = await discountService.create(value)
const discount = await discountService.retrieve(created.id, defaultRelations)
const discountService: DiscountService = req.scope.resolve("discountService")
const created = await discountService.create(validated)
const discount = await discountService.retrieve(
created.id,
defaultAdminDiscountsRelations
)
res.status(200).json({ discount })
}
export class AdminPostDiscountsReq {
@IsString()
@IsNotEmpty()
code: string
@IsNotEmpty()
@ValidateNested()
@Type(() => AdminPostDiscountsDiscountRule)
rule: AdminPostDiscountsDiscountRule
@IsBoolean()
@IsOptional()
is_dynamic = false
@IsBoolean()
@IsOptional()
is_disabled = false
@IsDate()
@IsOptional()
@Type(() => Date)
starts_at?: Date
@IsDate()
@IsOptional()
@IsGreaterThan("starts_at")
@Type(() => Date)
ends_at?: Date
@IsISO8601Duration()
@IsOptional()
valid_duration?: string
@IsNumber()
@IsOptional()
@IsPositive()
usage_limit?: number
@IsArray()
@IsOptional()
@IsString({ each: true })
regions?: string[]
@IsObject()
@IsOptional()
metadata?: object
}
export class AdminPostDiscountsDiscountRule {
@IsString()
@IsOptional()
description?: string
@IsString()
@IsNotEmpty()
type: string
@IsNumber()
value: number
@IsString()
@IsNotEmpty()
allocation: string
@IsOptional()
@IsArray()
@IsString({ each: true })
valid_for?: string[]
}
@@ -1,10 +1,18 @@
import { MedusaError, Validator } from "medusa-core-utils"
import {
IsNotEmpty,
IsNumber,
IsObject,
IsOptional,
IsString,
} from "class-validator"
import DiscountService from "../../../../services/discount"
import { validator } from "../../../../utils/validator"
/**
* @oas [post] /discounts/{id}/dynamic-codes
* operationId: "PostDiscountsDiscountDynamicCodes"
* summary: "Create a dynamic Discount code"
* description: "Creates a unique code that can map to a parent Discount. This is useful if you want to automatically generate codes with the same behaviour."
* x-authenticated: true
* parameters:
* - (path) id=* {string} The id of the Discount to create the dynamic code from."
* - (body) code=* {string} The unique code that will be used to redeem the Discount.
@@ -24,20 +32,16 @@ import { MedusaError, Validator } from "medusa-core-utils"
export default async (req, res) => {
const { discount_id } = req.params
const schema = Validator.object().keys({
code: Validator.string().required(),
usage_limit: Validator.number().default(1),
metadata: Validator.object().optional(),
})
const validated = await validator(
AdminPostDiscountsDiscountDynamicCodesReq,
req.body
)
const { value, error } = schema.validate(req.body)
if (error) {
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
}
const discountService = req.scope.resolve("discountService")
const created = await discountService.createDynamicCode(discount_id, value)
const discountService: DiscountService = req.scope.resolve("discountService")
const created = await discountService.createDynamicCode(
discount_id,
validated
)
const discount = await discountService.retrieve(created.id, {
relations: ["rule", "rule.valid_for", "regions"],
@@ -45,3 +49,17 @@ export default async (req, res) => {
res.status(200).json({ discount })
}
export class AdminPostDiscountsDiscountDynamicCodesReq {
@IsString()
@IsNotEmpty()
code: string
@IsNumber()
@IsOptional()
usage_limit = 1
@IsObject()
@IsOptional()
metadata?: object
}
@@ -1,8 +1,11 @@
import DiscountService from "../../../../services/discount"
/**
* @oas [delete] /discounts/{id}
* operationId: "DeleteDiscountsDiscount"
* summary: "Delete a Discount"
* description: "Deletes a Discount."
* x-authenticated: true
* parameters:
* - (path) id=* {string} The id of the Discount
* tags:
@@ -25,7 +28,8 @@
*/
export default async (req, res) => {
const { discount_id } = req.params
const discountService = req.scope.resolve("discountService")
const discountService: DiscountService = req.scope.resolve("discountService")
await discountService.delete(discount_id)
res.json({
@@ -1,8 +1,11 @@
import DiscountService from "../../../../services/discount"
/**
* @oas [delete] /discounts/{id}/dynamic-codes/{code}
* operationId: "DeleteDiscountsDiscountDynamicCodesCode"
* summary: "Delete a dynamic code"
* description: "Deletes a dynamic code from a Discount."
* x-authenticated: true
* parameters:
* - (path) id=* {string} The id of the Discount
* - (path) code=* {string} The id of the Discount
@@ -20,7 +23,8 @@
*/
export default async (req, res) => {
const { discount_id, code } = req.params
const discountService = req.scope.resolve("discountService")
const discountService: DiscountService = req.scope.resolve("discountService")
await discountService.deleteDynamicCode(discount_id, code)
const discount = await discountService.retrieve(discount_id, {
@@ -1,10 +1,11 @@
import { defaultRelations } from "./"
import { defaultAdminDiscountsRelations } from "."
import DiscountService from "../../../../services/discount"
/**
* @oas [get] /discounts/code/{code}
* operationId: "GetDiscountsDiscountCode"
* summary: "Retrieve a Discount by code"
* description: "Retrieves a Discount by its discount code"
* x-authenticated: true
* parameters:
* - (path) code=* {string} The code of the Discount
* tags:
@@ -21,8 +22,12 @@ import { defaultRelations } from "./"
*/
export default async (req, res) => {
const { code } = req.params
const discountService = req.scope.resolve("discountService")
const discount = await discountService.retrieveByCode(code, defaultRelations)
const discountService: DiscountService = req.scope.resolve("discountService")
const discount = await discountService.retrieveByCode(
code,
defaultAdminDiscountsRelations
)
res.status(200).json({ discount })
}
@@ -1,10 +1,11 @@
import { defaultFields, defaultRelations } from "./"
import { defaultAdminDiscountsFields, defaultAdminDiscountsRelations } from "."
import DiscountService from "../../../../services/discount"
/**
* @oas [get] /discounts/{id}
* operationId: "GetDiscountsDiscount"
* summary: "Retrieve a Discount"
* description: "Retrieves a Discount"
* x-authenticated: true
* parameters:
* - (path) id=* {string} The id of the Discount
* tags:
@@ -21,10 +22,11 @@ import { defaultFields, defaultRelations } from "./"
*/
export default async (req, res) => {
const { discount_id } = req.params
const discountService = req.scope.resolve("discountService")
const discountService: DiscountService = req.scope.resolve("discountService")
const data = await discountService.retrieve(discount_id, {
select: defaultFields,
relations: defaultRelations,
select: defaultAdminDiscountsFields,
relations: defaultAdminDiscountsRelations,
})
res.status(200).json({ discount: data })

Some files were not shown because too many files have changed in this diff Show More