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:
co-authored by
Philip Korsholm
Zakaria El Asri
Kasper Fabricius Kristensen
Philip Korsholm
Sebastian Rindom
parent
55e200bf68
commit
373532ecbc
@@ -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
@@ -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")
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
+1614
-1606
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -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,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"]
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
+12
-7
@@ -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
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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"]
|
||||
}
|
||||
+1267
-1578
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
node_modules
|
||||
/dist
|
||||
@@ -0,0 +1 @@
|
||||
# Medusa Commerce JS Client
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"transform": {
|
||||
"^.+\\.(t|j)sx?$": "ts-jest"
|
||||
},
|
||||
"testRegex": "(/tests/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$",
|
||||
"moduleFileExtensions": ["ts", "tsx", "js", "jsx", "json", "node"]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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 {}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,9 @@
|
||||
import Client from "../request"
|
||||
|
||||
export default class BaseResource {
|
||||
public client: Client
|
||||
|
||||
constructor(client: Client) {
|
||||
this.client = client
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
@@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
})
|
||||
})
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
declare module "medusa-interfaces"
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
+9
@@ -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
-2
@@ -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
|
||||
+10
-4
@@ -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)
|
||||
}
|
||||
}
|
||||
+10
-1
@@ -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"
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
+24
-23
@@ -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
|
||||
}
|
||||
+12
-13
@@ -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,
|
||||
})
|
||||
}
|
||||
+8
-10
@@ -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
|
||||
}
|
||||
+23
-21
@@ -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
|
||||
}
|
||||
+25
-16
@@ -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
|
||||
}
|
||||
+4
-1
@@ -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"],
|
||||
})
|
||||
+17
@@ -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
|
||||
}
|
||||
+35
-18
@@ -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"`
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
+9
-6
@@ -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 })
|
||||
+11
-8
@@ -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 })
|
||||
+99
-32
@@ -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[]
|
||||
}
|
||||
+33
-15
@@ -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
|
||||
}
|
||||
+5
-1
@@ -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({
|
||||
+5
-1
@@ -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, {
|
||||
+9
-4
@@ -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 })
|
||||
}
|
||||
+7
-5
@@ -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
Reference in New Issue
Block a user