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:
413 files changed
+20957
-10349
No files matched your search
@@ -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 })
|
||||
+28
-2
@@ -1,5 +1,8 @@
|
||||
import { Router } from "express"
|
||||
import { Discount } from "../../../.."
|
||||
import middlewares from "../../../middlewares"
|
||||
import "reflect-metadata"
|
||||
import { DeleteResponse, PaginatedResponse } from "../../../../types/common"
|
||||
|
||||
const route = Router()
|
||||
|
||||
@@ -59,7 +62,7 @@ export default (app) => {
|
||||
return app
|
||||
}
|
||||
|
||||
export const defaultFields = [
|
||||
export const defaultAdminDiscountsFields = [
|
||||
"id",
|
||||
"code",
|
||||
"is_dynamic",
|
||||
@@ -77,9 +80,32 @@ export const defaultFields = [
|
||||
"valid_duration",
|
||||
]
|
||||
|
||||
export const defaultRelations = [
|
||||
export const defaultAdminDiscountsRelations = [
|
||||
"rule",
|
||||
"parent_discount",
|
||||
"regions",
|
||||
"rule.valid_for",
|
||||
]
|
||||
|
||||
export type AdminDiscountsRes = {
|
||||
discount: Discount
|
||||
}
|
||||
|
||||
export type AdminDiscountsDeleteRes = DeleteResponse
|
||||
|
||||
export type AdminDiscountsListRes = PaginatedResponse & {
|
||||
discounts: Discount[]
|
||||
}
|
||||
|
||||
export * from "./add-region"
|
||||
export * from "./add-valid-product"
|
||||
export * from "./create-discount"
|
||||
export * from "./create-dynamic-code"
|
||||
export * from "./delete-discount"
|
||||
export * from "./delete-dynamic-code"
|
||||
export * from "./get-discount"
|
||||
export * from "./get-discount-by-code"
|
||||
export * from "./list-discounts"
|
||||
export * from "./remove-region"
|
||||
export * from "./remove-valid-product"
|
||||
export * from "./update-discount"
|
||||
@@ -1,52 +0,0 @@
|
||||
import { defaultFields, defaultRelations } from "./"
|
||||
|
||||
/**
|
||||
* @oas [get] /discounts
|
||||
* operationId: "GetDiscounts"
|
||||
* summary: "List Discounts"
|
||||
* description: "Retrieves a list of Discounts"
|
||||
* tags:
|
||||
* - Discount
|
||||
* responses:
|
||||
* 200:
|
||||
* description: OK
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* properties:
|
||||
* discount:
|
||||
* $ref: "#/components/schemas/discount"
|
||||
*/
|
||||
export default async (req, res) => {
|
||||
const discountService = req.scope.resolve("discountService")
|
||||
const limit = parseInt(req.query.limit) || 20
|
||||
const offset = parseInt(req.query.offset) || 0
|
||||
const selector = {}
|
||||
|
||||
if ("q" in req.query) {
|
||||
selector.q = req.query.q
|
||||
}
|
||||
|
||||
if ("is_dynamic" in req.query) {
|
||||
selector.is_dynamic = req.query.is_dynamic === "true"
|
||||
}
|
||||
|
||||
if ("is_disabled" in req.query) {
|
||||
selector.is_disabled = req.query.is_disabled === "true"
|
||||
}
|
||||
|
||||
const listConfig = {
|
||||
select: defaultFields,
|
||||
relations: defaultRelations,
|
||||
skip: offset,
|
||||
take: limit,
|
||||
order: { created_at: "DESC" },
|
||||
}
|
||||
|
||||
const [discounts, count] = await discountService.listAndCount(
|
||||
selector,
|
||||
listConfig
|
||||
)
|
||||
|
||||
res.status(200).json({ discounts, count, offset, limit })
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
import { Type, Transform } from "class-transformer"
|
||||
import { IsBoolean, IsInt, IsOptional, IsString } from "class-validator"
|
||||
import { defaultAdminDiscountsFields, defaultAdminDiscountsRelations } from "."
|
||||
import DiscountService from "../../../../services/discount"
|
||||
import { ListSelector } from "../../../../types/discount"
|
||||
import { validator } from "../../../../utils/validator"
|
||||
/**
|
||||
* @oas [get] /discounts
|
||||
* operationId: "GetDiscounts"
|
||||
* summary: "List Discounts"
|
||||
* x-authenticated: true
|
||||
* description: "Retrieves a list of Discounts"
|
||||
* parameters:
|
||||
* - (query) q {string} Search query applied on results.
|
||||
* - (query) is_dynamic {boolean} Return only dynamic discounts.
|
||||
* - (query) is_disabled {boolean} Return only disabled discounts.
|
||||
* - (query) limit {number} The number of items in the response
|
||||
* - (query) offset {number} The offset of items in response
|
||||
* - (query) expand {string} Comma separated list of relations to include in the results.
|
||||
* tags:
|
||||
* - Discount
|
||||
* responses:
|
||||
* 200:
|
||||
* description: OK
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* properties:
|
||||
* discount:
|
||||
* $ref: "#/components/schemas/discount"
|
||||
*/
|
||||
export default async (req, res) => {
|
||||
const validated = await validator(AdminGetDiscountsParams, req.query)
|
||||
|
||||
const discountService: DiscountService = req.scope.resolve("discountService")
|
||||
const selector: ListSelector = {}
|
||||
|
||||
if (validated.q) {
|
||||
selector.q = validated.q
|
||||
}
|
||||
|
||||
selector.is_disabled = validated.is_disabled
|
||||
selector.is_dynamic = validated.is_dynamic
|
||||
|
||||
const listConfig = {
|
||||
select: defaultAdminDiscountsFields,
|
||||
relations: defaultAdminDiscountsRelations,
|
||||
skip: validated.offset,
|
||||
take: validated.limit,
|
||||
order: { created_at: "DESC" },
|
||||
}
|
||||
const [discounts, count] = await discountService.listAndCount(
|
||||
selector,
|
||||
listConfig
|
||||
)
|
||||
|
||||
res.status(200).json({
|
||||
discounts,
|
||||
count,
|
||||
offset: validated.offset,
|
||||
limit: validated.limit,
|
||||
})
|
||||
}
|
||||
|
||||
export class AdminGetDiscountsParams {
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
q?: string
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
@Transform(({ value }) => value === "true")
|
||||
is_dynamic?: boolean
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
@Transform(({ value }) => value === "true")
|
||||
is_disabled?: boolean
|
||||
|
||||
@IsInt()
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
limit = 20
|
||||
|
||||
@IsInt()
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
offset = 0
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
expand?: string
|
||||
}
|
||||
+7
-5
@@ -1,9 +1,10 @@
|
||||
import { defaultFields, defaultRelations } from "./"
|
||||
|
||||
import DiscountService from "../../../../services/discount"
|
||||
import { defaultAdminDiscountsFields, defaultAdminDiscountsRelations } from "."
|
||||
/**
|
||||
* @oas [delete] /discounts/{id}/regions/{region_id}
|
||||
* operationId: "DeleteDiscountsDiscountRegionsRegion"
|
||||
* summary: "Remove Region availability"
|
||||
* x-authenticated: true
|
||||
* description: "Removes a Region from the list of Regions that a Discount can be used in."
|
||||
* parameters:
|
||||
* - (path) id=* {string} The id of the Discount.
|
||||
@@ -22,12 +23,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.removeRegion(discount_id, region_id)
|
||||
|
||||
const discount = await discountService.retrieve(discount_id, {
|
||||
select: defaultFields,
|
||||
relations: defaultRelations,
|
||||
select: defaultAdminDiscountsFields,
|
||||
relations: defaultAdminDiscountsRelations,
|
||||
})
|
||||
|
||||
res.status(200).json({ discount })
|
||||
+7
-5
@@ -1,10 +1,11 @@
|
||||
import { defaultFields, defaultRelations } from "./"
|
||||
|
||||
import DiscountService from "../../../../services/discount"
|
||||
import { defaultAdminDiscountsFields, defaultAdminDiscountsRelations } from "."
|
||||
/**
|
||||
* @oas [post] /discounts/{id}/products/{product_id}
|
||||
* operationId: "DeleteDiscountsDiscountProductsProduct"
|
||||
* summary: "Remove Product availability"
|
||||
* description: "Removes a Product from 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.
|
||||
@@ -22,12 +23,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.removeValidProduct(discount_id, variant_id)
|
||||
|
||||
const discount = await discountService.retrieve(discount_id, {
|
||||
select: defaultFields,
|
||||
relations: defaultRelations,
|
||||
select: defaultAdminDiscountsFields,
|
||||
relations: defaultAdminDiscountsRelations,
|
||||
})
|
||||
|
||||
res.status(200).json({ discount })
|
||||
@@ -1,95 +0,0 @@
|
||||
import { MedusaError, Validator } from "medusa-core-utils"
|
||||
import { defaultFields, defaultRelations } from "./"
|
||||
|
||||
/**
|
||||
* @oas [post] /discounts/{id}
|
||||
* operationId: "PostDiscountsDiscount"
|
||||
* summary: "Update a Discount"
|
||||
* description: "Updates a Discount with a given set of rules that define how the Discount behaves."
|
||||
* parameters:
|
||||
* - (path) id=* {string} The id of the Discount.
|
||||
* requestBody:
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* properties:
|
||||
* code:
|
||||
* type: string
|
||||
* description: A unique code that will be used to redeem the Discount
|
||||
* is_dynamic:
|
||||
* type: string
|
||||
* description: Whether the Discount should have multiple instances of itself, each with a different code. This can be useful for automatically generated codes that all have to follow a common set of rules.
|
||||
* rule:
|
||||
* description: The Discount Rule that defines how Discounts are calculated
|
||||
* oneOf:
|
||||
* - $ref: "#/components/schemas/discount_rule"
|
||||
* is_disabled:
|
||||
* type: boolean
|
||||
* description: Whether the Discount code is disabled on creation. You will have to enable it later to make it available to Customers.
|
||||
* starts_at:
|
||||
* type: string
|
||||
* format: date-time
|
||||
* description: The time at which the Discount should be available.
|
||||
* ends_at:
|
||||
* type: string
|
||||
* format: date-time
|
||||
* description: The time at which the Discount should no longer be available.
|
||||
* regions:
|
||||
* description: A list of Region ids representing the Regions in which the Discount can be used.
|
||||
* type: array
|
||||
* items:
|
||||
* type: string
|
||||
* tags:
|
||||
* - Discount
|
||||
* responses:
|
||||
* 200:
|
||||
* description: OK
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* properties:
|
||||
* discount:
|
||||
* $ref: "#/components/schemas/discount"
|
||||
*/
|
||||
export default async (req, res) => {
|
||||
const { discount_id } = req.params
|
||||
const schema = Validator.object().keys({
|
||||
code: Validator.string().optional(),
|
||||
is_dynamic: Validator.boolean().optional(),
|
||||
rule: Validator.object()
|
||||
.keys({
|
||||
id: Validator.string().required(),
|
||||
description: Validator.string().optional(),
|
||||
type: Validator.string().required(),
|
||||
value: Validator.number().required(),
|
||||
allocation: Validator.string().required(),
|
||||
valid_for: Validator.array().items(Validator.string()),
|
||||
})
|
||||
.optional(),
|
||||
is_disabled: Validator.boolean().optional(),
|
||||
starts_at: Validator.date().optional(),
|
||||
ends_at: Validator.when("starts_at", {
|
||||
not: undefined,
|
||||
then: Validator.date().greater(Validator.ref("starts_at")).optional(),
|
||||
otherwise: Validator.date().optional(),
|
||||
}),
|
||||
valid_duration: Validator.string().isoDuration().allow(null).optional(),
|
||||
usage_limit: Validator.number().positive().optional(),
|
||||
regions: Validator.array().items(Validator.string()).optional(),
|
||||
})
|
||||
|
||||
const { value, error } = schema.validate(req.body)
|
||||
|
||||
if (error) {
|
||||
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
|
||||
}
|
||||
|
||||
const discountService = req.scope.resolve("discountService")
|
||||
await discountService.update(discount_id, value)
|
||||
const discount = await discountService.retrieve(discount_id, {
|
||||
select: defaultFields,
|
||||
relations: defaultRelations,
|
||||
})
|
||||
|
||||
res.status(200).json({ discount })
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
import { Type } from "class-transformer"
|
||||
import {
|
||||
IsArray,
|
||||
IsBoolean,
|
||||
IsDate,
|
||||
IsNotEmpty,
|
||||
IsNumber,
|
||||
IsObject,
|
||||
IsOptional,
|
||||
IsPositive,
|
||||
IsString,
|
||||
ValidateNested,
|
||||
} from "class-validator"
|
||||
import { defaultAdminDiscountsFields, 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/{id}
|
||||
* operationId: "PostDiscountsDiscount"
|
||||
* summary: "Update a Discount"
|
||||
* description: "Updates a Discount with a given set of rules that define how the Discount behaves."
|
||||
* x-authenticated: true
|
||||
* parameters:
|
||||
* - (path) id=* {string} The id of the Discount.
|
||||
* requestBody:
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* properties:
|
||||
* code:
|
||||
* type: string
|
||||
* description: A unique code that will be used to redeem the Discount
|
||||
* is_dynamic:
|
||||
* type: string
|
||||
* description: Whether the Discount should have multiple instances of itself, each with a different code. This can be useful for automatically generated codes that all have to follow a common set of rules.
|
||||
* rule:
|
||||
* description: The Discount Rule that defines how Discounts are calculated
|
||||
* oneOf:
|
||||
* - $ref: "#/components/schemas/discount_rule"
|
||||
* is_disabled:
|
||||
* type: boolean
|
||||
* description: Whether the Discount code is disabled on creation. You will have to enable it later to make it available to Customers.
|
||||
* starts_at:
|
||||
* type: Date
|
||||
* description: The time at which the Discount should be available.
|
||||
* ends_at:
|
||||
* type: Date
|
||||
* description: The time at which the Discount should no longer be available.
|
||||
* regions:
|
||||
* description: A list of Region ids representing the Regions in which the Discount can be used.
|
||||
* type: array
|
||||
* items:
|
||||
* type: string
|
||||
* metadata:
|
||||
* description: An object containing metadata of the discount
|
||||
* type: object
|
||||
* tags:
|
||||
* - Discount
|
||||
* responses:
|
||||
* 200:
|
||||
* description: OK
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* properties:
|
||||
* discount:
|
||||
* $ref: "#/components/schemas/discount"
|
||||
*/
|
||||
export default async (req, res) => {
|
||||
const { discount_id } = req.params
|
||||
|
||||
const validated = await validator(AdminPostDiscountsDiscountReq, req.body)
|
||||
const discountService: DiscountService = req.scope.resolve("discountService")
|
||||
await discountService.update(discount_id, validated)
|
||||
const discount = await discountService.retrieve(discount_id, {
|
||||
select: defaultAdminDiscountsFields,
|
||||
relations: defaultAdminDiscountsRelations,
|
||||
})
|
||||
|
||||
res.status(200).json({ discount })
|
||||
}
|
||||
|
||||
export class AdminPostDiscountsDiscountReq {
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
code?: string
|
||||
|
||||
@IsOptional()
|
||||
@ValidateNested()
|
||||
@Type(() => AdminUpdateDiscountRule)
|
||||
rule?: AdminUpdateDiscountRule
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
is_dynamic?: boolean
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
is_disabled?: boolean
|
||||
|
||||
@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 AdminUpdateDiscountRule {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
id: string
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
description?: string
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
type: string
|
||||
|
||||
@IsNumber()
|
||||
value: string
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
allocation: string
|
||||
|
||||
@IsArray()
|
||||
@IsOptional()
|
||||
@IsString({ each: true })
|
||||
valid_for?: string
|
||||
}
|
||||
+131
-47
@@ -1,19 +1,40 @@
|
||||
import { Type } from "class-transformer"
|
||||
import {
|
||||
MedusaError,
|
||||
Validator,
|
||||
transformIdableFields,
|
||||
} from "medusa-core-utils"
|
||||
import { defaultFields, defaultRelations } from "."
|
||||
|
||||
IsArray,
|
||||
IsBoolean,
|
||||
IsEmail,
|
||||
IsEnum,
|
||||
IsNotEmpty,
|
||||
IsNumber,
|
||||
IsObject,
|
||||
IsOptional,
|
||||
IsString,
|
||||
ValidateNested,
|
||||
} from "class-validator"
|
||||
import { transformIdableFields } from "medusa-core-utils"
|
||||
import {
|
||||
defaultAdminDraftOrdersFields,
|
||||
defaultAdminDraftOrdersRelations,
|
||||
} from "."
|
||||
import { DraftOrder } from "../../../.."
|
||||
import { DraftOrderService } from "../../../../services"
|
||||
import { AddressPayload } from "../../../../types/common"
|
||||
import { validator } from "../../../../utils/validator"
|
||||
/**
|
||||
* @oas [post] /draft-orders
|
||||
* operationId: "PostDraftOrders"
|
||||
* summary: "Create a Draft Order"
|
||||
* description: "Creates a Draft Order"
|
||||
* x-authenticated: true
|
||||
* requestBody:
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* required:
|
||||
* - email
|
||||
* - items
|
||||
* - region_id
|
||||
* - shipping_methods
|
||||
* properties:
|
||||
* status:
|
||||
* description: "The status of the draft order"
|
||||
@@ -97,52 +118,115 @@ import { defaultFields, defaultRelations } from "."
|
||||
*/
|
||||
|
||||
export default async (req, res) => {
|
||||
const schema = Validator.object().keys({
|
||||
status: Validator.string().valid("open", "completed").optional(),
|
||||
email: Validator.string().email().required(),
|
||||
billing_address: Validator.address().optional(),
|
||||
shipping_address: Validator.address().optional(),
|
||||
items: Validator.array()
|
||||
.items({
|
||||
variant_id: Validator.string().optional().allow(""),
|
||||
unit_price: Validator.number().optional(),
|
||||
title: Validator.string().optional().allow(""),
|
||||
quantity: Validator.number().required(),
|
||||
metadata: Validator.object().default({}),
|
||||
})
|
||||
.required(),
|
||||
region_id: Validator.string().required(),
|
||||
discounts: Validator.array()
|
||||
.items({
|
||||
code: Validator.string().required(),
|
||||
})
|
||||
.optional(),
|
||||
customer_id: Validator.string().optional(),
|
||||
no_notification_order: Validator.boolean().optional(),
|
||||
shipping_methods: Validator.array()
|
||||
.items({
|
||||
option_id: Validator.string().required(),
|
||||
data: Validator.object().optional(),
|
||||
price: Validator.number().integer().integer().allow(0).optional(),
|
||||
})
|
||||
.required(),
|
||||
metadata: Validator.object().optional(),
|
||||
})
|
||||
const validated = await validator(AdminPostDraftOrdersReq, req.body)
|
||||
|
||||
let { value, error } = schema.validate(req.body)
|
||||
if (error) {
|
||||
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
|
||||
}
|
||||
const value = transformIdableFields(validated, [
|
||||
"shipping_address",
|
||||
"billing_address",
|
||||
])
|
||||
|
||||
value = transformIdableFields(value, ["shipping_address", "billing_address"])
|
||||
|
||||
const draftOrderService = req.scope.resolve("draftOrderService")
|
||||
let draftOrder = await draftOrderService.create(value)
|
||||
const draftOrderService: DraftOrderService =
|
||||
req.scope.resolve("draftOrderService")
|
||||
let draftOrder: DraftOrder = await draftOrderService.create(value)
|
||||
|
||||
draftOrder = await draftOrderService.retrieve(draftOrder.id, {
|
||||
relations: defaultRelations,
|
||||
select: defaultFields,
|
||||
relations: defaultAdminDraftOrdersRelations,
|
||||
select: defaultAdminDraftOrdersFields,
|
||||
})
|
||||
|
||||
res.status(200).json({ draft_order: draftOrder })
|
||||
}
|
||||
|
||||
enum Status {
|
||||
open = "open",
|
||||
completed = "completed",
|
||||
}
|
||||
|
||||
export class AdminPostDraftOrdersReq {
|
||||
@IsEnum(Status)
|
||||
@IsOptional()
|
||||
status?: string
|
||||
|
||||
@IsEmail()
|
||||
email: string
|
||||
|
||||
@IsOptional()
|
||||
@Type(() => AddressPayload)
|
||||
billing_address?: AddressPayload
|
||||
|
||||
@IsOptional()
|
||||
@Type(() => AddressPayload)
|
||||
shipping_address?: AddressPayload
|
||||
|
||||
@IsArray()
|
||||
@Type(() => Item)
|
||||
@IsNotEmpty()
|
||||
@ValidateNested({ each: true })
|
||||
items: Item[]
|
||||
|
||||
@IsString()
|
||||
region_id: string
|
||||
|
||||
@IsArray()
|
||||
@IsOptional()
|
||||
@Type(() => Discount)
|
||||
@ValidateNested({ each: true })
|
||||
discounts?: Discount[]
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
customer_id?: string
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
no_notification_order?: boolean
|
||||
|
||||
@IsArray()
|
||||
@Type(() => ShippingMethod)
|
||||
@IsNotEmpty()
|
||||
@ValidateNested({ each: true })
|
||||
shipping_methods: ShippingMethod[]
|
||||
|
||||
@IsObject()
|
||||
@IsOptional()
|
||||
metadata?: object = {}
|
||||
}
|
||||
|
||||
class ShippingMethod {
|
||||
@IsString()
|
||||
option_id: string
|
||||
|
||||
@IsObject()
|
||||
@IsOptional()
|
||||
data?: object = {}
|
||||
|
||||
@IsNumber()
|
||||
@IsOptional()
|
||||
price?: number
|
||||
}
|
||||
|
||||
class Discount {
|
||||
@IsString()
|
||||
code: string
|
||||
}
|
||||
|
||||
class Item {
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
title?: string
|
||||
|
||||
@IsNumber()
|
||||
@IsOptional()
|
||||
unit_price?: number
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
variant_id?: string
|
||||
|
||||
@IsNumber()
|
||||
quantity: number
|
||||
|
||||
@IsObject()
|
||||
@IsOptional()
|
||||
metadata?: object = {}
|
||||
}
|
||||
+60
-29
@@ -1,14 +1,28 @@
|
||||
import { MedusaError, Validator } from "medusa-core-utils"
|
||||
import { defaultCartFields, defaultCartRelations, defaultFields } from "."
|
||||
|
||||
import { IsInt, IsObject, IsOptional, IsString } from "class-validator"
|
||||
import { MedusaError } from "medusa-core-utils"
|
||||
import { EntityManager } from "typeorm"
|
||||
import {
|
||||
defaultAdminDraftOrdersCartFields,
|
||||
defaultAdminDraftOrdersCartRelations,
|
||||
defaultAdminDraftOrdersFields,
|
||||
} from "."
|
||||
import {
|
||||
CartService,
|
||||
DraftOrderService,
|
||||
LineItemService,
|
||||
} from "../../../../services"
|
||||
import { validator } from "../../../../utils/validator"
|
||||
/**
|
||||
* @oas [post] /draft-orders/{id}/line-items
|
||||
* operationId: "PostDraftOrdersDraftOrderLineItems"
|
||||
* summary: "Create a Line Item for Draft Order"
|
||||
* description: "Creates a Line Item for the Draft Order"
|
||||
* x-authenticated: true
|
||||
* requestBody:
|
||||
* content:
|
||||
* application/json:
|
||||
* required:
|
||||
* - quantity
|
||||
* schema:
|
||||
* properties:
|
||||
* variant_id:
|
||||
@@ -42,28 +56,24 @@ import { defaultCartFields, defaultCartRelations, defaultFields } from "."
|
||||
export default async (req, res) => {
|
||||
const { id } = req.params
|
||||
|
||||
const schema = Validator.object().keys({
|
||||
title: Validator.string().optional(),
|
||||
unit_price: Validator.number().optional(),
|
||||
variant_id: Validator.string().optional(),
|
||||
quantity: Validator.number().required(),
|
||||
metadata: Validator.object().optional(),
|
||||
})
|
||||
const validated = await validator(
|
||||
AdminPostDraftOrdersDraftOrderLineItemsReq,
|
||||
req.body
|
||||
)
|
||||
|
||||
const { value, error } = schema.validate(req.body)
|
||||
if (error) {
|
||||
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
|
||||
}
|
||||
|
||||
const draftOrderService = req.scope.resolve("draftOrderService")
|
||||
const cartService = req.scope.resolve("cartService")
|
||||
const lineItemService = req.scope.resolve("lineItemService")
|
||||
const entityManager = req.scope.resolve("manager")
|
||||
const draftOrderService: DraftOrderService =
|
||||
req.scope.resolve("draftOrderService")
|
||||
const cartService: CartService = req.scope.resolve("cartService")
|
||||
const lineItemService: LineItemService = req.scope.resolve("lineItemService")
|
||||
const entityManager: EntityManager = req.scope.resolve("manager")
|
||||
|
||||
await entityManager.transaction(async (manager) => {
|
||||
const draftOrder = await draftOrderService
|
||||
.withTransaction(manager)
|
||||
.retrieve(id, { select: defaultFields, relations: ["cart"] })
|
||||
.retrieve(id, {
|
||||
select: defaultAdminDraftOrdersFields,
|
||||
relations: ["cart"],
|
||||
})
|
||||
|
||||
if (draftOrder.status === "completed") {
|
||||
throw new MedusaError(
|
||||
@@ -72,12 +82,12 @@ export default async (req, res) => {
|
||||
)
|
||||
}
|
||||
|
||||
if (value.variant_id) {
|
||||
if (validated.variant_id) {
|
||||
const line = await lineItemService.generate(
|
||||
value.variant_id,
|
||||
validated.variant_id,
|
||||
draftOrder.cart.region_id,
|
||||
value.quantity,
|
||||
{ metadata: value.metadata, unit_price: value.unit_price }
|
||||
validated.quantity,
|
||||
{ metadata: validated.metadata, unit_price: validated.unit_price }
|
||||
)
|
||||
|
||||
await cartService
|
||||
@@ -88,20 +98,41 @@ export default async (req, res) => {
|
||||
await lineItemService.withTransaction(manager).create({
|
||||
cart_id: draftOrder.cart_id,
|
||||
has_shipping: true,
|
||||
title: value.title || "Custom item",
|
||||
title: validated.title,
|
||||
allow_discounts: false,
|
||||
unit_price: value.unit_price || 0,
|
||||
quantity: value.quantity,
|
||||
unit_price: validated.unit_price || 0,
|
||||
quantity: validated.quantity,
|
||||
})
|
||||
}
|
||||
|
||||
draftOrder.cart = await cartService
|
||||
.withTransaction(manager)
|
||||
.retrieve(draftOrder.cart_id, {
|
||||
relations: defaultCartRelations,
|
||||
select: defaultCartFields,
|
||||
relations: defaultAdminDraftOrdersCartRelations,
|
||||
select: defaultAdminDraftOrdersCartFields,
|
||||
})
|
||||
|
||||
res.status(200).json({ draft_order: draftOrder })
|
||||
})
|
||||
}
|
||||
|
||||
export class AdminPostDraftOrdersDraftOrderLineItemsReq {
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
title?: string = "Custom item"
|
||||
|
||||
@IsInt()
|
||||
@IsOptional()
|
||||
unit_price?: number
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
variant_id?: string
|
||||
|
||||
@IsInt()
|
||||
quantity: number
|
||||
|
||||
@IsObject()
|
||||
@IsOptional()
|
||||
metadata?: object = {}
|
||||
}
|
||||
+4
-2
@@ -1,8 +1,10 @@
|
||||
import { DraftOrderService } from "../../../../services"
|
||||
/**
|
||||
* @oas [delete] /draft-orders/{id}
|
||||
* operationId: DeleteDraftOrdersDraftOrder
|
||||
* summary: Delete a Draft Order
|
||||
* description: "Deletes a Draft Order"
|
||||
* x-authenticated: true
|
||||
* parameters:
|
||||
* - (path) id=* {string} The id of the Draft Order.
|
||||
* tags:
|
||||
@@ -23,11 +25,11 @@
|
||||
* deleted:
|
||||
* type: boolean
|
||||
*/
|
||||
|
||||
export default async (req, res) => {
|
||||
const { id } = req.params
|
||||
|
||||
const draftOrderService = req.scope.resolve("draftOrderService")
|
||||
const draftOrderService: DraftOrderService =
|
||||
req.scope.resolve("draftOrderService")
|
||||
await draftOrderService.delete(id)
|
||||
|
||||
res.json({
|
||||
+17
-9
@@ -1,11 +1,18 @@
|
||||
import { MedusaError } from "medusa-core-utils"
|
||||
import { defaultCartFields, defaultCartRelations, defaultFields } from "."
|
||||
|
||||
import { EntityManager } from "typeorm"
|
||||
import {
|
||||
defaultAdminDraftOrdersCartFields,
|
||||
defaultAdminDraftOrdersCartRelations,
|
||||
defaultAdminDraftOrdersFields,
|
||||
} from "."
|
||||
import { DraftOrder } from "../../../.."
|
||||
import { CartService, DraftOrderService } from "../../../../services"
|
||||
/**
|
||||
* @oas [delete] /draft-orders/{id}/line-items/{line_id}
|
||||
* operationId: DeleteDraftOrdersDraftOrderLineItemsItem
|
||||
* summary: Delete a Line Item
|
||||
* description: "Removes a Line Item from a Draft Order."
|
||||
* x-authenticated: true
|
||||
* parameters:
|
||||
* - (path) id=* {string} The id of the Draft Order.
|
||||
* - (path) line_id=* {string} The id of the Draft Order.
|
||||
@@ -25,14 +32,15 @@ import { defaultCartFields, defaultCartRelations, defaultFields } from "."
|
||||
export default async (req, res) => {
|
||||
const { id, line_id } = req.params
|
||||
|
||||
const draftOrderService = req.scope.resolve("draftOrderService")
|
||||
const cartService = req.scope.resolve("cartService")
|
||||
const entityManager = req.scope.resolve("manager")
|
||||
const draftOrderService: DraftOrderService =
|
||||
req.scope.resolve("draftOrderService")
|
||||
const cartService: CartService = req.scope.resolve("cartService")
|
||||
const entityManager: EntityManager = req.scope.resolve("manager")
|
||||
|
||||
await entityManager.transaction(async (manager) => {
|
||||
const draftOrder = await draftOrderService
|
||||
const draftOrder: DraftOrder = await draftOrderService
|
||||
.withTransaction(manager)
|
||||
.retrieve(id, { select: defaultFields })
|
||||
.retrieve(id, { select: defaultAdminDraftOrdersFields })
|
||||
|
||||
if (draftOrder.status === "completed") {
|
||||
throw new MedusaError(
|
||||
@@ -48,8 +56,8 @@ export default async (req, res) => {
|
||||
draftOrder.cart = await cartService
|
||||
.withTransaction(manager)
|
||||
.retrieve(draftOrder.cart_id, {
|
||||
relations: defaultCartRelations,
|
||||
select: defaultCartFields,
|
||||
relations: defaultAdminDraftOrdersCartRelations,
|
||||
select: defaultAdminDraftOrdersCartFields,
|
||||
})
|
||||
|
||||
res.status(200).json({ draft_order: draftOrder })
|
||||
@@ -1,45 +0,0 @@
|
||||
import {
|
||||
defaultRelations,
|
||||
defaultFields,
|
||||
defaultCartRelations,
|
||||
defaultCartFields,
|
||||
} from "."
|
||||
|
||||
/**
|
||||
* @oas [get] /draft-orders/{id}
|
||||
* operationId: "GetDraftOrdersDraftOrder"
|
||||
* summary: "Retrieve a Draft Order"
|
||||
* description: "Retrieves a Draft Order."
|
||||
* parameters:
|
||||
* - (path) id=* {string} The id of the Draft Order.
|
||||
* tags:
|
||||
* - Draft Order
|
||||
* responses:
|
||||
* 200:
|
||||
* description: OK
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* properties:
|
||||
* draft_order:
|
||||
* $ref: "#/components/schemas/draft-order"
|
||||
*/
|
||||
|
||||
export default async (req, res) => {
|
||||
const { id } = req.params
|
||||
|
||||
const draftOrderService = req.scope.resolve("draftOrderService")
|
||||
const cartService = req.scope.resolve("cartService")
|
||||
|
||||
const draftOrder = await draftOrderService.retrieve(id, {
|
||||
select: defaultFields,
|
||||
relations: defaultRelations,
|
||||
})
|
||||
|
||||
draftOrder.cart = await cartService.retrieve(draftOrder.cart_id, {
|
||||
relations: defaultCartRelations,
|
||||
select: defaultCartFields,
|
||||
})
|
||||
|
||||
res.json({ draft_order: draftOrder })
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import {
|
||||
defaultAdminDraftOrdersRelations,
|
||||
defaultAdminDraftOrdersFields,
|
||||
defaultAdminDraftOrdersCartRelations,
|
||||
defaultAdminDraftOrdersCartFields,
|
||||
} from "."
|
||||
import { DraftOrder } from "../../../.."
|
||||
import { CartService, DraftOrderService } from "../../../../services"
|
||||
|
||||
/**
|
||||
* @oas [get] /draft-orders/{id}
|
||||
* operationId: "GetDraftOrdersDraftOrder"
|
||||
* summary: "Retrieve a Draft Order"
|
||||
* description: "Retrieves a Draft Order."
|
||||
* x-authenticated: true
|
||||
* parameters:
|
||||
* - (path) id=* {string} The id of the Draft Order.
|
||||
* tags:
|
||||
* - Draft Order
|
||||
* responses:
|
||||
* 200:
|
||||
* description: OK
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* properties:
|
||||
* draft_order:
|
||||
* $ref: "#/components/schemas/draft-order"
|
||||
*/
|
||||
|
||||
export default async (req, res) => {
|
||||
const { id } = req.params
|
||||
|
||||
const draftOrderService: DraftOrderService =
|
||||
req.scope.resolve("draftOrderService")
|
||||
const cartService: CartService = req.scope.resolve("cartService")
|
||||
|
||||
const draftOrder: DraftOrder = await draftOrderService.retrieve(id, {
|
||||
select: defaultAdminDraftOrdersFields,
|
||||
relations: defaultAdminDraftOrdersRelations,
|
||||
})
|
||||
|
||||
draftOrder.cart = await cartService.retrieve(draftOrder.cart_id, {
|
||||
relations: defaultAdminDraftOrdersCartRelations,
|
||||
select: defaultAdminDraftOrdersCartFields,
|
||||
})
|
||||
|
||||
res.json({ draft_order: draftOrder })
|
||||
}
|
||||
+32
-6
@@ -1,5 +1,7 @@
|
||||
import { Router } from "express"
|
||||
import { DraftOrder, Order } from "../../../.."
|
||||
import middlewares from "../../../middlewares"
|
||||
import { DeleteResponse, PaginatedResponse } from "../../../../types/common"
|
||||
|
||||
const route = Router()
|
||||
|
||||
@@ -44,9 +46,9 @@ export default (app) => {
|
||||
return app
|
||||
}
|
||||
|
||||
export const defaultRelations = ["order", "cart"]
|
||||
export const defaultAdminDraftOrdersRelations = ["order", "cart"]
|
||||
|
||||
export const defaultCartRelations = [
|
||||
export const defaultAdminDraftOrdersCartRelations = [
|
||||
"region",
|
||||
"items",
|
||||
"payment",
|
||||
@@ -60,7 +62,7 @@ export const defaultCartRelations = [
|
||||
"discounts.rule",
|
||||
]
|
||||
|
||||
export const defaultCartFields = [
|
||||
export const defaultAdminDraftOrdersCartFields = [
|
||||
"subtotal",
|
||||
"tax_total",
|
||||
"shipping_total",
|
||||
@@ -69,7 +71,7 @@ export const defaultCartFields = [
|
||||
"total",
|
||||
]
|
||||
|
||||
export const defaultFields = [
|
||||
export const defaultAdminDraftOrdersFields = [
|
||||
"id",
|
||||
"status",
|
||||
"display_id",
|
||||
@@ -82,7 +84,7 @@ export const defaultFields = [
|
||||
"no_notification_order",
|
||||
]
|
||||
|
||||
export const allowedFields = [
|
||||
export const allowedAdminDraftOrdersFields = [
|
||||
"id",
|
||||
"status",
|
||||
"display_id",
|
||||
@@ -94,4 +96,28 @@ export const allowedFields = [
|
||||
"no_notification_order",
|
||||
]
|
||||
|
||||
export const allowedRelations = ["cart"]
|
||||
export const allowedAdminDraftOrdersRelations = ["cart"]
|
||||
|
||||
export type AdminPostDraftOrdersDraftOrderRegisterPaymentRes = {
|
||||
order: Order
|
||||
}
|
||||
|
||||
export type AdminDraftOrdersRes = {
|
||||
draft_order: DraftOrder
|
||||
}
|
||||
|
||||
export type AdminDraftOrdersDeleteRes = DeleteResponse
|
||||
|
||||
export type AdminDraftOrdersListRes = PaginatedResponse & {
|
||||
draft_orders: DraftOrder[]
|
||||
}
|
||||
|
||||
export * from "./create-draft-order"
|
||||
export * from "./create-line-item"
|
||||
export * from "./delete-draft-order"
|
||||
export * from "./delete-line-item"
|
||||
export * from "./get-draft-order"
|
||||
export * from "./list-draft-orders"
|
||||
export * from "./register-payment"
|
||||
export * from "./update-draft-order"
|
||||
export * from "./update-line-item"
|
||||
@@ -1,47 +0,0 @@
|
||||
import { defaultFields, defaultRelations } from "./"
|
||||
|
||||
/**
|
||||
* @oas [get] /draft-orders
|
||||
* operationId: "GetDraftOrders"
|
||||
* summary: "List Draft Orders"
|
||||
* description: "Retrieves an list of Draft Orders"
|
||||
* tags:
|
||||
* - Draft Order
|
||||
* responses:
|
||||
* 200:
|
||||
* description: OK
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* properties:
|
||||
* draft_order:
|
||||
* $ref: "#/components/schemas/draft-order"
|
||||
*/
|
||||
|
||||
export default async (req, res) => {
|
||||
const draftOrderService = req.scope.resolve("draftOrderService")
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
const listConfig = {
|
||||
select: defaultFields,
|
||||
relations: defaultRelations,
|
||||
skip: offset,
|
||||
take: limit,
|
||||
order: { created_at: "DESC" },
|
||||
}
|
||||
|
||||
const [draftOrders, count] = await draftOrderService.listAndCount(
|
||||
selector,
|
||||
listConfig
|
||||
)
|
||||
|
||||
res.json({ draft_orders: draftOrders, count, offset, limit })
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
import {
|
||||
defaultAdminDraftOrdersFields,
|
||||
defaultAdminDraftOrdersRelations,
|
||||
} from "."
|
||||
import { DraftOrderService } from "../../../../services"
|
||||
import { IsNumber, IsOptional, IsString } from "class-validator"
|
||||
import { validator } from "../../../../utils/validator"
|
||||
import { Type } from "class-transformer"
|
||||
import { DraftOrderListSelector } from "../../../../types/draft-orders"
|
||||
/**
|
||||
* @oas [get] /draft-orders
|
||||
* operationId: "GetDraftOrders"
|
||||
* summary: "List Draft Orders"
|
||||
* description: "Retrieves an list of Draft Orders"
|
||||
* x-authenticated: true
|
||||
* tags:
|
||||
* - Draft Order
|
||||
* responses:
|
||||
* 200:
|
||||
* description: OK
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* properties:
|
||||
* draft_order:
|
||||
* $ref: "#/components/schemas/draft-order"
|
||||
*/
|
||||
|
||||
export default async (req, res) => {
|
||||
const draftOrderService: DraftOrderService =
|
||||
req.scope.resolve("draftOrderService")
|
||||
|
||||
const validated = await validator(AdminGetDraftOrdersParams, req.query)
|
||||
|
||||
const selector: DraftOrderListSelector = {}
|
||||
|
||||
if (validated.q) {
|
||||
selector.q = validated.q
|
||||
}
|
||||
|
||||
const listConfig = {
|
||||
select: defaultAdminDraftOrdersFields,
|
||||
relations: defaultAdminDraftOrdersRelations,
|
||||
skip: validated.offset,
|
||||
take: validated.limit,
|
||||
order: { created_at: "DESC" },
|
||||
}
|
||||
|
||||
const [draftOrders, count] = await draftOrderService.listAndCount(
|
||||
selector,
|
||||
listConfig
|
||||
)
|
||||
|
||||
res.json({
|
||||
draft_orders: draftOrders,
|
||||
count,
|
||||
offset: validated.offset,
|
||||
limit: validated.limit,
|
||||
})
|
||||
}
|
||||
|
||||
export class AdminGetDraftOrdersParams {
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
q?: string
|
||||
|
||||
@IsNumber()
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
limit?: number = 50
|
||||
|
||||
@IsNumber()
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
offset?: number = 0
|
||||
}
|
||||
+18
-7
@@ -1,6 +1,13 @@
|
||||
import { EntityManager } from "typeorm"
|
||||
import {
|
||||
defaultFields as defaultOrderFields,
|
||||
defaultRelations as defaultOrderRelations,
|
||||
CartService,
|
||||
DraftOrderService,
|
||||
OrderService,
|
||||
PaymentProviderService,
|
||||
} from "../../../../services"
|
||||
import {
|
||||
defaultAdminOrdersFields as defaultOrderFields,
|
||||
defaultAdminOrdersRelations as defaultOrderRelations,
|
||||
} from "../orders/index"
|
||||
|
||||
/**
|
||||
@@ -8,6 +15,7 @@ import {
|
||||
* summary: "Registers a payment for a Draft Order"
|
||||
* operationId: "PostDraftOrdersDraftOrderRegisterPayment"
|
||||
* description: "Registers a payment for a Draft Order."
|
||||
* x-authenticated: true
|
||||
* parameters:
|
||||
* - (path) id=* {String} The Draft Order id.
|
||||
* tags:
|
||||
@@ -26,11 +34,14 @@ import {
|
||||
export default async (req, res) => {
|
||||
const { id } = req.params
|
||||
|
||||
const draftOrderService = req.scope.resolve("draftOrderService")
|
||||
const paymentProviderService = req.scope.resolve("paymentProviderService")
|
||||
const orderService = req.scope.resolve("orderService")
|
||||
const cartService = req.scope.resolve("cartService")
|
||||
const entityManager = req.scope.resolve("manager")
|
||||
const draftOrderService: DraftOrderService =
|
||||
req.scope.resolve("draftOrderService")
|
||||
const paymentProviderService: PaymentProviderService = req.scope.resolve(
|
||||
"paymentProviderService"
|
||||
)
|
||||
const orderService: OrderService = req.scope.resolve("orderService")
|
||||
const cartService: CartService = req.scope.resolve("cartService")
|
||||
const entityManager: EntityManager = req.scope.resolve("manager")
|
||||
|
||||
let result
|
||||
await entityManager.transaction(async (manager) => {
|
||||
+69
-30
@@ -1,11 +1,26 @@
|
||||
import { MedusaError, Validator } from "medusa-core-utils"
|
||||
import { defaultCartFields, defaultCartRelations } from "."
|
||||
|
||||
import { MedusaError } from "medusa-core-utils"
|
||||
import {
|
||||
defaultAdminDraftOrdersCartFields,
|
||||
defaultAdminDraftOrdersCartRelations,
|
||||
} from "."
|
||||
import {
|
||||
IsArray,
|
||||
IsBoolean,
|
||||
IsEmail,
|
||||
IsOptional,
|
||||
IsString,
|
||||
ValidateNested,
|
||||
} from "class-validator"
|
||||
import { CartService, DraftOrderService } from "../../../../services"
|
||||
import { Type } from "class-transformer"
|
||||
import { AddressPayload } from "../../../../types/common"
|
||||
import { validator } from "../../../../utils/validator"
|
||||
/**
|
||||
* @oas [post] /admin/draft-orders/{id}
|
||||
* operationId: PostDraftOrdersDraftOrder
|
||||
* summary: Update a Draft Order"
|
||||
* description: "Updates a Draft Order."
|
||||
* x-authenticated: true
|
||||
* parameters:
|
||||
* - (path) id=* {string} The id of the Draft Order.
|
||||
* requestBody:
|
||||
@@ -57,28 +72,11 @@ import { defaultCartFields, defaultCartRelations } from "."
|
||||
export default async (req, res) => {
|
||||
const { id } = req.params
|
||||
|
||||
const schema = Validator.object().keys({
|
||||
region_id: Validator.string().optional(),
|
||||
country_code: Validator.string().optional(),
|
||||
email: Validator.string().email().optional(),
|
||||
billing_address: Validator.object().optional(),
|
||||
shipping_address: Validator.object().optional(),
|
||||
discounts: Validator.array()
|
||||
.items({
|
||||
code: Validator.string(),
|
||||
})
|
||||
.optional(),
|
||||
customer_id: Validator.string().optional(),
|
||||
no_notification_order: Validator.boolean().optional(),
|
||||
})
|
||||
const validated = await validator(AdminPostDraftOrdersDraftOrderReq, req.body)
|
||||
|
||||
const { value, error } = schema.validate(req.body)
|
||||
if (error) {
|
||||
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
|
||||
}
|
||||
|
||||
const draftOrderService = req.scope.resolve("draftOrderService")
|
||||
const cartService = req.scope.resolve("cartService")
|
||||
const draftOrderService: DraftOrderService =
|
||||
req.scope.resolve("draftOrderService")
|
||||
const cartService: CartService = req.scope.resolve("cartService")
|
||||
|
||||
const draftOrder = await draftOrderService.retrieve(id)
|
||||
|
||||
@@ -89,19 +87,60 @@ export default async (req, res) => {
|
||||
)
|
||||
}
|
||||
|
||||
if ("no_notification_order" in value) {
|
||||
if (validated.no_notification_order !== undefined) {
|
||||
await draftOrderService.update(draftOrder.id, {
|
||||
no_notification_order: value.no_notification_order,
|
||||
no_notification_order: validated.no_notification_order,
|
||||
})
|
||||
delete value.no_notification_order
|
||||
delete validated.no_notification_order
|
||||
}
|
||||
|
||||
await cartService.update(draftOrder.cart_id, value)
|
||||
await cartService.update(draftOrder.cart_id, validated)
|
||||
|
||||
draftOrder.cart = await cartService.retrieve(draftOrder.cart_id, {
|
||||
relations: defaultCartRelations,
|
||||
select: defaultCartFields,
|
||||
relations: defaultAdminDraftOrdersCartRelations,
|
||||
select: defaultAdminDraftOrdersCartFields,
|
||||
})
|
||||
|
||||
res.status(200).json({ draft_order: draftOrder })
|
||||
}
|
||||
|
||||
export class AdminPostDraftOrdersDraftOrderReq {
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
region_id?: string
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
country_code?: string
|
||||
|
||||
@IsEmail()
|
||||
@IsOptional()
|
||||
email?: string
|
||||
|
||||
@IsOptional()
|
||||
@Type(() => AddressPayload)
|
||||
billing_address?: AddressPayload
|
||||
|
||||
@IsOptional()
|
||||
@Type(() => AddressPayload)
|
||||
shipping_address?: AddressPayload
|
||||
|
||||
@IsArray()
|
||||
@IsOptional()
|
||||
@Type(() => Discount)
|
||||
@ValidateNested({ each: true })
|
||||
discounts?: Discount[]
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
customer_id?: string
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
no_notification_order?: boolean
|
||||
}
|
||||
|
||||
class Discount {
|
||||
@IsString()
|
||||
code: string
|
||||
}
|
||||
+54
-24
@@ -1,11 +1,20 @@
|
||||
import { MedusaError, Validator } from "medusa-core-utils"
|
||||
import { defaultCartFields, defaultCartRelations, defaultFields } from "."
|
||||
|
||||
import { IsInt, IsObject, IsOptional, IsString } from "class-validator"
|
||||
import { MedusaError } from "medusa-core-utils"
|
||||
import { EntityManager } from "typeorm"
|
||||
import {
|
||||
defaultAdminDraftOrdersCartFields,
|
||||
defaultAdminDraftOrdersCartRelations,
|
||||
defaultAdminDraftOrdersFields,
|
||||
} from "."
|
||||
import { DraftOrder } from "../../../.."
|
||||
import { CartService, DraftOrderService } from "../../../../services"
|
||||
import { validator } from "../../../../utils/validator"
|
||||
/**
|
||||
* @oas [post] /draft-orders/{id}/line-items/{line_id}
|
||||
* operationId: "PostDraftOrdersDraftOrderLineItemsItem"
|
||||
* summary: "Update a Line Item for a Draft Order"
|
||||
* description: "Updates a Line Item for a Draft Order"
|
||||
* x-authenticated: true
|
||||
* requestBody:
|
||||
* content:
|
||||
* application/json:
|
||||
@@ -39,27 +48,21 @@ import { defaultCartFields, defaultCartRelations, defaultFields } from "."
|
||||
export default async (req, res) => {
|
||||
const { id, line_id } = req.params
|
||||
|
||||
const schema = Validator.object().keys({
|
||||
title: Validator.string().optional(),
|
||||
unit_price: Validator.number().optional(),
|
||||
quantity: Validator.number().optional(),
|
||||
metadata: Validator.object().optional(),
|
||||
})
|
||||
const validated = await validator(
|
||||
AdminPostDraftOrdersDraftOrderLineItemsItemReq,
|
||||
req.body
|
||||
)
|
||||
|
||||
const { value, error } = schema.validate(req.body)
|
||||
if (error) {
|
||||
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
|
||||
}
|
||||
|
||||
const draftOrderService = req.scope.resolve("draftOrderService")
|
||||
const cartService = req.scope.resolve("cartService")
|
||||
const entityManager = req.scope.resolve("manager")
|
||||
const draftOrderService: DraftOrderService =
|
||||
req.scope.resolve("draftOrderService")
|
||||
const cartService: CartService = req.scope.resolve("cartService")
|
||||
const entityManager: EntityManager = req.scope.resolve("manager")
|
||||
|
||||
await entityManager.transaction(async (manager) => {
|
||||
const draftOrder = await draftOrderService
|
||||
const draftOrder: DraftOrder = await draftOrderService
|
||||
.withTransaction(manager)
|
||||
.retrieve(id, {
|
||||
select: defaultFields,
|
||||
select: defaultAdminDraftOrdersFields,
|
||||
relations: ["cart", "cart.items"],
|
||||
})
|
||||
|
||||
@@ -70,7 +73,7 @@ export default async (req, res) => {
|
||||
)
|
||||
}
|
||||
|
||||
if (value.quantity === 0) {
|
||||
if (validated.quantity === 0) {
|
||||
await cartService
|
||||
.withTransaction(manager)
|
||||
.removeLineItem(draftOrder.cart.id, line_id)
|
||||
@@ -84,8 +87,8 @@ export default async (req, res) => {
|
||||
)
|
||||
}
|
||||
|
||||
const lineItemUpdate = {
|
||||
...value,
|
||||
const lineItemUpdate: LineItemUpdate = {
|
||||
...validated,
|
||||
region_id: draftOrder.cart.region_id,
|
||||
}
|
||||
|
||||
@@ -101,10 +104,37 @@ export default async (req, res) => {
|
||||
draftOrder.cart = await cartService
|
||||
.withTransaction(manager)
|
||||
.retrieve(draftOrder.cart_id, {
|
||||
relations: defaultCartRelations,
|
||||
select: defaultCartFields,
|
||||
relations: defaultAdminDraftOrdersCartRelations,
|
||||
select: defaultAdminDraftOrdersCartFields,
|
||||
})
|
||||
|
||||
res.status(200).json({ draft_order: draftOrder })
|
||||
})
|
||||
}
|
||||
|
||||
class LineItemUpdate {
|
||||
title?: string
|
||||
unit_price?: number
|
||||
quantity?: number
|
||||
metadata?: object = {}
|
||||
region_id?: string
|
||||
variant_id?: string
|
||||
}
|
||||
|
||||
export class AdminPostDraftOrdersDraftOrderLineItemsItemReq {
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
title?: string
|
||||
|
||||
@IsInt()
|
||||
@IsOptional()
|
||||
unit_price?: number
|
||||
|
||||
@IsInt()
|
||||
@IsOptional()
|
||||
quantity?: number
|
||||
|
||||
@IsObject()
|
||||
@IsOptional()
|
||||
metadata?: object = {}
|
||||
}
|
||||
+34
-19
@@ -1,11 +1,15 @@
|
||||
import { MedusaError, Validator } from "medusa-core-utils"
|
||||
import { defaultFields, defaultRelations } from "./"
|
||||
import { Type } from "class-transformer"
|
||||
import { IsBoolean, IsDate, IsInt, IsOptional, IsString } from "class-validator"
|
||||
import { defaultAdminGiftCardFields, defaultAdminGiftCardRelations } from "."
|
||||
import { GiftCardService } from "../../../../services"
|
||||
import { validator } from "../../../../utils/validator"
|
||||
|
||||
/**
|
||||
* @oas [post] /gift-cards
|
||||
* operationId: "PostGiftCards"
|
||||
* summary: "Create a Gift Card"
|
||||
* description: "Creates a Gift Card that can redeemed by its unique code. The Gift Card is only valid within 1 region."
|
||||
* x-authenticated: true
|
||||
* requestBody:
|
||||
* content:
|
||||
* application/json:
|
||||
@@ -42,30 +46,41 @@ import { defaultFields, defaultRelations } from "./"
|
||||
* $ref: "#/components/schemas/gift_card"
|
||||
*/
|
||||
export default async (req, res) => {
|
||||
const schema = Validator.object().keys({
|
||||
value: Validator.number().integer().optional(),
|
||||
ends_at: Validator.date().optional(),
|
||||
is_disabled: Validator.boolean().optional(),
|
||||
region_id: Validator.string().optional(),
|
||||
metadata: Validator.object().optional(),
|
||||
})
|
||||
const validated = await validator(AdminPostGiftCardsReq, req.body)
|
||||
|
||||
const { value, error } = schema.validate(req.body)
|
||||
if (error) {
|
||||
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
|
||||
}
|
||||
|
||||
const giftCardService = req.scope.resolve("giftCardService")
|
||||
const giftCardService: GiftCardService = req.scope.resolve("giftCardService")
|
||||
|
||||
const newly = await giftCardService.create({
|
||||
...value,
|
||||
balance: value.value,
|
||||
...validated,
|
||||
balance: validated.value,
|
||||
})
|
||||
|
||||
const giftCard = await giftCardService.retrieve(newly.id, {
|
||||
select: defaultFields,
|
||||
relations: defaultRelations,
|
||||
select: defaultAdminGiftCardFields,
|
||||
relations: defaultAdminGiftCardRelations,
|
||||
})
|
||||
|
||||
res.status(200).json({ gift_card: giftCard })
|
||||
}
|
||||
|
||||
export class AdminPostGiftCardsReq {
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
value?: number
|
||||
|
||||
@IsOptional()
|
||||
@IsDate()
|
||||
@Type(() => Date)
|
||||
ends_at?: Date
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
is_disabled?: boolean
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
region_id?: string
|
||||
|
||||
@IsOptional()
|
||||
metadata?: object
|
||||
}
|
||||
+1
@@ -3,6 +3,7 @@
|
||||
* operationId: "DeleteGiftCardsGiftCard"
|
||||
* summary: "Delete a Gift Card"
|
||||
* description: "Deletes a Gift Card"
|
||||
* x-authenticated: true
|
||||
* parameters:
|
||||
* - (path) id=* {string} The id of the Gift Card to delete.
|
||||
* tags:
|
||||
+4
-3
@@ -1,10 +1,11 @@
|
||||
import { defaultFields, defaultRelations } from "./"
|
||||
import { defaultAdminGiftCardFields, defaultAdminGiftCardRelations } from "./"
|
||||
|
||||
/**
|
||||
* @oas [get] /gift-cards/{id}
|
||||
* operationId: "GetGiftCardsGiftCard"
|
||||
* summary: "Retrieve a Gift Card"
|
||||
* description: "Retrieves a Gift Card."
|
||||
* x-authenticated: true
|
||||
* parameters:
|
||||
* - (path) id=* {string} The id of the Gift Card.
|
||||
* tags:
|
||||
@@ -24,8 +25,8 @@ export default async (req, res) => {
|
||||
|
||||
const giftCardService = req.scope.resolve("giftCardService")
|
||||
const giftCard = await giftCardService.retrieve(id, {
|
||||
select: defaultFields,
|
||||
relations: defaultRelations,
|
||||
select: defaultAdminGiftCardFields,
|
||||
relations: defaultAdminGiftCardRelations,
|
||||
})
|
||||
|
||||
res.status(200).json({ gift_card: giftCard })
|
||||
Loaded 100 of 413 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user