feat(medusa): Improve config loading (#1290)

This commit is contained in:
Adrien de Peretti authored and GitHub committed 2022-04-21 12:49:56 +02:00
1 parent 89a6de4660
commit 313cb0658b
25 files changed
+214 -164

No files matched your search

@@ -28,7 +28,7 @@ export const simpleProductFactory = async (
connection: Connection, connection: Connection,
data: ProductFactoryData = {}, data: ProductFactoryData = {},
seed?: number seed?: number
): Promise<Product> => { ): Promise<Product | undefined> => {
if (typeof seed !== "undefined") { if (typeof seed !== "undefined") {
faker.seed(seed) faker.seed(seed)
} }
@@ -50,9 +50,9 @@ export const simpleProductFactory = async (
status: data.status, status: data.status,
is_giftcard: data.is_giftcard || false, is_giftcard: data.is_giftcard || false,
discountable: !data.is_giftcard, discountable: !data.is_giftcard,
tags: [], tags: [] as ProductTag[],
profile_id: data.is_giftcard ? gcProfile.id : defaultProfile.id, profile_id: data.is_giftcard ? gcProfile?.id : defaultProfile?.id,
} } as Product
if (typeof data.tags !== "undefined") { if (typeof data.tags !== "undefined") {
for (let i = 0; i < data.tags.length; i++) { for (let i = 0; i < data.tags.length; i++) {
@@ -112,5 +112,5 @@ export const simpleProductFactory = async (
await simpleProductVariantFactory(connection, factoryData) await simpleProductVariantFactory(connection, factoryData)
} }
return await manager.findOne(Product, { id: prodId }, { relations: ["tags"] }) return manager.findOne(Product, { id: prodId }, { relations: ["tags"] })
} }
+2
View File
@@ -8,5 +8,7 @@ module.exports = {
// redis_url: REDIS_URL, // redis_url: REDIS_URL,
database_url: `postgres://${DB_USERNAME}:${DB_PASSWORD}@localhost/medusa-integration-${workerId}`, database_url: `postgres://${DB_USERNAME}:${DB_PASSWORD}@localhost/medusa-integration-${workerId}`,
database_type: "postgres", database_type: "postgres",
jwt_secret: 'test',
cookie_secret: 'test'
}, },
} }
+1
View File
@@ -6,6 +6,7 @@ const pkgs = glob
module.exports = { module.exports = {
testEnvironment: `node`, testEnvironment: `node`,
testTimeout: 10000,
globalSetup: "<rootDir>/integration-tests/globalSetup.js", globalSetup: "<rootDir>/integration-tests/globalSetup.js",
globalTeardown: "<rootDir>/integration-tests/globalTeardown.js", globalTeardown: "<rootDir>/integration-tests/globalTeardown.js",
rootDir: `../`, rootDir: `../`,
@@ -24,5 +24,7 @@ module.exports = {
// redis_url: REDIS_URL, // redis_url: REDIS_URL,
database_url: `postgres://${DB_USERNAME}:${DB_PASSWORD}@localhost/medusa-integration-${workerId}`, database_url: `postgres://${DB_USERNAME}:${DB_PASSWORD}@localhost/medusa-integration-${workerId}`,
database_type: "postgres", database_type: "postgres",
jwt_secret: 'test',
cookie_secret: 'test'
}, },
} }
-1
View File
@@ -62,7 +62,6 @@
"core-js": "^3.6.5", "core-js": "^3.6.5",
"cors": "^2.8.5", "cors": "^2.8.5",
"cross-spawn": "^7.0.3", "cross-spawn": "^7.0.3",
"dotenv": "^8.2.0",
"express": "^4.17.1", "express": "^4.17.1",
"express-session": "^1.17.1", "express-session": "^1.17.1",
"fs-exists-cached": "^1.0.0", "fs-exists-cached": "^1.0.0",
@@ -1,6 +1,5 @@
import _ from "lodash" import _ from "lodash"
import jwt from "jsonwebtoken" import jwt from "jsonwebtoken"
import config from "../../../../config"
import { validator } from "../../../../utils/validator" import { validator } from "../../../../utils/validator"
import { IsEmail, IsNotEmpty, IsString } from "class-validator" import { IsEmail, IsNotEmpty, IsString } from "class-validator"
import AuthService from "../../../../services/auth" import AuthService from "../../../../services/auth"
@@ -28,10 +27,11 @@ import { MedusaError } from "medusa-core-utils"
* $ref: "#/components/schemas/user" * $ref: "#/components/schemas/user"
*/ */
export default async (req, res) => { export default async (req, res) => {
if (!config.jwtSecret) { const { projectConfig: { jwt_secret } } = req.scope.resolve('configModule')
if (!jwt_secret) {
throw new MedusaError( throw new MedusaError(
MedusaError.Types.NOT_FOUND, MedusaError.Types.NOT_FOUND,
"Please configure jwtSecret in your environment" "Please configure jwt_secret in your environment"
) )
} }
const validated = await validator(AdminPostAuthReq, req.body) const validated = await validator(AdminPostAuthReq, req.body)
@@ -44,7 +44,7 @@ export default async (req, res) => {
if (result.success && result.user) { if (result.success && result.user) {
// Add JWT to cookie // Add JWT to cookie
req.session.jwt = jwt.sign({ userId: result.user.id }, config.jwtSecret, { req.session.jwt = jwt.sign({ userId: result.user.id }, jwt_secret, {
expiresIn: "24h", expiresIn: "24h",
}) })
@@ -1,6 +1,5 @@
import { IsEmail, IsNotEmpty } from "class-validator" import { IsEmail, IsNotEmpty } from "class-validator"
import jwt from "jsonwebtoken" import jwt from "jsonwebtoken"
import config from "../../../../config"
import AuthService from "../../../../services/auth" import AuthService from "../../../../services/auth"
import CustomerService from "../../../../services/customer" import CustomerService from "../../../../services/customer"
import { validator } from "../../../../utils/validator" import { validator } from "../../../../utils/validator"
@@ -29,8 +28,6 @@ export default async (req, res) => {
const validated = await validator(StorePostAuthReq, req.body) const validated = await validator(StorePostAuthReq, req.body)
const authService: AuthService = req.scope.resolve("authService") const authService: AuthService = req.scope.resolve("authService")
const customerService: CustomerService = req.scope.resolve("customerService")
const result = await authService.authenticateCustomer( const result = await authService.authenticateCustomer(
validated.email, validated.email,
validated.password validated.password
@@ -41,14 +38,14 @@ export default async (req, res) => {
} }
// Add JWT to cookie // Add JWT to cookie
req.session.jwt = jwt.sign( const {
{ customer_id: result.customer?.id }, projectConfig: { jwt_secret },
config.jwtSecret as string, } = req.scope.resolve("configModule")
{ req.session.jwt = jwt.sign({ customer_id: result.customer?.id }, jwt_secret!, {
expiresIn: "30d", expiresIn: "30d",
} })
)
const customerService: CustomerService = req.scope.resolve("customerService")
const customer = await customerService.retrieve(result.customer?.id || "", { const customer = await customerService.retrieve(result.customer?.id || "", {
relations: ["orders", "orders.items"], relations: ["orders", "orders.items"],
}) })
@@ -2,7 +2,6 @@ import { IsEmail, IsOptional, IsString } from "class-validator"
import jwt from "jsonwebtoken" import jwt from "jsonwebtoken"
import { defaultStoreCustomersFields, defaultStoreCustomersRelations } from "." import { defaultStoreCustomersFields, defaultStoreCustomersRelations } from "."
import { Customer } from "../../../.." import { Customer } from "../../../.."
import config from "../../../../config"
import CustomerService from "../../../../services/customer" import CustomerService from "../../../../services/customer"
import { validator } from "../../../../utils/validator" import { validator } from "../../../../utils/validator"
@@ -36,7 +35,10 @@ export default async (req, res) => {
let customer: Customer = await customerService.create(validated) let customer: Customer = await customerService.create(validated)
// Add JWT to cookie // Add JWT to cookie
req.session.jwt = jwt.sign({ customer_id: customer.id }, config.jwtSecret!, { const {
projectConfig: { jwt_secret },
} = req.scope.resolve("configModule")
req.session.jwt = jwt.sign({ customer_id: customer.id }, jwt_secret!, {
expiresIn: "30d", expiresIn: "30d",
}) })
-44
View File
@@ -1,44 +0,0 @@
import dotenv from "dotenv"
// Set the NODE_ENV to 'development' by default
process.env.NODE_ENV = process.env.NODE_ENV || "development"
const envFound = dotenv.config()
if (!envFound) {
// This error should crash whole process
throw new Error("⚠️ Couldn't find .env file ⚠️")
}
const config = {
/**
* Your favorite port
*/
port: parseInt(process.env.PORT, 10),
databaseURL: process.env.MONGODB_URI,
redisURI: process.env.REDIS_URI,
/**
* Your secret sauce
*/
jwtSecret: process.env.NODE_ENV === "test" ? "test" : process.env.JWT_SECRET,
cookieSecret:
process.env.NODE_ENV === "test" ? "test" : process.env.COOKIE_SECRET,
/**
* Used by winston logger
*/
logs: {
level: process.env.LOG_LEVEL || "silly",
},
/**
* API configs
*/
api: {
prefix: "/api",
},
}
export default config
+16 -7
View File
@@ -4,7 +4,6 @@ import jwt from "jsonwebtoken"
import { MockManager } from "medusa-test-utils" import { MockManager } from "medusa-test-utils"
import "reflect-metadata" import "reflect-metadata"
import supertest from "supertest" import supertest from "supertest"
import config from "../config"
import apiLoader from "../loaders/api" import apiLoader from "../loaders/api"
import passportLoader from "../loaders/passport" import passportLoader from "../loaders/passport"
import servicesLoader from "../loaders/services" import servicesLoader from "../loaders/services"
@@ -22,9 +21,19 @@ const clientSessionOpts = {
secret: "test", secret: "test",
} }
const config = {
projectConfig: {
jwt_secret: 'supersecret',
cookie_secret: 'superSecret',
admin_cors: '',
store_cors: ''
}
}
const testApp = express() const testApp = express()
const container = createContainer() const container = createContainer()
container.register('configModule', asValue(config))
container.register({ container.register({
logger: asValue({ logger: asValue({
error: () => {}, error: () => {},
@@ -45,16 +54,16 @@ testApp.use((req, res, next) => {
next() next()
}) })
servicesLoader({ container }) servicesLoader({ container, configModule: config })
strategiesLoader({ container }) strategiesLoader({ container, configModule: config })
passportLoader({ app: testApp, container }) passportLoader({ app: testApp, container, configModule: config })
testApp.use((req, res, next) => { testApp.use((req, res, next) => {
req.scope = container.createScope() req.scope = container.createScope()
next() next()
}) })
apiLoader({ container, rootDirectory: ".", app: testApp }) apiLoader({ container, app: testApp, configModule: config })
const supertestRequest = supertest(testApp) const supertestRequest = supertest(testApp)
@@ -68,7 +77,7 @@ export async function request(method, url, opts = {}) {
if (opts.adminSession.jwt) { if (opts.adminSession.jwt) {
opts.adminSession.jwt = jwt.sign( opts.adminSession.jwt = jwt.sign(
opts.adminSession.jwt, opts.adminSession.jwt,
config.jwtSecret, config.projectConfig.jwt_secret,
{ {
expiresIn: "30m", expiresIn: "30m",
} }
@@ -80,7 +89,7 @@ export async function request(method, url, opts = {}) {
if (opts.clientSession.jwt) { if (opts.clientSession.jwt) {
opts.clientSession.jwt = jwt.sign( opts.clientSession.jwt = jwt.sign(
opts.clientSession.jwt, opts.clientSession.jwt,
config.jwtSecret, config.projectConfig.jwt_secret,
{ {
expiresIn: "30d", expiresIn: "30d",
} }
+5 -8
View File
@@ -1,21 +1,18 @@
import { Express } from 'express' import { Express } from 'express'
import bodyParser from "body-parser" import bodyParser from "body-parser"
import { getConfigFile } from "medusa-core-utils"
import routes from "../api" import routes from "../api"
import { AwilixContainer } from "awilix" import { AwilixContainer } from "awilix"
import { ConfigModule } from "../types/global"
type Options = { type Options = {
app: Express; app: Express
rootDirectory: string;
container: AwilixContainer container: AwilixContainer
configModule: ConfigModule
} }
export default async ({ app, rootDirectory, container }: Options) => { export default async ({ app, container, configModule }: Options) => {
const { configModule } = getConfigFile(rootDirectory, `medusa-config`) as { configModule: Record<string, unknown> }
const config = (configModule && configModule.projectConfig) || {}
app.use(bodyParser.json()) app.use(bodyParser.json())
app.use("/", routes(container, config)) app.use("/", routes(container, configModule.projectConfig))
return app return app
} }
+61
View File
@@ -0,0 +1,61 @@
import { ConfigModule } from "../types/global"
import { getConfigFile } from "medusa-core-utils/dist"
const isProduction = ["production", "prod"].includes(process.env.NODE_ENV || "")
const errorHandler = isProduction
? (msg: string): never => {
throw new Error(msg)
}
: console.log
export default (rootDirectory: string): ConfigModule => {
const { configModule } = getConfigFile(rootDirectory, `medusa-config`) as {
configModule: ConfigModule
}
if (!configModule?.projectConfig?.redis_url) {
console.log(
`[medusa-config] ⚠️ redis_url not found. A fake redis instance will be used.`
)
}
const jwt_secret =
configModule?.projectConfig?.jwt_secret ?? process.env.JWT_SECRET
if (!jwt_secret) {
errorHandler(
`[medusa-config] ⚠️ jwt_secret not found.${
isProduction
? ""
: " fallback to either cookie_secret or default 'supersecret'."
}`
)
}
const cookie_secret =
configModule?.projectConfig?.cookie_secret ?? process.env.COOKIE_SECRET
if (!cookie_secret) {
errorHandler(
`[medusa-config] ⚠️ cookie_secret not found.${
isProduction
? ""
: " fallback to either cookie_secret or default 'supersecret'."
}`
)
}
if (!configModule?.projectConfig?.database_type) {
console.log(
`[medusa-config] ⚠️ database_type not found. fallback to default sqlite.`
)
}
return {
projectConfig: {
jwt_secret: jwt_secret ?? "supersecret",
cookie_secret: cookie_secret ?? "supersecret",
...configModule?.projectConfig,
},
plugins: configModule?.plugins ?? [],
}
}
+2 -9
View File
@@ -2,17 +2,10 @@ import { Connection, createConnection, LoggerOptions } from "typeorm"
import { ShortenedNamingStrategy } from "../utils/naming-strategy" import { ShortenedNamingStrategy } from "../utils/naming-strategy"
import { AwilixContainer } from "awilix" import { AwilixContainer } from "awilix"
import { ConnectionOptions } from "typeorm/connection/ConnectionOptions" import { ConnectionOptions } from "typeorm/connection/ConnectionOptions"
import { ConfigModule } from "../types/global"
export type DatabaseConfig = {
database_type: string;
database_url?: string;
database_database?: string;
database_extra?: Record<string, unknown>;
database_logging: LoggerOptions
}
type Options = { type Options = {
configModule: { projectConfig: DatabaseConfig }; configModule: ConfigModule
container: AwilixContainer container: AwilixContainer
} }
+6 -6
View File
@@ -4,12 +4,11 @@ import cookieParser from "cookie-parser"
import morgan from "morgan" import morgan from "morgan"
import redis, { RedisConfig } from "redis" import redis, { RedisConfig } from "redis"
import createStore from "connect-redis" import createStore from "connect-redis"
import { ConfigModule } from "../types/global"
import config from "../config"
type Options = { type Options = {
app: Express; app: Express;
configModule: { projectConfig: RedisConfig } configModule: ConfigModule
} }
export default async ({ app, configModule }: Options): Promise<Express> => { export default async ({ app, configModule }: Options): Promise<Express> => {
@@ -23,12 +22,13 @@ export default async ({ app, configModule }: Options): Promise<Express> => {
sameSite = "none" sameSite = "none"
} }
const sessionOpts = { const { cookie_secret } = configModule.projectConfig
let sessionOpts = {
resave: true, resave: true,
saveUninitialized: true, saveUninitialized: true,
cookieName: "session", cookieName: "session",
proxy: true, proxy: true,
secret: config.cookieSecret, secret: cookie_secret,
cookie: { cookie: {
sameSite, sameSite,
secure, secure,
@@ -37,7 +37,7 @@ export default async ({ app, configModule }: Options): Promise<Express> => {
store: null store: null
} }
if (configModule.projectConfig.redis_url) { if (configModule?.projectConfig?.redis_url) {
const RedisStore = createStore(session) const RedisStore = createStore(session)
const redisClient = redis.createClient(configModule.projectConfig.redis_url) const redisClient = redis.createClient(configModule.projectConfig.redis_url)
sessionOpts.store = new RedisStore({ client: redisClient }) sessionOpts.store = new RedisStore({ client: redisClient })
+11 -15
View File
@@ -1,13 +1,14 @@
import loadConfig from './config'
import "reflect-metadata" import "reflect-metadata"
import Logger from "./logger" import Logger from "./logger"
import apiLoader from "./api" import apiLoader from "./api"
import databaseLoader, { DatabaseConfig } from "./database" import databaseLoader from "./database"
import defaultsLoader from "./defaults" import defaultsLoader from "./defaults"
import expressLoader from "./express" import expressLoader from "./express"
import modelsLoader from "./models" import modelsLoader from "./models"
import passportLoader from "./passport" import passportLoader from "./passport"
import pluginsLoader, { registerPluginModels } from "./plugins" import pluginsLoader, { registerPluginModels } from "./plugins"
import redisLoader, { RedisConfig } from "./redis" import redisLoader from "./redis"
import repositoriesLoader from "./repositories" import repositoriesLoader from "./repositories"
import requestIp from "request-ip" import requestIp from "request-ip"
import searchIndexLoader from "./search-index" import searchIndexLoader from "./search-index"
@@ -18,7 +19,6 @@ import { ClassOrFunctionReturning } from "awilix/lib/container"
import { Connection, getManager } from "typeorm" import { Connection, getManager } from "typeorm"
import { Express, NextFunction, Request, Response } from "express" import { Express, NextFunction, Request, Response } from "express"
import { asFunction, asValue, AwilixContainer, createContainer, Resolver } from "awilix" import { asFunction, asValue, AwilixContainer, createContainer, Resolver } from "awilix"
import { getConfigFile } from "medusa-core-utils"
import { track } from "medusa-telemetry" import { track } from "medusa-telemetry"
import { MedusaContainer } from "../types/global" import { MedusaContainer } from "../types/global"
@@ -28,14 +28,6 @@ type Options = {
isTest: boolean isTest: boolean
} }
export type ConfigModule = {
projectConfig: DatabaseConfig & RedisConfig;
plugins: {
resolve: string;
options: Record<string, unknown>
}[];
}
export default async ( export default async (
{ {
directory: rootDirectory, directory: rootDirectory,
@@ -43,9 +35,11 @@ export default async (
isTest isTest
}: Options }: Options
): Promise<{ container: MedusaContainer; dbConnection: Connection; app: Express }> => { ): Promise<{ container: MedusaContainer; dbConnection: Connection; app: Express }> => {
const { configModule } = getConfigFile(rootDirectory, `medusa-config`) as { configModule: ConfigModule } const configModule = loadConfig(rootDirectory)
const container = createContainer() as MedusaContainer const container = createContainer() as MedusaContainer
container.register('configModule', asValue(configModule))
container.registerAdd = function (this: MedusaContainer, name: string, registration: typeof asFunction | typeof asValue) { container.registerAdd = function (this: MedusaContainer, name: string, registration: typeof asFunction | typeof asValue) {
const storeKey = name + "_STORE" const storeKey = name + "_STORE"
@@ -73,7 +67,7 @@ export default async (
}) })
container.register({ container.register({
logger: asValue(Logger), logger: asValue(Logger)
}) })
await redisLoader({ container, configModule, logger: Logger }) await redisLoader({ container, configModule, logger: Logger })
@@ -89,6 +83,7 @@ export default async (
await registerPluginModels({ await registerPluginModels({
rootDirectory, rootDirectory,
container, container,
configModule
}) })
const pmAct = Logger.success(pmActivity, "Plugin models initialized") || {} const pmAct = Logger.success(pmActivity, "Plugin models initialized") || {}
track("PLUGIN_MODELS_INIT_COMPLETED", { duration: pmAct.duration }) track("PLUGIN_MODELS_INIT_COMPLETED", { duration: pmAct.duration })
@@ -122,7 +117,7 @@ export default async (
const expActivity = Logger.activity("Initializing express") const expActivity = Logger.activity("Initializing express")
track("EXPRESS_INIT_STARTED") track("EXPRESS_INIT_STARTED")
await expressLoader({ app: expressApp, configModule }) await expressLoader({ app: expressApp, configModule })
await passportLoader({ app: expressApp, container }) await passportLoader({ app: expressApp, container, configModule })
const exAct = Logger.success(expActivity, "Express intialized") || {} const exAct = Logger.success(expActivity, "Express intialized") || {}
track("EXPRESS_INIT_COMPLETED", { duration: exAct.duration }) track("EXPRESS_INIT_COMPLETED", { duration: exAct.duration })
@@ -138,6 +133,7 @@ export default async (
await pluginsLoader({ await pluginsLoader({
container, container,
rootDirectory, rootDirectory,
configModule,
app: expressApp, app: expressApp,
activityId: pluginsActivity, activityId: pluginsActivity,
}) })
@@ -152,7 +148,7 @@ export default async (
const apiActivity = Logger.activity("Initializing API") const apiActivity = Logger.activity("Initializing API")
track("API_INIT_STARTED") track("API_INIT_STARTED")
await apiLoader({ container, rootDirectory, app: expressApp }) await apiLoader({ container, app: expressApp, configModule })
const apiAct = Logger.success(apiActivity, "API initialized") || {} const apiAct = Logger.success(apiActivity, "API initialized") || {}
track("API_INIT_COMPLETED", { duration: apiAct.duration }) track("API_INIT_COMPLETED", { duration: apiAct.duration })
+4 -4
View File
@@ -1,13 +1,12 @@
import config from "../config"
import passport from "passport" import passport from "passport"
import { AuthService } from "../services" import { AuthService } from "../services"
import { Express } from 'express' import { Express } from 'express'
import { MedusaContainer } from "../types/global" import { ConfigModule, MedusaContainer } from "../types/global"
import { Strategy as BearerStrategy } from "passport-http-bearer" import { Strategy as BearerStrategy } from "passport-http-bearer"
import { Strategy as JWTStrategy } from "passport-jwt" import { Strategy as JWTStrategy } from "passport-jwt"
import { Strategy as LocalStrategy } from "passport-local" import { Strategy as LocalStrategy } from "passport-local"
export default async ({ app, container }: { app: Express; container: MedusaContainer }): Promise<void> => { export default async ({ app, container, configModule }: { app: Express; container: MedusaContainer; configModule: ConfigModule; }): Promise<void> => {
const authService = container.resolve<AuthService>("authService") const authService = container.resolve<AuthService>("authService")
// For good old email password authentication // For good old email password authentication
@@ -34,11 +33,12 @@ export default async ({ app, container }: { app: Express; container: MedusaConta
// After a user has authenticated a JWT will be placed on a cookie, all // After a user has authenticated a JWT will be placed on a cookie, all
// calls will be authenticated based on the JWT // calls will be authenticated based on the JWT
const { jwt_secret } = configModule.projectConfig
passport.use( passport.use(
new JWTStrategy( new JWTStrategy(
{ {
jwtFromRequest: (req) => req.session.jwt, jwtFromRequest: (req) => req.session.jwt,
secretOrKey: config.jwtSecret, secretOrKey: jwt_secret,
}, },
async (jwtPayload, done) => { async (jwtPayload, done) => {
return done(null, jwtPayload) return done(null, jwtPayload)
+19 -21
View File
@@ -18,30 +18,30 @@ import { asValue, asClass, asFunction, aliasTo } from "awilix"
import { sync as existsSync } from "fs-exists-cached" import { sync as existsSync } from "fs-exists-cached"
import { AbstractTaxService, isTaxCalculationStrategy } from "../interfaces" import { AbstractTaxService, isTaxCalculationStrategy } from "../interfaces"
import formatRegistrationName from "../utils/format-registration-name" import formatRegistrationName from "../utils/format-registration-name"
import { ClassConstructor, Logger, MedusaContainer } from "../types/global" import { ClassConstructor, ConfigModule, Logger, MedusaContainer } from "../types/global"
import { ConfigModule } from "./index"
import { MiddlewareService } from "../services" import { MiddlewareService } from "../services"
type Options = { type Options = {
rootDirectory: string; rootDirectory: string
container: MedusaContainer; container: MedusaContainer
app: Express; configModule: ConfigModule
app: Express
activityId: string activityId: string
} }
type PluginDetails = { type PluginDetails = {
resolve: string; resolve: string
name: string; name: string
id: string; id: string
options: Record<string, unknown>; options: Record<string, unknown>
version: string; version: string
} }
/** /**
* Registers all services in the services directory * Registers all services in the services directory
*/ */
export default async ({ rootDirectory, container, app, activityId }: Options): Promise<void> => { export default async ({ rootDirectory, container, app, configModule, activityId }: Options): Promise<void> => {
const resolved = getResolvedPlugins(rootDirectory) || [] const resolved = getResolvedPlugins(rootDirectory, configModule) || []
await Promise.all( await Promise.all(
resolved.map(async (pluginDetails) => { resolved.map(async (pluginDetails) => {
@@ -59,13 +59,7 @@ export default async ({ rootDirectory, container, app, activityId }: Options): P
) )
} }
function getResolvedPlugins(rootDirectory: string): undefined | PluginDetails[] { function getResolvedPlugins(rootDirectory: string, configModule: ConfigModule): undefined | PluginDetails[] {
const { configModule } = getConfigFile(rootDirectory, `medusa-config`) as { configModule: ConfigModule }
if (!configModule) {
return
}
const { plugins } = configModule const { plugins } = configModule
const resolved = plugins.map((plugin) => { const resolved = plugins.map((plugin) => {
@@ -90,8 +84,12 @@ function getResolvedPlugins(rootDirectory: string): undefined | PluginDetails[]
return resolved return resolved
} }
export async function registerPluginModels({ rootDirectory, container }: { rootDirectory: string; container: MedusaContainer }): Promise<void> { export async function registerPluginModels({
const resolved = getResolvedPlugins(rootDirectory) || [] rootDirectory,
container,
configModule
}: { rootDirectory: string; container: MedusaContainer; configModule: ConfigModule; }): Promise<void> {
const resolved = getResolvedPlugins(rootDirectory, configModule) || []
await Promise.all( await Promise.all(
resolved.map(async (pluginDetails) => { resolved.map(async (pluginDetails) => {
registerModels(pluginDetails, container) registerModels(pluginDetails, container)
+2 -6
View File
@@ -1,16 +1,12 @@
import { asValue } from "awilix" import { asValue } from "awilix"
import RealRedis from "ioredis" import RealRedis from "ioredis"
import FakeRedis from "ioredis-mock" import FakeRedis from "ioredis-mock"
import { MedusaContainer } from "../types/global" import { ConfigModule, MedusaContainer } from "../types/global"
import { Logger } from "../types/global" import { Logger } from "../types/global"
export type RedisConfig = {
redis_url?: string;
}
type Options = { type Options = {
container: MedusaContainer; container: MedusaContainer;
configModule: { projectConfig: RedisConfig }; configModule: ConfigModule;
logger: Logger; logger: Logger;
} }
+2 -3
View File
@@ -2,13 +2,12 @@ import glob from "glob"
import path from "path" import path from "path"
import { asFunction } from "awilix" import { asFunction } from "awilix"
import formatRegistrationName from "../utils/format-registration-name" import formatRegistrationName from "../utils/format-registration-name"
import { MedusaContainer } from "../types/global" import { ConfigModule, MedusaContainer } from "../types/global"
import { ConfigModule } from "./index"
type Options = { type Options = {
container: MedusaContainer; container: MedusaContainer;
configModule: ConfigModule configModule: ConfigModule
isTest: boolean; isTest?: boolean;
} }
/** /**
+1 -1
View File
@@ -7,7 +7,7 @@ import formatRegistrationName from "../utils/format-registration-name"
type LoaderOptions = { type LoaderOptions = {
container: AwilixContainer container: AwilixContainer
configModule: object configModule: object
isTest: boolean isTest?: boolean
} }
/** /**
@@ -1,11 +1,11 @@
import Bull from "bull" import Bull from "bull"
import { MockRepository, MockManager } from "medusa-test-utils" import { MockRepository, MockManager } from "medusa-test-utils"
import EventBusService from "../event-bus" import EventBusService from "../event-bus"
import config from "../../config" import config from "../../loaders/config"
jest.genMockFromModule("bull") jest.genMockFromModule("bull")
jest.mock("bull") jest.mock("bull")
jest.mock("../../config") jest.mock("../../loaders/config")
config.redisURI = "testhost" config.redisURI = "testhost"
@@ -19,6 +19,8 @@ describe("InviteService", () => {
userRepository: {}, userRepository: {},
inviteRepository: inviteRepo, inviteRepository: inviteRepo,
eventBusService: EventBusServiceMock, eventBusService: EventBusServiceMock,
}, {
projectConfig: { jwt_secret: 'superSecret' }
}) })
it("calls invite repository find", async () => { it("calls invite repository find", async () => {
@@ -40,6 +42,8 @@ describe("InviteService", () => {
userRepository: {}, userRepository: {},
inviteRepository: {}, inviteRepository: {},
eventBusService: EventBusServiceMock, eventBusService: EventBusServiceMock,
}, {
projectConfig: { jwt_secret: 'superSecret' }
}) })
it("validating a signed token succeeds", () => { it("validating a signed token succeeds", () => {
@@ -108,6 +112,8 @@ describe("InviteService", () => {
userRepository: userRepo, userRepository: userRepo,
inviteRepository: inviteRepo, inviteRepository: inviteRepo,
eventBusService: EventBusServiceMock, eventBusService: EventBusServiceMock,
}, {
projectConfig: { jwt_secret: 'superSecret' }
}) })
beforeEach(() => jest.clearAllMocks()) beforeEach(() => jest.clearAllMocks())
@@ -200,6 +206,8 @@ describe("InviteService", () => {
userRepository: {}, userRepository: {},
inviteRepository: inviteRepo, inviteRepository: inviteRepo,
eventBusService: EventBusServiceMock, eventBusService: EventBusServiceMock,
}, {
projectConfig: { jwt_secret: 'superSecret' }
}) })
inviteService.generateToken = jest.fn() inviteService.generateToken = jest.fn()
+23 -11
View File
@@ -4,11 +4,11 @@ import { BaseService } from "medusa-interfaces"
import { EntityManager } from "typeorm" import { EntityManager } from "typeorm"
import { EventBusService, UserService } from "." import { EventBusService, UserService } from "."
import { User } from ".." import { User } from ".."
import config from "../config"
import { UserRoles } from "../models/user" import { UserRoles } from "../models/user"
import { InviteRepository } from "../repositories/invite" import { InviteRepository } from "../repositories/invite"
import { UserRepository } from "../repositories/user" import { UserRepository } from "../repositories/user"
import { ListInvite } from "../types/invites" import { ListInvite } from "../types/invites"
import { ConfigModule } from "../types/global"
// 7 days // 7 days
const DEFAULT_VALID_DURATION = 1000 * 60 * 60 * 24 * 7 const DEFAULT_VALID_DURATION = 1000 * 60 * 60 * 24 * 7
@@ -32,15 +32,22 @@ class InviteService extends BaseService {
private inviteRepository_: InviteRepository private inviteRepository_: InviteRepository
private eventBus_: EventBusService private eventBus_: EventBusService
constructor({ protected readonly configModule_: ConfigModule
constructor(
{
manager, manager,
userService, userService,
userRepository, userRepository,
inviteRepository, inviteRepository,
eventBusService, eventBusService,
}: InviteServiceProps) { }: InviteServiceProps,
configModule: ConfigModule
) {
super() super()
this.configModule_ = configModule
/** @private @constant {EntityManager} */ /** @private @constant {EntityManager} */
this.manager_ = manager this.manager_ = manager
@@ -62,13 +69,16 @@ class InviteService extends BaseService {
return this return this
} }
const cloned = new InviteService({ const cloned = new InviteService(
{
manager, manager,
inviteRepository: this.inviteRepository_, inviteRepository: this.inviteRepository_,
userService: this.userService_, userService: this.userService_,
userRepository: this.userRepo_, userRepository: this.userRepo_,
eventBusService: this.eventBus_, eventBusService: this.eventBus_,
}) },
this.configModule_
)
cloned.transactionManager_ = manager cloned.transactionManager_ = manager
@@ -76,12 +86,13 @@ class InviteService extends BaseService {
} }
generateToken(data): string { generateToken(data): string {
if (config.jwtSecret) { const { jwt_secret } = this.configModule_.projectConfig
return jwt.sign(data, config.jwtSecret) if (jwt_secret) {
return jwt.sign(data, jwt_secret)
} }
throw new MedusaError( throw new MedusaError(
MedusaError.Types.INVALID_DATA, MedusaError.Types.INVALID_DATA,
"Please configure JwtSecret" "Please configure jwt_secret"
) )
} }
@@ -248,12 +259,13 @@ class InviteService extends BaseService {
} }
verifyToken(token): JwtPayload | string { verifyToken(token): JwtPayload | string {
if (config.jwtSecret) { const { jwt_secret } = this.configModule_.projectConfig
return jwt.verify(token, config.jwtSecret) if (jwt_secret) {
return jwt.verify(token, jwt_secret)
} }
throw new MedusaError( throw new MedusaError(
MedusaError.Types.INVALID_DATA, MedusaError.Types.INVALID_DATA,
"Please configure JwtSecret" "Please configure jwt_secret"
) )
} }
+28
View File
@@ -1,5 +1,6 @@
import { AwilixContainer } from "awilix" import { AwilixContainer } from "awilix"
import { Logger as _Logger } from "winston" import { Logger as _Logger } from "winston"
import { LoggerOptions } from "typeorm"
export type ClassConstructor<T> = { export type ClassConstructor<T> = {
new (...args: unknown[]): T new (...args: unknown[]): T
@@ -12,3 +13,30 @@ export type MedusaContainer = AwilixContainer & {
export type Logger = _Logger & { export type Logger = _Logger & {
progress: (activityId: string, msg: string) => void progress: (activityId: string, msg: string) => void
} }
export type ConfigModule = {
projectConfig: {
redis_url?: string
jwt_secret?: string
cookie_secret?: string
database_url?: string
database_type: string
database_database?: string
database_logging: LoggerOptions
database_extra?: Record<string, unknown> & {
ssl: { rejectUnauthorized: false }
}
store_cors?: string
admin_cors?: string
}
plugins: (
| {
resolve: string
options: Record<string, unknown>
}
| string
)[]
}
-6
View File
@@ -1519,7 +1519,6 @@
chalk "^4.0.0" chalk "^4.0.0"
configstore "5.0.1" configstore "5.0.1"
core-js "^3.6.5" core-js "^3.6.5"
dotenv "^8.2.0"
execa "^5.1.1" execa "^5.1.1"
fs-exists-cached "^1.0.0" fs-exists-cached "^1.0.0"
fs-extra "^10.0.0" fs-extra "^10.0.0"
@@ -3242,11 +3241,6 @@ dot-prop@^5.2.0:
dependencies: dependencies:
is-obj "^2.0.0" is-obj "^2.0.0"
dotenv@^8.2.0:
version "8.6.0"
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.6.0.tgz#061af664d19f7f4d8fc6e4ff9b584ce237adcb8b"
integrity sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==
duplexer3@^0.1.4: duplexer3@^0.1.4:
version "0.1.4" version "0.1.4"
resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2"