chore(framework): Move and improve routes loader (#8392)

* chore(framework): Move and improve routes loader

* cleanup

* fix(framework): import
This commit is contained in:
Adrien de Peretti
2024-08-01 16:18:42 +02:00
committed by GitHub
parent 4081b3359d
commit f81652bf6e
102 changed files with 925 additions and 802 deletions
+6 -1
View File
@@ -41,8 +41,10 @@
"devDependencies": {
"@medusajs/types": "^1.11.16",
"@types/express": "^4.17.17",
"@types/jsonwebtoken": "^8.5.9",
"cross-env": "^7.0.3",
"rimraf": "^3.0.2",
"supertest": "^4.0.2",
"tsc-alias": "^1.8.6",
"typescript": "^5.1.6",
"vite": "^5.2.11"
@@ -55,11 +57,14 @@
"@medusajs/workflows-sdk": "^0.1.6",
"awilix": "^8.0.0",
"cookie-parser": "^1.4.6",
"cors": "^2.8.5",
"express": "^4.18.2",
"express-session": "^1.17.3",
"ioredis": "^5.2.5",
"ioredis-mock": "8.4.0",
"jsonwebtoken": "^9.0.2",
"medusa-telemetry": "^0.0.17",
"morgan": "^1.9.1"
"morgan": "^1.9.1",
"zod": "3.22.4"
}
}
@@ -1,4 +1,4 @@
import { defineMiddlewares } from "../../../../../utils/define-middlewares"
import { defineMiddlewares } from "../../utils/define-middlewares"
export default defineMiddlewares({
errorHandler: (err, _req, res, _next) => {
@@ -1,10 +1,10 @@
import { NextFunction, Request, Response, raw } from "express"
import { NextFunction, raw, Request, Response } from "express"
import {
customersCreateMiddlewareMock,
customersGlobalMiddlewareMock,
storeGlobalMiddlewareMock,
} from "../mocks"
import { defineMiddlewares } from "../../../../../utils/define-middlewares"
import { defineMiddlewares } from "../../utils/define-middlewares"
const customersGlobalMiddleware = (
req: Request,
@@ -3,24 +3,20 @@ import {
ModulesDefinition,
registerMedusaModule,
} from "@medusajs/modules-sdk"
import {
configManager,
ConfigModule,
container,
featureFlagsLoader,
logger,
} from "@medusajs/framework"
import { ContainerRegistrationKeys, generateJwtToken } from "@medusajs/utils"
import { asValue } from "awilix"
import express from "express"
import querystring from "querystring"
import supertest from "supertest"
import apiLoader from "../../../../api"
import { getResolvedPlugins } from "../../../../helpers/resolve-plugins"
import RoutesLoader from "../.."
import { config } from "../mocks"
import { MedusaContainer } from "@medusajs/types"
import { configManager, ConfigModule } from "../../../config"
import { container } from "../../../container"
import { featureFlagsLoader } from "../../../feature-flags"
import { logger } from "../../../logger"
import { MedusaRequest } from "../../types"
import { RoutesLoader } from "../../router"
function asArray(resolvers) {
return {
@@ -90,30 +86,19 @@ export const createServer = async (rootDir) => {
next()
})
const plugins = getResolvedPlugins(rootDir, config) || []
await featureFlagsLoader()
await moduleLoader({ container, moduleResolutions, logger })
app.use((req, res, next) => {
req.scope = container.createScope() as MedusaContainer
;(req as MedusaRequest).scope = container.createScope() as MedusaContainer
next()
})
// This where plugins normally load, but we simply load the routes
await new RoutesLoader({
app,
rootDir,
configModule: config,
sourceDir: rootDir,
}).load()
// the apiLoader needs to be called after plugins otherwise the core middleware bleads into the plugins
await apiLoader({
container,
app: app,
plugins,
})
const superRequest = supertest(app)
return {
@@ -1,7 +1,6 @@
import express from "express"
import { resolve } from "path"
import {
config,
customersCreateMiddlewareMock,
customersGlobalMiddlewareMock,
storeGlobalMiddlewareMock,
@@ -230,8 +229,7 @@ describe("RoutesLoader", function () {
)
const err = await new RoutesLoader({
app,
rootDir,
configModule: config,
sourceDir: rootDir,
})
.load()
.catch((e) => e)
@@ -1 +1,5 @@
export * from "./express-loader"
export * from "./router"
export * from "./types"
export * from "./middlewares"
export * from "./utils/define-middlewares"
@@ -0,0 +1,208 @@
import { ApiKeyDTO, IApiKeyModuleService } from "@medusajs/types"
import {
ContainerRegistrationKeys,
ModuleRegistrationName,
} from "@medusajs/utils"
import { NextFunction, RequestHandler } from "express"
import { JwtPayload, verify } from "jsonwebtoken"
import {
AuthContext,
AuthenticatedMedusaRequest,
MedusaRequest,
MedusaResponse,
} from "../types"
import { ConfigModule } from "../../config"
const SESSION_AUTH = "session"
const BEARER_AUTH = "bearer"
const API_KEY_AUTH = "api-key"
// This is the only hard-coded actor type, as API keys have special handling for now. We could also generalize API keys to carry the actor type with them.
const ADMIN_ACTOR_TYPE = "user"
type AuthType = typeof SESSION_AUTH | typeof BEARER_AUTH | typeof API_KEY_AUTH
type MedusaSession = {
auth_context: AuthContext
}
export const authenticate = (
actorType: string | string[],
authType: AuthType | AuthType[],
options: { allowUnauthenticated?: boolean; allowUnregistered?: boolean } = {}
): RequestHandler => {
const handler = async (
req: MedusaRequest,
res: MedusaResponse,
next: NextFunction
): Promise<void> => {
const authTypes = Array.isArray(authType) ? authType : [authType]
const actorTypes = Array.isArray(actorType) ? actorType : [actorType]
const req_ = req as AuthenticatedMedusaRequest
// We only allow authenticating using a secret API key on the admin
const isExclusivelyUser =
actorTypes.length === 1 && actorTypes[0] === ADMIN_ACTOR_TYPE
if (authTypes.includes(API_KEY_AUTH) && isExclusivelyUser) {
const apiKey = await getApiKeyInfo(req)
if (apiKey) {
req_.auth_context = {
actor_id: apiKey.id,
actor_type: "api-key",
auth_identity_id: "",
app_metadata: {},
}
return next()
}
}
// We try to extract the auth context either from the session or from a JWT token
let authContext: AuthContext | null = getAuthContextFromSession(
req.session,
authTypes,
actorTypes
)
if (!authContext) {
const {
projectConfig: { http },
} = req.scope.resolve<ConfigModule>(
ContainerRegistrationKeys.CONFIG_MODULE
)
authContext = getAuthContextFromJwtToken(
req.headers.authorization,
http.jwtSecret!,
authTypes,
actorTypes
)
}
// If the entity is authenticated, and it is a registered actor we can continue
if (authContext?.actor_id) {
req_.auth_context = authContext
return next()
}
// If the entity is authenticated, but there is no registered actor yet, we can continue (eg. in the case of a user invite) if allow unregistered is set
if (authContext?.auth_identity_id && options.allowUnregistered) {
req_.auth_context = authContext
return next()
}
// If we allow unauthenticated requests (i.e public endpoints), just continue
if (options.allowUnauthenticated) {
return next()
}
res.status(401).json({ message: "Unauthorized" })
}
return handler as unknown as RequestHandler
}
const getApiKeyInfo = async (req: MedusaRequest): Promise<ApiKeyDTO | null> => {
const authHeader = req.headers.authorization
if (!authHeader) {
return null
}
const [tokenType, token] = authHeader.split(" ")
if (tokenType.toLowerCase() !== "basic" || !token) {
return null
}
// The token could have been base64 encoded, we want to decode it first.
let normalizedToken = token
if (!token.startsWith("sk_")) {
normalizedToken = Buffer.from(token, "base64").toString("utf-8")
}
// Basic auth is defined as a username:password set, and since the token is set to the username we need to trim the colon
if (normalizedToken.endsWith(":")) {
normalizedToken = normalizedToken.slice(0, -1)
}
// Secret tokens start with 'sk_', and if it doesn't it could be a user JWT or a malformed token
if (!normalizedToken.startsWith("sk_")) {
return null
}
const apiKeyModule = req.scope.resolve(
ModuleRegistrationName.API_KEY
) as IApiKeyModuleService
try {
const apiKey = await apiKeyModule.authenticate(normalizedToken)
if (!apiKey) {
return null
}
return apiKey
} catch (error) {
console.error(error)
return null
}
}
const getAuthContextFromSession = (
session: Partial<MedusaSession> = {},
authTypes: AuthType[],
actorTypes: string[]
): AuthContext | null => {
if (!authTypes.includes(SESSION_AUTH)) {
return null
}
if (
session.auth_context &&
(actorTypes.includes("*") ||
actorTypes.includes(session.auth_context.actor_type))
) {
return session.auth_context
}
return null
}
const getAuthContextFromJwtToken = (
authHeader: string | undefined,
jwtSecret: string,
authTypes: AuthType[],
actorTypes: string[]
): AuthContext | null => {
if (!authTypes.includes(BEARER_AUTH)) {
return null
}
if (!authHeader) {
return null
}
const re = /(\S+)\s+(\S+)/
const matches = authHeader.match(re)
// TODO: figure out how to obtain token (and store correct data in token)
if (matches) {
const tokenType = matches[1]
const token = matches[2]
if (tokenType.toLowerCase() === BEARER_AUTH) {
// get config jwt secret
// verify token and set authUser
try {
const verified = verify(token, jwtSecret) as JwtPayload
if (
actorTypes.includes("*") ||
actorTypes.includes(verified.actor_type)
) {
return verified as AuthContext
}
} catch (err) {
return null
}
}
}
return null
}
@@ -0,0 +1,102 @@
import { NextFunction, Response } from "express"
import { ContainerRegistrationKeys, MedusaError } from "@medusajs/utils"
import { formatException } from "./exception-formatter"
import { MedusaRequest } from "../types"
import { logger as logger_ } from "../../logger"
const QUERY_RUNNER_RELEASED = "QueryRunnerAlreadyReleasedError"
const TRANSACTION_STARTED = "TransactionAlreadyStartedError"
const TRANSACTION_NOT_STARTED = "TransactionNotStartedError"
const API_ERROR = "api_error"
const INVALID_REQUEST_ERROR = "invalid_request_error"
const INVALID_STATE_ERROR = "invalid_state_error"
export function errorHandler() {
return (
err: MedusaError,
req: MedusaRequest,
res: Response,
next: NextFunction
) => {
const logger: typeof logger_ = req.scope.resolve(
ContainerRegistrationKeys.LOGGER
)
err = formatException(err)
logger.error(err)
const errorType = err.type || err.name
const errObj = {
code: err.code,
type: err.type,
message: err.message,
}
let statusCode = 500
switch (errorType) {
case QUERY_RUNNER_RELEASED:
case TRANSACTION_STARTED:
case TRANSACTION_NOT_STARTED:
case MedusaError.Types.CONFLICT:
statusCode = 409
errObj.code = INVALID_STATE_ERROR
errObj.message =
"The request conflicted with another request. You may retry the request with the provided Idempotency-Key."
break
case MedusaError.Types.UNAUTHORIZED:
statusCode = 401
break
case MedusaError.Types.PAYMENT_AUTHORIZATION_ERROR:
statusCode = 422
break
case MedusaError.Types.DUPLICATE_ERROR:
statusCode = 422
errObj.code = INVALID_REQUEST_ERROR
break
case MedusaError.Types.NOT_ALLOWED:
case MedusaError.Types.INVALID_DATA:
statusCode = 400
break
case MedusaError.Types.NOT_FOUND:
statusCode = 404
break
case MedusaError.Types.DB_ERROR:
statusCode = 500
errObj.code = API_ERROR
break
case MedusaError.Types.UNEXPECTED_STATE:
case MedusaError.Types.INVALID_ARGUMENT:
break
default:
errObj.code = "unknown_error"
errObj.message = "An unknown error occurred."
errObj.type = "unknown_error"
break
}
res.status(statusCode).json(errObj)
}
}
/**
* @schema Error
* title: "Response Error"
* type: object
* properties:
* code:
* type: string
* description: A slug code to indicate the type of the error.
* enum: [invalid_state_error, invalid_request_error, api_error, unknown_error]
* message:
* type: string
* description: Description of the error that occurred.
* example: "first_name must be a string"
* type:
* type: string
* description: A slug indicating the type of the error.
* enum: [QueryRunnerAlreadyReleasedError, TransactionAlreadyStartedError, TransactionNotStartedError, conflict, unauthorized, payment_authorization_error, duplicate_error, not_allowed, invalid_data, not_found, database_error, unexpected_state, invalid_argument, unknown_error]
*/
@@ -0,0 +1,56 @@
import { MedusaError } from "@medusajs/utils"
export enum PostgresError {
DUPLICATE_ERROR = "23505",
FOREIGN_KEY_ERROR = "23503",
SERIALIZATION_FAILURE = "40001",
NULL_VIOLATION = "23502",
}
export const formatException = (err): MedusaError => {
switch (err.code) {
case PostgresError.DUPLICATE_ERROR:
return new MedusaError(
MedusaError.Types.DUPLICATE_ERROR,
`${err.table.charAt(0).toUpperCase()}${err.table.slice(
1
)} with ${err.detail.slice(4).replace(/[()=]/g, (s) => {
return s === "=" ? " " : ""
})}`
)
case PostgresError.FOREIGN_KEY_ERROR: {
const matches =
/Key \(([\w-\d]+)\)=\(([\w-\d]+)\) is not present in table "(\w+)"/g.exec(
err.detail
)
if (matches?.length !== 4) {
return new MedusaError(
MedusaError.Types.NOT_FOUND,
JSON.stringify(matches)
)
}
return new MedusaError(
MedusaError.Types.NOT_FOUND,
`${matches[3]?.charAt(0).toUpperCase()}${matches[3]?.slice(1)} with ${
matches[1]
} ${matches[2]} does not exist.`
)
}
case PostgresError.SERIALIZATION_FAILURE: {
return new MedusaError(
MedusaError.Types.CONFLICT,
err?.detail ?? err?.message
)
}
case PostgresError.NULL_VIOLATION: {
return new MedusaError(
MedusaError.Types.INVALID_DATA,
`Can't insert null value in field ${err?.column} on insert in table ${err?.table}`
)
}
default:
return err
}
}
@@ -0,0 +1,3 @@
export * from "./authenticate-middleware"
export * from "./error-handler"
export * from "./exception-formatter"
@@ -1,16 +1,13 @@
import { ConfigModule } from "@medusajs/types"
import { parseCorsOrigins, promiseAll, wrapHandler } from "@medusajs/utils"
import cors from "cors"
import { type Express, json, Router, text, urlencoded } from "express"
import { readdir } from "fs/promises"
import { extname, join, sep } from "path"
import { MedusaRequest, MedusaResponse } from "../../../types/routing"
import { authenticate, errorHandler } from "../../../utils/middlewares"
import { logger } from "@medusajs/framework"
import { extname, join, parse, sep } from "path"
import {
AsyncRouteHandler,
GlobalMiddlewareDescriptor,
HTTP_METHODS,
MedusaRequest,
MedusaResponse,
MiddlewareRoute,
MiddlewaresConfig,
MiddlewareVerb,
@@ -19,6 +16,9 @@ import {
RouteDescriptor,
RouteVerb,
} from "./types"
import { authenticate, errorHandler } from "./middlewares"
import { configManager } from "../config"
import { logger } from "../logger"
const log = ({
activityId,
@@ -163,40 +163,68 @@ function getBodyParserMiddleware(args?: ParserConfigArgs) {
]
}
export class RoutesLoader {
protected routesMap = new Map<string, RouteDescriptor>()
protected globalMiddlewaresDescriptor: GlobalMiddlewareDescriptor | undefined
// TODO this router would need a proper rework, but it is out of scope right now
protected app: Express
protected router: Router
protected activityId?: string
protected rootDir: string
protected configModule: ConfigModule
protected excludes: RegExp[] = [
class ApiRoutesLoader {
/**
* Map of router path and its descriptor
* @private
*/
#routesMap = new Map<string, RouteDescriptor>()
/**
* Global middleware descriptors
* @private
*/
#globalMiddlewaresDescriptor: GlobalMiddlewareDescriptor | undefined
/**
* An express instance
* @private
*/
readonly #app: Express
/**
* A router to assign the route to
* @private
*/
readonly #router: Router
/**
* An eventual activity id for information tracking
* @private
*/
readonly #activityId?: string
/**
* The list of file names to exclude from the routes scan
* @private
*/
#excludes: RegExp[] = [
/\.DS_Store/,
/(\.ts\.map|\.js\.map|\.d\.ts)/,
/(\.ts\.map|\.js\.map|\.d\.ts|\.md)/,
/^_[^/\\]*(\.[^/\\]+)?$/,
]
/**
* Path from where to load the routes from
* @private
*/
readonly #sourceDir: string
constructor({
app,
activityId,
rootDir,
configModule,
excludes = [],
sourceDir,
}: {
app: Express
activityId?: string
rootDir: string
configModule: ConfigModule
excludes?: RegExp[]
sourceDir: string
}) {
this.app = app
this.router = Router()
this.activityId = activityId
this.rootDir = rootDir
this.configModule = configModule
this.excludes.push(...(excludes ?? []))
this.#app = app
this.#router = Router()
this.#activityId = activityId
this.#sourceDir = sourceDir
}
/**
@@ -215,7 +243,7 @@ export class RoutesLoader {
}): void {
if (!config?.routes && !config?.errorHandler) {
log({
activityId: this.activityId,
activityId: this.#activityId,
message: `Empty middleware config. Skipping middleware application.`,
})
@@ -255,7 +283,7 @@ export class RoutesLoader {
if (match?.[1] && !Number.isInteger(match?.[1])) {
if (parameters.has(match?.[1])) {
log({
activityId: this.activityId,
activityId: this.#activityId,
message: `Duplicate parameters found in route ${route} (${match?.[1]})`,
})
@@ -289,12 +317,12 @@ export class RoutesLoader {
*/
protected async createRoutesConfig(): Promise<void> {
await promiseAll(
[...this.routesMap.values()].map(async (descriptor: RouteDescriptor) => {
[...this.#routesMap.values()].map(async (descriptor: RouteDescriptor) => {
const absolutePath = descriptor.absolutePath
const route = descriptor.route
return await import(absolutePath).then((import_) => {
const map = this.routesMap
const map = this.#routesMap
const config: RouteConfig = {
routes: [],
@@ -353,7 +381,7 @@ export class RoutesLoader {
})
} else {
log({
activityId: this.activityId,
activityId: this.#activityId,
message: `Skipping handler ${handler} in ${absolutePath}. Invalid HTTP method: ${handler}.`,
})
}
@@ -361,7 +389,7 @@ export class RoutesLoader {
if (!config.routes?.length) {
log({
activityId: this.activityId,
activityId: this.#activityId,
message: `No valid route handlers detected in ${absolutePath}. Skipping route configuration.`,
})
@@ -376,29 +404,20 @@ export class RoutesLoader {
)
}
protected createRoutesDescriptor({
childPath,
parentPath,
}: {
childPath: string
parentPath?: string
}) {
protected createRoutesDescriptor(path: string) {
const descriptor: RouteDescriptor = {
absolutePath: childPath,
relativePath: "",
absolutePath: path,
relativePath: path,
route: "",
priority: Infinity,
}
if (parentPath) {
childPath = childPath.replace(parentPath, "")
}
const childPath = path.replace(this.#sourceDir, "")
descriptor.relativePath = childPath
let routeToParse = childPath
const pathSegments = childPath.split(sep)
const pathSegments = routeToParse.split(sep)
const lastSegment = pathSegments[pathSegments.length - 1]
if (lastSegment.startsWith("route")) {
@@ -409,40 +428,36 @@ export class RoutesLoader {
descriptor.route = this.parseRoute(routeToParse)
descriptor.priority = calculatePriority(descriptor.route)
this.routesMap.set(childPath, descriptor)
this.#routesMap.set(path, descriptor)
}
protected async createMiddlewaresDescriptor({
dirPath,
}: {
dirPath: string
}) {
const files = await readdir(dirPath)
protected async createMiddlewaresDescriptor() {
const filePaths = await readdir(this.#sourceDir)
const middlewareFilePath = files
.filter((path) => {
if (
this.excludes.length &&
this.excludes.some((exclude) => exclude.test(path))
) {
return false
}
const filteredFilePaths = filePaths.filter((path) => {
const pathToCheck = path.replace(this.#sourceDir, "")
return !pathToCheck
.split(sep)
.some((segment) =>
this.#excludes.some((exclude) => exclude.test(segment))
)
})
return true
})
.find((file) => {
return file.replace(/\.[^/.]+$/, "") === MIDDLEWARES_NAME
})
const middlewareFilePath = filteredFilePaths.find((file) => {
return file.replace(/\.[^/.]+$/, "") === MIDDLEWARES_NAME
})
if (!middlewareFilePath) {
log({
activityId: this.activityId,
message: `No middleware files found in ${dirPath}. Skipping middleware configuration.`,
activityId: this.#activityId,
message: `No middleware files found in ${
this.#sourceDir
}. Skipping middleware configuration.`,
})
return
}
const absolutePath = join(dirPath, middlewareFilePath)
const absolutePath = join(this.#sourceDir, middlewareFilePath)
try {
await import(absolutePath).then((import_) => {
@@ -452,7 +467,7 @@ export class RoutesLoader {
if (!middlewaresConfig) {
log({
activityId: this.activityId,
activityId: this.#activityId,
message: `No middleware configuration found in ${absolutePath}. Skipping middleware configuration.`,
})
return
@@ -471,11 +486,11 @@ export class RoutesLoader {
this.validateMiddlewaresConfig(descriptor)
this.globalMiddlewaresDescriptor = descriptor
this.#globalMiddlewaresDescriptor = descriptor
})
} catch (error) {
log({
activityId: this.activityId,
activityId: this.#activityId,
message: `Failed to load middleware configuration in ${absolutePath}. Skipping middleware configuration.`,
})
@@ -483,54 +498,34 @@ export class RoutesLoader {
}
}
protected async createRoutesMap({
dirPath,
parentPath,
}: {
dirPath: string
parentPath?: string
}): Promise<void> {
protected async createRoutesMap(): Promise<void> {
await promiseAll(
await readdir(dirPath, { withFileTypes: true }).then((entries) => {
return entries
.filter((entry) => {
if (
this.excludes.length &&
this.excludes.some((exclude) => exclude.test(entry.name))
) {
return false
}
await readdir(this.#sourceDir, {
recursive: true,
withFileTypes: true,
}).then((entries) => {
const fileEntries = entries.filter((entry) => {
const fullPathFromSource = join(entry.path, entry.name).replace(
this.#sourceDir,
""
)
const isExcluded = fullPathFromSource
.split(sep)
.some((segment) =>
this.#excludes.some((exclude) => exclude.test(segment))
)
let name = entry.name
return (
!entry.isDirectory() &&
!isExcluded &&
parse(entry.name).name === ROUTE_NAME
)
})
const extension = extname(name)
if (extension) {
name = name.replace(extension, "")
}
if (entry.isFile() && name !== ROUTE_NAME) {
return false
}
return true
})
.map(async (entry) => {
const childPath = join(dirPath, entry.name)
if (entry.isDirectory()) {
return await this.createRoutesMap({
dirPath: childPath,
parentPath: parentPath ?? dirPath,
})
}
return this.createRoutesDescriptor({
childPath,
parentPath,
})
})
.flat(Infinity)
return fileEntries.map(async (entry) => {
const path = join(entry.path, entry.name)
return this.createRoutesDescriptor(path)
})
})
)
}
@@ -539,7 +534,7 @@ export class RoutesLoader {
* Apply the most specific body parser middleware to the router
*/
applyBodyParserMiddleware(path: string, method: RouteVerb): void {
const middlewareDescriptor = this.globalMiddlewaresDescriptor
const middlewareDescriptor = this.#globalMiddlewaresDescriptor
const mostSpecificConfig = findMatch(
path,
@@ -548,13 +543,13 @@ export class RoutesLoader {
)
if (!mostSpecificConfig || mostSpecificConfig?.bodyParser === undefined) {
this.router[method.toLowerCase()](path, ...getBodyParserMiddleware())
this.#router[method.toLowerCase()](path, ...getBodyParserMiddleware())
return
}
if (mostSpecificConfig?.bodyParser) {
this.router[method.toLowerCase()](
this.#router[method.toLowerCase()](
path,
...getBodyParserMiddleware(mostSpecificConfig?.bodyParser)
)
@@ -572,7 +567,7 @@ export class RoutesLoader {
* that they are applied before any other middleware.
*/
applyRouteSpecificMiddlewares(): void {
const prioritizedRoutes = prioritize([...this.routesMap.values()])
const prioritizedRoutes = prioritize([...this.#routesMap.values()])
for (const descriptor of prioritizedRoutes) {
if (!descriptor.config?.routes?.length) {
@@ -591,11 +586,11 @@ export class RoutesLoader {
/**
* Apply the admin cors
*/
this.router.use(
this.#router.use(
descriptor.route,
cors({
origin: parseCorsOrigins(
this.configModule.projectConfig.http.adminCors
configManager.config.projectConfig.http.adminCors
),
credentials: true,
})
@@ -606,11 +601,11 @@ export class RoutesLoader {
/**
* Apply the auth cors
*/
this.router.use(
this.#router.use(
descriptor.route,
cors({
origin: parseCorsOrigins(
this.configModule.projectConfig.http.authCors
configManager.config.projectConfig.http.authCors
),
credentials: true,
})
@@ -621,11 +616,11 @@ export class RoutesLoader {
/**
* Apply the store cors
*/
this.router.use(
this.#router.use(
descriptor.route,
cors({
origin: parseCorsOrigins(
this.configModule.projectConfig.http.storeCors
configManager.config.projectConfig.http.storeCors
),
credentials: true,
})
@@ -634,7 +629,7 @@ export class RoutesLoader {
// We only apply the auth middleware to store routes to populate the auth context. For actual authentication, users can just reapply the middleware.
if (!config.optedOutOfAuth && config.routeType === "store") {
this.router.use(
this.#router.use(
descriptor.route,
authenticate("customer", ["bearer", "session"], {
allowUnauthenticated: true,
@@ -644,7 +639,7 @@ export class RoutesLoader {
if (!config.optedOutOfAuth && config.routeType === "admin") {
// We probably don't want to allow access to all endpoints using an api key, but it will do until we revamp our routing.
this.router.use(
this.#router.use(
descriptor.route,
authenticate("user", ["bearer", "session", "api-key"])
)
@@ -664,7 +659,7 @@ export class RoutesLoader {
* Apply the error handler middleware to the router
*/
applyErrorHandlerMiddleware(): void {
const middlewareDescriptor = this.globalMiddlewaresDescriptor
const middlewareDescriptor = this.#globalMiddlewaresDescriptor
const errorHandlerFn = middlewareDescriptor?.config?.errorHandler
/**
@@ -678,7 +673,7 @@ export class RoutesLoader {
* If the user has provided a custom error handler then use it
*/
if (errorHandlerFn) {
this.router.use(errorHandlerFn)
this.#router.use(errorHandlerFn as any)
return
}
@@ -686,17 +681,17 @@ export class RoutesLoader {
* If the user has not provided a custom error handler then use the
* default one.
*/
this.router.use(errorHandler())
this.#router.use(errorHandler() as any)
}
protected async registerRoutes(): Promise<void> {
const middlewareDescriptor = this.globalMiddlewaresDescriptor
const middlewareDescriptor = this.#globalMiddlewaresDescriptor
const shouldWrapHandler = middlewareDescriptor?.config
? middlewareDescriptor.config.errorHandler !== false
: true
const prioritizedRoutes = prioritize([...this.routesMap.values()])
const prioritizedRoutes = prioritize([...this.#routesMap.values()])
for (const descriptor of prioritizedRoutes) {
if (!descriptor.config?.routes?.length) {
@@ -707,7 +702,7 @@ export class RoutesLoader {
for (const route of routes) {
log({
activityId: this.activityId,
activityId: this.#activityId,
message: `Registering route [${route.method?.toUpperCase()}] - ${
descriptor.route
}`,
@@ -718,16 +713,16 @@ export class RoutesLoader {
* we wrap the handler in a try/catch block.
*/
const handler = shouldWrapHandler
? wrapHandler(route.handler as AsyncRouteHandler)
? wrapHandler(route.handler as Parameters<typeof wrapHandler>[0])
: route.handler
this.router[route.method!.toLowerCase()](descriptor.route, handler)
this.#router[route.method!.toLowerCase()](descriptor.route, handler)
}
}
}
protected async registerMiddlewares(): Promise<void> {
const descriptor = this.globalMiddlewaresDescriptor
const descriptor = this.#globalMiddlewaresDescriptor
if (!descriptor) {
return
@@ -757,17 +752,17 @@ export class RoutesLoader {
for (const method of methods) {
log({
activityId: this.activityId,
activityId: this.#activityId,
message: `Registering middleware [${method}] - ${route.matcher}`,
})
this.router[method.toLowerCase()](route.matcher, ...route.middlewares)
this.#router[method.toLowerCase()](route.matcher, ...route.middlewares)
}
}
}
async load() {
performance && performance.mark("file-base-routing-start" + this.rootDir)
performance && performance.mark("file-base-routing-start" + this.#sourceDir)
let apiExists = true
@@ -778,15 +773,15 @@ export class RoutesLoader {
* directory does not exist.
*/
try {
await readdir(this.rootDir)
await readdir(this.#sourceDir)
} catch (_error) {
apiExists = false
}
if (apiExists) {
await this.createMiddlewaresDescriptor({ dirPath: this.rootDir })
await this.createMiddlewaresDescriptor()
await this.createRoutesMap({ dirPath: this.rootDir })
await this.createRoutesMap()
await this.createRoutesConfig()
this.applyRouteSpecificMiddlewares()
@@ -802,28 +797,78 @@ export class RoutesLoader {
* This prevents middleware from a plugin from
* bleeding into the global middleware stack.
*/
this.app.use("/", this.router)
this.#app.use("/", this.#router)
}
performance && performance.mark("file-base-routing-end" + this.rootDir)
performance && performance.mark("file-base-routing-end" + this.#sourceDir)
const timeSpent =
performance &&
performance
.measure(
"file-base-routing-measure" + this.rootDir,
"file-base-routing-start" + this.rootDir,
"file-base-routing-end" + this.rootDir
"file-base-routing-measure" + this.#sourceDir,
"file-base-routing-start" + this.#sourceDir,
"file-base-routing-end" + this.#sourceDir
)
?.duration?.toFixed(2)
log({
activityId: this.activityId,
activityId: this.#activityId,
message: `Routes loaded in ${timeSpent} ms`,
})
this.routesMap.clear()
this.globalMiddlewaresDescriptor = undefined
this.#routesMap.clear()
this.#globalMiddlewaresDescriptor = undefined
}
}
export default RoutesLoader
export class RoutesLoader {
/**
* An express instance
* @private
*/
readonly #app: Express
/**
* An eventual activity id for information tracking
* @private
*/
readonly #activityId?: string
/**
* Path from where to load the routes from
* @private
*/
readonly #sourceDir: string | string[]
constructor({
app,
activityId,
sourceDir,
}: {
app: Express
activityId?: string
sourceDir: string | string[]
}) {
this.#app = app
this.#activityId = activityId
this.#sourceDir = sourceDir
}
async load() {
const normalizedSourcePath = Array.isArray(this.#sourceDir)
? this.#sourceDir
: [this.#sourceDir]
const promises = normalizedSourcePath.map(async (sourcePath) => {
const apiRoutesLoader = new ApiRoutesLoader({
app: this.#app,
activityId: this.#activityId,
sourceDir: sourcePath,
})
await apiRoutesLoader.load()
})
await promiseAll(promises)
}
}
@@ -0,0 +1,180 @@
import { ZodObject } from "zod"
import type { NextFunction, Request, Response } from "express"
import { MedusaPricingContext, RequestQueryFields } from "@medusajs/types"
import * as core from "express-serve-static-core"
import { MedusaContainer } from "../container"
export interface FindConfig<Entity> {
select?: (keyof Entity)[]
skip?: number
take?: number
relations?: string[]
order?: { [K: string]: "ASC" | "DESC" }
}
/**
* List of all the supported HTTP methods
*/
export const HTTP_METHODS = [
"GET",
"POST",
"PUT",
"PATCH",
"DELETE",
"OPTIONS",
"HEAD",
] as const
export type RouteVerb = (typeof HTTP_METHODS)[number]
export type MiddlewareVerb = "USE" | "ALL" | RouteVerb
type SyncRouteHandler = (req: MedusaRequest, res: MedusaResponse) => void
export type AsyncRouteHandler = (
req: MedusaRequest,
res: MedusaResponse
) => Promise<void>
type RouteHandler = SyncRouteHandler | AsyncRouteHandler
export type RouteImplementation = {
method?: RouteVerb
handler: RouteHandler
}
export type RouteConfig = {
optedOutOfAuth?: boolean
routeType?: "admin" | "store" | "auth"
shouldAppendAdminCors?: boolean
shouldAppendStoreCors?: boolean
shouldAppendAuthCors?: boolean
routes?: RouteImplementation[]
}
export type MiddlewareFunction =
| MedusaRequestHandler
| ((...args: any[]) => any)
export type MedusaErrorHandlerFunction = (
error: any,
req: MedusaRequest,
res: MedusaResponse,
next: MedusaNextFunction
) => Promise<void> | void
export type ParserConfigArgs = {
sizeLimit?: string | number | undefined
preserveRawBody?: boolean
}
export type ParserConfig = false | ParserConfigArgs
export type MiddlewareRoute = {
method?: MiddlewareVerb | MiddlewareVerb[]
matcher: string | RegExp
bodyParser?: ParserConfig
middlewares?: MiddlewareFunction[]
}
export type MiddlewaresConfig = {
errorHandler?: false | MedusaErrorHandlerFunction
routes?: MiddlewareRoute[]
}
export type RouteDescriptor = {
absolutePath: string
relativePath: string
route: string
priority: number
config?: RouteConfig
}
export type GlobalMiddlewareDescriptor = {
config?: MiddlewaresConfig
}
export interface MedusaRequest<Body = unknown>
extends Request<core.ParamsDictionary, any, Body> {
validatedBody: Body
validatedQuery: RequestQueryFields & Record<string, unknown>
/**
* TODO: shouldn't this correspond to returnable fields instead of allowed fields? also it is used by the cleanResponseData util
*/
allowedProperties: string[]
/**
* An object containing the select, relation, skip, take and order to be used with medusa internal services
*/
listConfig: FindConfig<unknown>
/**
* An object containing the select, relation to be used with medusa internal services
*/
retrieveConfig: FindConfig<unknown>
/**
* An object containing fields and variables to be used with the remoteQuery
*/
remoteQueryConfig: {
fields: string[]
pagination: { order?: Record<string, string>; skip?: number; take?: number }
}
/**
* An object containing the fields that are filterable e.g `{ id: Any<String> }`
*/
filterableFields: Record<string, unknown>
includes?: Record<string, boolean>
/**
* An array of fields and relations that are allowed to be queried, this can be set by the
* consumer as part of a middleware and it will take precedence over the defaultAllowedFields
* @deprecated use `allowed` instead
*/
allowedFields?: string[]
/**
* An array of fields and relations that are allowed to be queried, this can be set by the
* consumer as part of a middleware and it will take precedence over the defaultAllowedFields set
* by the api
*/
allowed?: string[]
errors: string[]
scope: MedusaContainer
session?: any
rawBody?: any
requestId?: string
/**
* An object that carries the context that is used to calculate prices for variants
*/
pricingContext?: MedusaPricingContext
/**
* A generic context object that can be used across the request lifecycle
*/
context?: Record<string, any>
/**
* Custom validators for the request body and query params that will be
* merged with the original validator of the route.
*/
extendedValidators?: {
body?: ZodObject<any, any>
queryParams?: ZodObject<any, any>
}
}
export interface AuthContext {
actor_id: string
actor_type: string
auth_identity_id: string
app_metadata: Record<string, unknown>
}
export interface AuthenticatedMedusaRequest<Body = never>
extends MedusaRequest<Body> {
auth_context: AuthContext
}
export type MedusaResponse<Body = unknown> = Response<Body>
export type MedusaNextFunction = NextFunction
export type MedusaRequestHandler<Body = unknown, Res = unknown> = (
req: MedusaRequest<Body>,
res: MedusaResponse<Res>,
next: MedusaNextFunction
) => Promise<void> | void
@@ -0,0 +1,65 @@
import {
MedusaNextFunction,
MedusaRequest,
MedusaRequestHandler,
MedusaResponse,
MiddlewaresConfig,
MiddlewareVerb,
ParserConfig,
} from "../types"
import { ZodObject } from "zod"
/**
* A helper function to configure the routes by defining custom middleware,
* bodyparser config and validators to be merged with the pre-existing
* route validators.
*/
export function defineMiddlewares<
Route extends {
method?: MiddlewareVerb | MiddlewareVerb[]
matcher: string | RegExp
bodyParser?: ParserConfig
extendedValidators?: {
body?: ZodObject<any, any>
queryParams?: ZodObject<any, any>
}
// eslint-disable-next-line space-before-function-paren
middlewares?: (<Req extends MedusaRequest>(
req: Req,
res: MedusaResponse,
next: MedusaNextFunction
) => any)[]
}
>(
config:
| Route[]
| { routes?: Route[]; errorHandler?: MiddlewaresConfig["errorHandler"] }
): MiddlewaresConfig {
const routes = Array.isArray(config) ? config : config.routes || []
const errorHandler = Array.isArray(config) ? undefined : config.errorHandler
return {
errorHandler,
routes: routes.map((route) => {
const { middlewares, extendedValidators, ...rest } = route
const customMiddleware: MedusaRequestHandler[] = []
/**
* Define a custom validator when "extendedValidators.body" or
* "extendedValidators.queryParams" validation schema is
* provided.
*/
if (extendedValidators?.body || extendedValidators?.queryParams) {
customMiddleware.push((req, _, next) => {
req.extendedValidators = extendedValidators
next()
})
}
return {
...rest,
middlewares: customMiddleware.concat(middlewares || []),
}
}),
}
}
@@ -1,6 +1,6 @@
import * as QueryConfig from "./query-config"
import { MiddlewareRoute } from "../../../loaders/helpers/routing/types"
import { MiddlewareRoute } from "@medusajs/framework"
import { validateAndTransformQuery } from "../../utils/validate-query"
import {
AdminCreateApiKey,
@@ -1,4 +1,4 @@
import { MiddlewareRoute } from "../../../loaders/helpers/routing/types"
import { MiddlewareRoute } from "@medusajs/framework"
import { validateAndTransformBody } from "../../utils/validate-body"
import { validateAndTransformQuery } from "../../utils/validate-query"
import { createLinkBody } from "../../utils/validators"
@@ -1,4 +1,4 @@
import { MiddlewareRoute } from "../../../loaders/helpers/routing/types"
import { MiddlewareRoute } from "@medusajs/framework"
import { validateAndTransformBody } from "../../utils/validate-body"
import { validateAndTransformQuery } from "../../utils/validate-query"
import * as QueryConfig from "./query-config"
@@ -1,5 +1,5 @@
import * as QueryConfig from "./query-config"
import { MiddlewareRoute } from "../../../loaders/helpers/routing/types"
import { MiddlewareRoute } from "@medusajs/framework"
import { validateAndTransformQuery } from "../../utils/validate-query"
import {
AdminCreateCollection,
@@ -1,4 +1,4 @@
import { MiddlewareRoute } from "../../../loaders/helpers/routing/types"
import { MiddlewareRoute } from "@medusajs/framework"
import { validateAndTransformQuery } from "../../utils/validate-query"
import * as QueryConfig from "./query-config"
import { AdminGetCurrencyParams, AdminGetCurrenciesParams } from "./validators"
@@ -1,4 +1,4 @@
import { MiddlewareRoute } from "../../../loaders/helpers/routing/types"
import { MiddlewareRoute } from "@medusajs/framework"
import { validateAndTransformBody } from "../../utils/validate-body"
import { validateAndTransformQuery } from "../../utils/validate-query"
import { createLinkBody } from "../../utils/validators"
@@ -10,7 +10,7 @@ import {
AdminUpdateCustomerAddress,
} from "./validators"
import { MiddlewareRoute } from "../../../loaders/helpers/routing/types"
import { MiddlewareRoute } from "@medusajs/framework"
import { validateAndTransformBody } from "../../utils/validate-body"
import { validateAndTransformQuery } from "../../utils/validate-query"
@@ -1,4 +1,4 @@
import { MiddlewareRoute } from "../../../loaders/helpers/routing/types"
import { MiddlewareRoute } from "@medusajs/framework"
import { validateAndTransformBody } from "../../utils/validate-body"
import { validateAndTransformQuery } from "../../utils/validate-query"
import * as QueryConfig from "./query-config"
@@ -1,4 +1,4 @@
import { MiddlewareRoute } from "../../../types/middlewares"
import { MiddlewareRoute } from "@medusajs/framework"
import { maybeApplyLinkFilter } from "../../utils/maybe-apply-link-filter"
import { validateAndTransformQuery } from "../../utils/validate-query"
import * as QueryConfig from "./query-config"
@@ -1,4 +1,4 @@
import { MiddlewareRoute } from "../../../types/middlewares"
import { MiddlewareRoute } from "@medusajs/framework"
import { validateAndTransformBody } from "../../utils/validate-body"
import { validateAndTransformQuery } from "../../utils/validate-query"
import * as QueryConfig from "./query-config"
@@ -1,4 +1,4 @@
import { MiddlewareRoute } from "../../../types/middlewares"
import { MiddlewareRoute } from "@medusajs/framework"
import { validateAndTransformBody } from "../../utils/validate-body"
import { validateAndTransformQuery } from "../../utils/validate-query"
import * as QueryConfig from "./query-config"
@@ -1,5 +1,5 @@
import * as QueryConfig from "./query-config"
import { MiddlewareRoute } from "../../../types/middlewares"
import { MiddlewareRoute } from "@medusajs/framework"
import { validateAndTransformQuery } from "../../utils/validate-query"
import {
AdminCreateInventoryItem,
@@ -8,7 +8,7 @@ import {
AdminInviteAccept,
} from "./validators"
import { MiddlewareRoute } from "../../../types/middlewares"
import { MiddlewareRoute } from "@medusajs/framework"
import { authenticate } from "../../../utils/middlewares/authenticate-middleware"
import { validateAndTransformQuery } from "../../utils/validate-query"
import { validateAndTransformBody } from "../../utils/validate-body"
@@ -1,4 +1,4 @@
import { MiddlewareRoute } from "../../../loaders/helpers/routing/types"
import { MiddlewareRoute } from "@medusajs/framework"
import { validateAndTransformQuery } from "../../utils/validate-query"
import * as QueryConfig from "./query-config"
import {
@@ -1,4 +1,4 @@
import { MiddlewareRoute } from "../../../loaders/helpers/routing/types"
import { MiddlewareRoute } from "@medusajs/framework"
import { validateAndTransformBody } from "../../utils/validate-body"
import { validateAndTransformQuery } from "../../utils/validate-query"
import * as QueryConfig from "./query-config"
@@ -1,4 +1,4 @@
import { MiddlewareRoute } from "../../../types/middlewares"
import { MiddlewareRoute } from "@medusajs/framework"
import { unlessPath } from "../../utils/unless-path"
import { validateAndTransformBody } from "../../utils/validate-body"
import { validateAndTransformQuery } from "../../utils/validate-query"
@@ -1,4 +1,4 @@
import { MiddlewareRoute } from "../../../loaders/helpers/routing/types"
import { MiddlewareRoute } from "@medusajs/framework"
import { validateAndTransformBody } from "../../utils/validate-body"
import { validateAndTransformQuery } from "../../utils/validate-query"
import { createBatchBody, createLinkBody } from "../../utils/validators"
@@ -1,4 +1,4 @@
import { MiddlewareRoute } from "../../../loaders/helpers/routing/types"
import { MiddlewareRoute } from "@medusajs/framework"
import { validateAndTransformBody } from "../../utils/validate-body"
import { validateAndTransformQuery } from "../../utils/validate-query"
import * as QueryConfig from "./query-config"
@@ -1,4 +1,4 @@
import { MiddlewareRoute } from "../../../loaders/helpers/routing/types"
import { MiddlewareRoute } from "@medusajs/framework"
import { validateAndTransformBody } from "../../utils/validate-body"
import { validateAndTransformQuery } from "../../utils/validate-query"
import { createLinkBody } from "../../utils/validators"
@@ -1,5 +1,5 @@
import * as QueryConfig from "./query-config"
import { MiddlewareRoute } from "../../../loaders/helpers/routing/types"
import { MiddlewareRoute } from "@medusajs/framework"
import { validateAndTransformQuery } from "../../utils/validate-query"
import {
AdminCreateProductTag,
@@ -1,5 +1,5 @@
import * as QueryConfig from "./query-config"
import { MiddlewareRoute } from "../../../loaders/helpers/routing/types"
import { MiddlewareRoute } from "@medusajs/framework"
import { validateAndTransformQuery } from "../../utils/validate-query"
import {
AdminCreateProductType,
@@ -1,4 +1,4 @@
import { MiddlewareRoute } from "../../../loaders/helpers/routing/types"
import { MiddlewareRoute } from "@medusajs/framework"
import { maybeApplyLinkFilter } from "../../utils/maybe-apply-link-filter"
import { unlessPath } from "../../utils/unless-path"
import { validateAndTransformBody } from "../../utils/validate-body"
@@ -1,4 +1,4 @@
import { MiddlewareRoute } from "../../../loaders/helpers/routing/types"
import { MiddlewareRoute } from "@medusajs/framework"
import { unlessPath } from "../../utils/unless-path"
import { validateAndTransformBody } from "../../utils/validate-body"
import { validateAndTransformQuery } from "../../utils/validate-query"
@@ -1,5 +1,5 @@
import * as QueryConfig from "./query-config"
import { MiddlewareRoute } from "../../../loaders/helpers/routing/types"
import { MiddlewareRoute } from "@medusajs/framework"
import { validateAndTransformQuery } from "../../utils/validate-query"
import {
AdminCreateRegion,
@@ -1,6 +1,6 @@
import * as QueryConfig from "./query-config"
import { MiddlewareRoute } from "../../../loaders/helpers/routing/types"
import { MiddlewareRoute } from "@medusajs/framework"
import { validateAndTransformQuery } from "../../utils/validate-query"
import {
AdminCreateReservation,
@@ -1,4 +1,4 @@
import { MiddlewareRoute } from "../../../loaders/helpers/routing/types"
import { MiddlewareRoute } from "@medusajs/framework"
import { validateAndTransformBody } from "../../utils/validate-body"
import { validateAndTransformQuery } from "../../utils/validate-query"
import * as QueryConfig from "./query-config"
@@ -1,4 +1,4 @@
import { MiddlewareRoute } from "../../../loaders/helpers/routing/types"
import { MiddlewareRoute } from "@medusajs/framework"
import { validateAndTransformBody } from "../../utils/validate-body"
import { validateAndTransformQuery } from "../../utils/validate-query"
import * as QueryConfig from "./query-config"
@@ -1,4 +1,4 @@
import { MiddlewareRoute } from "../../../loaders/helpers/routing/types"
import { MiddlewareRoute } from "@medusajs/framework"
import { maybeApplyLinkFilter } from "../../utils/maybe-apply-link-filter"
import { validateAndTransformBody } from "../../utils/validate-body"
import { validateAndTransformQuery } from "../../utils/validate-query"
@@ -1,4 +1,4 @@
import { MiddlewareRoute } from "../../../loaders/helpers/routing/types"
import { MiddlewareRoute } from "@medusajs/framework"
import {
AdminCreateShippingOption,
AdminCreateShippingOptionRule,
@@ -1,4 +1,4 @@
import { MiddlewareRoute } from "../../../loaders/helpers/routing/types"
import { MiddlewareRoute } from "@medusajs/framework"
import { validateAndTransformBody } from "../../utils/validate-body"
import { validateAndTransformQuery } from "../../utils/validate-query"
import {
@@ -1,4 +1,4 @@
import { MiddlewareRoute } from "../../../types/middlewares"
import { MiddlewareRoute } from "@medusajs/framework"
import { maybeApplyLinkFilter } from "../../utils/maybe-apply-link-filter"
import { validateAndTransformBody } from "../../utils/validate-body"
import { validateAndTransformQuery } from "../../utils/validate-query"
@@ -1,4 +1,4 @@
import { MiddlewareRoute } from "../../../loaders/helpers/routing/types"
import { MiddlewareRoute } from "@medusajs/framework"
import { validateAndTransformBody } from "../../utils/validate-body"
import { validateAndTransformQuery } from "../../utils/validate-query"
import * as QueryConfig from "./query-config"
@@ -9,7 +9,7 @@ import {
AdminUpdateTaxRate,
} from "./validators"
import { MiddlewareRoute } from "../../../loaders/helpers/routing/types"
import { MiddlewareRoute } from "@medusajs/framework"
import { validateAndTransformBody } from "../../utils/validate-body"
export const adminTaxRateRoutesMiddlewares: MiddlewareRoute[] = [
@@ -6,7 +6,7 @@ import {
AdminGetTaxRegionsParams,
} from "./validators"
import { MiddlewareRoute } from "../../../loaders/helpers/routing/types"
import { MiddlewareRoute } from "@medusajs/framework"
import { validateAndTransformBody } from "../../utils/validate-body"
import { validateAndTransformQuery } from "../../utils/validate-query"
@@ -1,5 +1,5 @@
import multer from "multer"
import { MiddlewareRoute } from "../../../loaders/helpers/routing/types"
import { MiddlewareRoute } from "@medusajs/framework"
import { validateAndTransformQuery } from "../../utils/validate-query"
import { retrieveUploadConfig } from "./query-config"
import { AdminGetUploadParams } from "./validators"
@@ -7,7 +7,7 @@ import {
AdminUpdateUser,
} from "./validators"
import { MiddlewareRoute } from "../../../types/middlewares"
import { MiddlewareRoute } from "@medusajs/framework"
import { authenticate } from "../../../utils/middlewares/authenticate-middleware"
import { validateAndTransformQuery } from "../../utils/validate-query"
import { validateAndTransformBody } from "../../utils/validate-body"
@@ -7,7 +7,7 @@ import {
AdminGetWorkflowExecutionsParams,
} from "./validators"
import { MiddlewareRoute } from "../../../loaders/helpers/routing/types"
import { MiddlewareRoute } from "@medusajs/framework"
import { validateAndTransformQuery } from "../../utils/validate-query"
import { validateAndTransformBody } from "../../utils/validate-body"
+1 -1
View File
@@ -1,4 +1,4 @@
import { MiddlewareRoute } from "../../types/middlewares"
import { MiddlewareRoute } from "@medusajs/framework"
import { authenticate } from "../../utils/middlewares/authenticate-middleware"
export const authRoutesMiddlewares: MiddlewareRoute[] = [
+1 -1
View File
@@ -1,4 +1,4 @@
import { MiddlewareRoute } from "../../types/middlewares"
import { MiddlewareRoute } from "@medusajs/framework"
export const hooksRoutesMiddlewares: MiddlewareRoute[] = [
{
@@ -1,4 +1,4 @@
import { MiddlewareRoute } from "../../../loaders/helpers/routing/types"
import { MiddlewareRoute } from "@medusajs/framework"
import { ensurePublishableKeyAndSalesChannelMatch } from "../../utils/middlewares/common/ensure-pub-key-sales-channel-match"
import { maybeAttachPublishableKeyScopes } from "../../utils/middlewares/common/maybe-attach-pub-key-scopes"
import { validateAndTransformBody } from "../../utils/validate-body"
@@ -1,5 +1,5 @@
import * as QueryConfig from "./query-config"
import { MiddlewareRoute } from "../../../loaders/helpers/routing/types"
import { MiddlewareRoute } from "@medusajs/framework"
import { validateAndTransformQuery } from "../../utils/validate-query"
import {
StoreGetCollectionParams,
@@ -1,4 +1,4 @@
import { MiddlewareRoute } from "../../../loaders/helpers/routing/types"
import { MiddlewareRoute } from "@medusajs/framework"
import { validateAndTransformQuery } from "../../utils/validate-query"
import * as QueryConfig from "./query-config"
import { StoreGetCurrenciesParams, StoreGetCurrencyParams } from "./validators"
@@ -10,7 +10,7 @@ import {
StoreGetCustomerAddressParams,
} from "./validators"
import { MiddlewareRoute } from "../../../loaders/helpers/routing/types"
import { MiddlewareRoute } from "@medusajs/framework"
import { authenticate } from "../../../utils/middlewares/authenticate-middleware"
import { validateAndTransformBody } from "../../utils/validate-body"
import { validateAndTransformQuery } from "../../utils/validate-query"
@@ -1,4 +1,4 @@
import { MiddlewareRoute } from "../../../loaders/helpers/routing/types"
import { MiddlewareRoute } from "@medusajs/framework"
import { authenticate } from "../../../utils/middlewares/authenticate-middleware"
import { validateAndTransformQuery } from "../../utils/validate-query"
import * as QueryConfig from "./query-config"
@@ -1,11 +1,11 @@
import { MiddlewareRoute } from "../../../types/middlewares"
import { MiddlewareRoute } from "@medusajs/framework"
import { validateAndTransformBody } from "../../utils/validate-body"
import { validateAndTransformQuery } from "../../utils/validate-query"
import * as queryConfig from "./query-config"
import {
StoreGetPaymentCollectionParams,
StoreCreatePaymentCollection,
StoreCreatePaymentSession,
StoreGetPaymentCollectionParams,
} from "./validators"
export const storePaymentCollectionsMiddlewares: MiddlewareRoute[] = [
@@ -1,4 +1,4 @@
import { MiddlewareRoute } from "../../../types/middlewares"
import { MiddlewareRoute } from "@medusajs/framework"
import { validateAndTransformQuery } from "../../utils/validate-query"
import * as queryConfig from "./query-config"
import { StoreGetPaymentProvidersParams } from "./validators"
@@ -1,4 +1,4 @@
import { MiddlewareRoute } from "../../../loaders/helpers/routing/types"
import { MiddlewareRoute } from "@medusajs/framework"
import { validateAndTransformQuery } from "../../utils/validate-query"
import { applyCategoryFilters } from "./helpers"
import * as QueryConfig from "./query-config"
@@ -1,5 +1,5 @@
import { isPresent, ProductStatus } from "@medusajs/utils"
import { MiddlewareRoute } from "../../../loaders/helpers/routing/types"
import { MiddlewareRoute } from "@medusajs/framework"
import { maybeApplyLinkFilter } from "../../utils/maybe-apply-link-filter"
import {
applyDefaultFilters,
@@ -1,4 +1,4 @@
import { MiddlewareRoute } from "../../../loaders/helpers/routing/types"
import { MiddlewareRoute } from "@medusajs/framework"
import { validateAndTransformQuery } from "../../utils/validate-query"
import * as QueryConfig from "./query-config"
import { StoreGetRegionParams, StoreGetRegionsParams } from "./validators"
@@ -1,4 +1,4 @@
import { MiddlewareRoute } from "../../../loaders/helpers/routing/types"
import { MiddlewareRoute } from "@medusajs/framework"
import { validateAndTransformQuery } from "../../utils/validate-query"
import * as QueryConfig from "./query-config"
import { StoreReturnReasonParams } from "./validators"
@@ -1,4 +1,4 @@
import { MiddlewareRoute } from "../../../loaders/helpers/routing/types"
import { MiddlewareRoute } from "@medusajs/framework"
import { validateAndTransformBody } from "../../utils/validate-body"
import { validateAndTransformQuery } from "../../utils/validate-query"
import * as QueryConfig from "./query-config"
@@ -1,4 +1,4 @@
import { MiddlewareRoute } from "../../../loaders/helpers/routing/types"
import { MiddlewareRoute } from "@medusajs/framework"
import { validateAndTransformQuery } from "../../utils/validate-query"
import { listTransformQueryConfig } from "./query-config"
import { StoreGetShippingOptions } from "./validators"
+1 -1
View File
@@ -1,6 +1,6 @@
import { NextFunction } from "express"
import { MedusaRequest, MedusaResponse } from "../../types/routing"
import { MiddlewareFunction } from "../../types/middlewares"
import { MiddlewareFunction } from "@medusajs/framework"
// Due to how our route loader works, where we load all middlewares before routes, ambiguous routes end up having all middlewares on different routes executed before the route handler is.
// This function allows us to skip middlewares for particular routes, so we can temporarily solve this without completely breaking the route loader for everyone.
+13 -20
View File
@@ -1,8 +1,7 @@
import { ContainerRegistrationKeys } from "@medusajs/utils"
import { Express } from "express"
import path from "path"
import { join } from "path"
import qs from "qs"
import { RoutesLoader } from "./helpers/routing"
import { RoutesLoader } from "@medusajs/framework"
import { MedusaContainer, PluginDetails } from "@medusajs/types"
type Options = {
@@ -24,9 +23,7 @@ export default async ({ app, container, plugins }: Options) => {
next()
})
const configModule = container.resolve(
ContainerRegistrationKeys.CONFIG_MODULE
)
const sourcePaths: string[] = []
/**
* Always load plugin routes before the Medusa core routes, since it
@@ -40,31 +37,27 @@ export default async ({ app, container, plugins }: Options) => {
* route will never be resolved, because it will be handled by the
* "/products/:id" route.
*/
await Promise.all(
plugins.map(async (pluginDetails) => {
return new RoutesLoader({
app: app,
configModule,
rootDir: path.join(pluginDetails.resolve, "api"),
}).load()
})
sourcePaths.push(
...plugins.map((pluginDetails) => {
return join(pluginDetails.resolve, "api")
}),
/**
* Register the Medusa CORE API routes using the file based routing.
*/
join(__dirname, "../api")
)
// TODO: Figure out why this is causing issues with test when placed inside ./api.ts
// Adding this here temporarily
// Test: (packages/medusa/src/api/routes/admin/currencies/update-currency.ts)
try {
/**
* Register the Medusa CORE API routes using the file based routing.
*/
await new RoutesLoader({
app: app,
rootDir: path.join(__dirname, "../api"),
configModule,
sourceDir: sourcePaths,
}).load()
} catch (err) {
throw Error(
"An error occurred while registering Medusa Core API Routes. See error in logs for more details.",
"An error occurred while registering API Routes. See error in logs for more details.",
{ cause: err }
)
}
@@ -1,4 +1,4 @@
import { FlagSettings } from "../../types/feature-flags"
import { FlagSettings } from "@medusajs/framework"
const AnalyticsFeatureFlag: FlagSettings = {
key: "analytics",
@@ -1,4 +1,4 @@
import { FlagSettings } from "../../types/feature-flags"
import { FlagSettings } from "@medusajs/framework"
const OrderEditingFeatureFlag: FlagSettings = {
key: "order_editing",
@@ -1,4 +1,4 @@
import { FlagSettings } from "../../types/feature-flags"
import { FlagSettings } from "@medusajs/framework"
const ProductCategoryFeatureFlag: FlagSettings = {
key: "product_categories",
@@ -1,4 +1,4 @@
import { FlagSettings } from "../../types/feature-flags"
import { FlagSettings } from "@medusajs/framework"
const PublishableAPIKeysFeatureFlag: FlagSettings = {
key: "publishable_api_keys",
@@ -1,4 +1,4 @@
import { FlagSettings } from "../../types/feature-flags"
import { FlagSettings } from "@medusajs/framework"
const SalesChannelFeatureFlag: FlagSettings = {
key: "sales_channels",
@@ -1,4 +1,4 @@
import { FlagSettings } from "../../types/feature-flags"
import { FlagSettings } from "@medusajs/framework"
const TaxInclusivePricingFeatureFlag: FlagSettings = {
key: "tax_inclusive_pricing",
@@ -1,87 +0,0 @@
import {
MedusaNextFunction,
MedusaRequest,
MedusaRequestHandler,
MedusaResponse,
} from "../../../types/routing"
/**
* List of all the supported HTTP methods
*/
export const HTTP_METHODS = [
"GET",
"POST",
"PUT",
"PATCH",
"DELETE",
"OPTIONS",
"HEAD",
] as const
export type RouteVerb = (typeof HTTP_METHODS)[number]
export type MiddlewareVerb = "USE" | "ALL" | RouteVerb
type SyncRouteHandler = (req: MedusaRequest, res: MedusaResponse) => void
export type AsyncRouteHandler = (
req: MedusaRequest,
res: MedusaResponse
) => Promise<void>
type RouteHandler = SyncRouteHandler | AsyncRouteHandler
export type RouteImplementation = {
method?: RouteVerb
handler: RouteHandler
}
export type RouteConfig = {
optedOutOfAuth?: boolean
routeType?: "admin" | "store" | "auth"
shouldAppendAdminCors?: boolean
shouldAppendStoreCors?: boolean
shouldAppendAuthCors?: boolean
routes?: RouteImplementation[]
}
export type MiddlewareFunction =
| MedusaRequestHandler
| ((...args: any[]) => any)
export type MedusaErrorHandlerFunction = (
error: any,
req: MedusaRequest,
res: MedusaResponse,
next: MedusaNextFunction
) => Promise<void> | void
export type ParserConfigArgs = {
sizeLimit?: string | number | undefined
preserveRawBody?: boolean
}
export type ParserConfig = false | ParserConfigArgs
export type MiddlewareRoute = {
method?: MiddlewareVerb | MiddlewareVerb[]
matcher: string | RegExp
bodyParser?: ParserConfig
middlewares?: MiddlewareFunction[]
}
export type MiddlewaresConfig = {
errorHandler?: false | MedusaErrorHandlerFunction
routes?: MiddlewareRoute[]
}
export type RouteDescriptor = {
absolutePath: string
relativePath: string
route: string
priority: number
config?: RouteConfig
}
export type GlobalMiddlewareDescriptor = {
config?: MiddlewaresConfig
}
@@ -1,32 +0,0 @@
export interface IFlagRouter {
isFeatureEnabled: (key: string) => boolean
listFlags: () => FeatureFlagsResponse
}
/**
* @schema FeatureFlagsResponse
* type: array
* items:
* type: object
* required:
* - key
* - value
* properties:
* key:
* description: The key of the feature flag.
* type: string
* value:
* description: The value of the feature flag.
* type: boolean
*/
export type FeatureFlagsResponse = {
key: string
value: boolean | Record<string, boolean>
}[]
export type FlagSettings = {
key: string
description: string
env_key: string
default_val: boolean
}
+1 -1
View File
@@ -2,4 +2,4 @@ export type {
MiddlewareFunction,
MiddlewareRoute,
MiddlewaresConfig,
} from "../loaders/helpers/routing/types"
} from "@medusajs/framework"
+1
View File
@@ -9,6 +9,7 @@ import {
import * as core from "express-serve-static-core"
import { FindConfig } from "./common"
// TODO this will be reqorked and move to the framework at a later point unless decided otherwise
export interface MedusaRequest<Body = unknown>
extends Request<core.ParamsDictionary, any, Body> {
validatedBody: Body
@@ -1,67 +1,8 @@
import { ZodObject } from "zod"
import {
MedusaRequest,
MedusaResponse,
MedusaNextFunction,
MedusaRequestHandler,
} from "../types/routing"
import {
ParserConfig,
MiddlewareVerb,
MiddlewaresConfig,
} from "../loaders/helpers/routing/types"
import { defineMiddlewares as originalDefineMiddlewares } from "@medusajs/framework"
/**
* A helper function to configure the routes by defining custom middleware,
* bodyparser config and validators to be merged with the pre-existing
* route validators.
*/
export function defineMiddlewares<
Route extends {
method?: MiddlewareVerb | MiddlewareVerb[]
matcher: string | RegExp
bodyParser?: ParserConfig
extendedValidators?: {
body?: ZodObject<any, any>
queryParams?: ZodObject<any, any>
}
// eslint-disable-next-line space-before-function-paren
middlewares?: (<Req extends MedusaRequest>(
req: Req,
res: MedusaResponse,
next: MedusaNextFunction
) => any)[]
}
>(
config:
| Route[]
| { routes?: Route[]; errorHandler?: MiddlewaresConfig["errorHandler"] }
): MiddlewaresConfig {
const routes = Array.isArray(config) ? config : config.routes || []
const errorHandler = Array.isArray(config) ? undefined : config.errorHandler
return {
errorHandler,
routes: routes.map((route) => {
const { middlewares, extendedValidators, ...rest } = route
const customMiddleware: MedusaRequestHandler[] = []
/**
* Define a custom validator when "extendedValidators.body" or
* "extendedValidators.queryParams" validation schema is
* provided.
*/
if (extendedValidators?.body || extendedValidators?.queryParams) {
customMiddleware.push((req, _, next) => {
req.extendedValidators = extendedValidators
next()
})
}
return {
...rest,
middlewares: customMiddleware.concat(middlewares || []),
}
}),
}
}
export const defineMiddlewares = originalDefineMiddlewares
@@ -1,56 +1,3 @@
import { MedusaError } from "@medusajs/utils"
import { formatException as originalFormatException } from "@medusajs/framework"
export enum PostgresError {
DUPLICATE_ERROR = "23505",
FOREIGN_KEY_ERROR = "23503",
SERIALIZATION_FAILURE = "40001",
NULL_VIOLATION = "23502",
}
export const formatException = (err): MedusaError => {
switch (err.code) {
case PostgresError.DUPLICATE_ERROR:
return new MedusaError(
MedusaError.Types.DUPLICATE_ERROR,
`${err.table.charAt(0).toUpperCase()}${err.table.slice(
1
)} with ${err.detail.slice(4).replace(/[()=]/g, (s) => {
return s === "=" ? " " : ""
})}`
)
case PostgresError.FOREIGN_KEY_ERROR: {
const matches =
/Key \(([\w-\d]+)\)=\(([\w-\d]+)\) is not present in table "(\w+)"/g.exec(
err.detail
)
if (matches?.length !== 4) {
return new MedusaError(
MedusaError.Types.NOT_FOUND,
JSON.stringify(matches)
)
}
return new MedusaError(
MedusaError.Types.NOT_FOUND,
`${matches[3]?.charAt(0).toUpperCase()}${matches[3]?.slice(1)} with ${
matches[1]
} ${matches[2]} does not exist.`
)
}
case PostgresError.SERIALIZATION_FAILURE: {
return new MedusaError(
MedusaError.Types.CONFLICT,
err?.detail ?? err?.message
)
}
case PostgresError.NULL_VIOLATION: {
return new MedusaError(
MedusaError.Types.INVALID_DATA,
`Can't insert null value in field ${err?.column} on insert in table ${err?.table}`
)
}
default:
return err
}
}
export const formatException = originalFormatException
@@ -1,203 +1,3 @@
import { ApiKeyDTO, ConfigModule, IApiKeyModuleService } from "@medusajs/types"
import {
ContainerRegistrationKeys,
ModuleRegistrationName,
} from "@medusajs/utils"
import { NextFunction, RequestHandler } from "express"
import jwt, { JwtPayload } from "jsonwebtoken"
import {
AuthContext,
AuthenticatedMedusaRequest,
MedusaRequest,
MedusaResponse,
} from "../../types/routing"
import { authenticate as originalAuthenticate } from "@medusajs/framework"
const SESSION_AUTH = "session"
const BEARER_AUTH = "bearer"
const API_KEY_AUTH = "api-key"
// This is the only hard-coded actor type, as API keys have special handling for now. We could also generalize API keys to carry the actor type with them.
const ADMIN_ACTOR_TYPE = "user"
type AuthType = typeof SESSION_AUTH | typeof BEARER_AUTH | typeof API_KEY_AUTH
type MedusaSession = {
auth_context: AuthContext
}
export const authenticate = (
actorType: string | string[],
authType: AuthType | AuthType[],
options: { allowUnauthenticated?: boolean; allowUnregistered?: boolean } = {}
): RequestHandler => {
return async (
req: MedusaRequest,
res: MedusaResponse,
next: NextFunction
): Promise<void> => {
const authTypes = Array.isArray(authType) ? authType : [authType]
const actorTypes = Array.isArray(actorType) ? actorType : [actorType]
const req_ = req as AuthenticatedMedusaRequest
// We only allow authenticating using a secret API key on the admin
const isExclusivelyUser =
actorTypes.length === 1 && actorTypes[0] === ADMIN_ACTOR_TYPE
if (authTypes.includes(API_KEY_AUTH) && isExclusivelyUser) {
const apiKey = await getApiKeyInfo(req)
if (apiKey) {
req_.auth_context = {
actor_id: apiKey.id,
actor_type: "api-key",
auth_identity_id: "",
app_metadata: {},
}
return next()
}
}
// We try to extract the auth context either from the session or from a JWT token
let authContext: AuthContext | null = getAuthContextFromSession(
req.session,
authTypes,
actorTypes
)
if (!authContext) {
const { http } = req.scope.resolve<ConfigModule>(
ContainerRegistrationKeys.CONFIG_MODULE
).projectConfig
authContext = getAuthContextFromJwtToken(
req.headers.authorization,
http.jwtSecret!,
authTypes,
actorTypes
)
}
// If the entity is authenticated, and it is a registered actor we can continue
if (authContext?.actor_id) {
req_.auth_context = authContext
return next()
}
// If the entity is authenticated, but there is no registered actor yet, we can continue (eg. in the case of a user invite) if allow unregistered is set
if (authContext?.auth_identity_id && options.allowUnregistered) {
req_.auth_context = authContext
return next()
}
// If we allow unauthenticated requests (i.e public endpoints), just continue
if (options.allowUnauthenticated) {
return next()
}
res.status(401).json({ message: "Unauthorized" })
}
}
const getApiKeyInfo = async (req: MedusaRequest): Promise<ApiKeyDTO | null> => {
const authHeader = req.headers.authorization
if (!authHeader) {
return null
}
const [tokenType, token] = authHeader.split(" ")
if (tokenType.toLowerCase() !== "basic" || !token) {
return null
}
// The token could have been base64 encoded, we want to decode it first.
let normalizedToken = token
if (!token.startsWith("sk_")) {
normalizedToken = Buffer.from(token, "base64").toString("utf-8")
}
// Basic auth is defined as a username:password set, and since the token is set to the username we need to trim the colon
if (normalizedToken.endsWith(":")) {
normalizedToken = normalizedToken.slice(0, -1)
}
// Secret tokens start with 'sk_', and if it doesn't it could be a user JWT or a malformed token
if (!normalizedToken.startsWith("sk_")) {
return null
}
const apiKeyModule = req.scope.resolve(
ModuleRegistrationName.API_KEY
) as IApiKeyModuleService
try {
const apiKey = await apiKeyModule.authenticate(normalizedToken)
if (!apiKey) {
return null
}
return apiKey
} catch (error) {
console.error(error)
return null
}
}
const getAuthContextFromSession = (
session: Partial<MedusaSession> = {},
authTypes: AuthType[],
actorTypes: string[]
): AuthContext | null => {
if (!authTypes.includes(SESSION_AUTH)) {
return null
}
if (
session.auth_context &&
(actorTypes.includes("*") ||
actorTypes.includes(session.auth_context.actor_type))
) {
return session.auth_context
}
return null
}
const getAuthContextFromJwtToken = (
authHeader: string | undefined,
jwtSecret: string,
authTypes: AuthType[],
actorTypes: string[]
): AuthContext | null => {
if (!authTypes.includes(BEARER_AUTH)) {
return null
}
if (!authHeader) {
return null
}
const re = /(\S+)\s+(\S+)/
const matches = authHeader.match(re)
// TODO: figure out how to obtain token (and store correct data in token)
if (matches) {
const tokenType = matches[1]
const token = matches[2]
if (tokenType.toLowerCase() === BEARER_AUTH) {
// get config jwt secret
// verify token and set authUser
try {
const verified = jwt.verify(token, jwtSecret) as JwtPayload
if (
actorTypes.includes("*") ||
actorTypes.includes(verified.actor_type)
) {
return verified as AuthContext
}
} catch (err) {
return null
}
}
}
return null
}
export const authenticate = originalAuthenticate
@@ -1,99 +1,3 @@
import { NextFunction, Request, Response } from "express"
import { errorHandler as originalErrorHandler } from "@medusajs/framework"
import { MedusaError } from "@medusajs/utils"
import { Logger } from "../../types/global"
import { formatException } from "../../utils"
const QUERY_RUNNER_RELEASED = "QueryRunnerAlreadyReleasedError"
const TRANSACTION_STARTED = "TransactionAlreadyStartedError"
const TRANSACTION_NOT_STARTED = "TransactionNotStartedError"
const API_ERROR = "api_error"
const INVALID_REQUEST_ERROR = "invalid_request_error"
const INVALID_STATE_ERROR = "invalid_state_error"
export default () => {
return (
err: MedusaError,
req: Request,
res: Response,
next: NextFunction
) => {
const logger: Logger = req.scope.resolve("logger")
err = formatException(err)
logger.error(err)
const errorType = err.type || err.name
const errObj = {
code: err.code,
type: err.type,
message: err.message,
}
let statusCode = 500
switch (errorType) {
case QUERY_RUNNER_RELEASED:
case TRANSACTION_STARTED:
case TRANSACTION_NOT_STARTED:
case MedusaError.Types.CONFLICT:
statusCode = 409
errObj.code = INVALID_STATE_ERROR
errObj.message =
"The request conflicted with another request. You may retry the request with the provided Idempotency-Key."
break
case MedusaError.Types.UNAUTHORIZED:
statusCode = 401
break
case MedusaError.Types.PAYMENT_AUTHORIZATION_ERROR:
statusCode = 422
break
case MedusaError.Types.DUPLICATE_ERROR:
statusCode = 422
errObj.code = INVALID_REQUEST_ERROR
break
case MedusaError.Types.NOT_ALLOWED:
case MedusaError.Types.INVALID_DATA:
statusCode = 400
break
case MedusaError.Types.NOT_FOUND:
statusCode = 404
break
case MedusaError.Types.DB_ERROR:
statusCode = 500
errObj.code = API_ERROR
break
case MedusaError.Types.UNEXPECTED_STATE:
case MedusaError.Types.INVALID_ARGUMENT:
break
default:
errObj.code = "unknown_error"
errObj.message = "An unknown error occurred."
errObj.type = "unknown_error"
break
}
res.status(statusCode).json(errObj)
}
}
/**
* @schema Error
* title: "Response Error"
* type: object
* properties:
* code:
* type: string
* description: A slug code to indicate the type of the error.
* enum: [invalid_state_error, invalid_request_error, api_error, unknown_error]
* message:
* type: string
* description: Description of the error that occurred.
* example: "first_name must be a string"
* type:
* type: string
* description: A slug indicating the type of the error.
* enum: [QueryRunnerAlreadyReleasedError, TransactionAlreadyStartedError, TransactionNotStartedError, conflict, unauthorized, payment_authorization_error, duplicate_error, not_allowed, invalid_data, not_found, database_error, unexpected_state, invalid_argument, unknown_error]
*/
export const errorHandler = originalErrorHandler

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