Don't initialize database on file require (#6003)

This commit is contained in:
Rijk van Zanten
2021-06-02 11:41:42 -04:00
committed by GitHub
parent 02fc696c53
commit 77e00b7db4
39 changed files with 222 additions and 146 deletions

View File

@@ -4,6 +4,8 @@ import installDatabase from '../../../database/seeds/run';
import env from '../../../env';
import logger from '../../../logger';
import { getSchema } from '../../../utils/get-schema';
import { RolesService, UsersService, SettingsService } from '../../../services';
import getDatabase, { isInstalled, hasDatabaseConnection } from '../../../database';
export default async function bootstrap(): Promise<void> {
logger.info('Initializing bootstrap...');
@@ -13,10 +15,7 @@ export default async function bootstrap(): Promise<void> {
process.exit(1);
}
const { isInstalled, default: database } = require('../../../database');
const { RolesService } = require('../../../services/roles');
const { UsersService } = require('../../../services/users');
const { SettingsService } = require('../../../services/settings');
const database = getDatabase();
if ((await isInstalled()) === false) {
logger.info('Installing Directus system tables...');
@@ -66,8 +65,6 @@ export default async function bootstrap(): Promise<void> {
}
async function isDatabaseAvailable() {
const { hasDatabaseConnection } = require('../../../database');
const tries = 5;
const secondsBetweenTries = 5;

View File

@@ -1,5 +1,7 @@
import getDatabase from '../../../database';
export default async function count(collection: string): Promise<void> {
const database = require('../../../database/index').default;
const database = getDatabase();
if (!collection) {
console.error('Collection is required');

View File

@@ -1,9 +1,9 @@
import { Knex } from 'knex';
import runMigrations from '../../../database/migrations/run';
import installSeeds from '../../../database/seeds/run';
import getDatabase from '../../../database';
export default async function start(): Promise<void> {
const database = require('../../../database/index').default as Knex;
const database = getDatabase();
try {
await installSeeds(database);

View File

@@ -1,7 +1,8 @@
import run from '../../../database/migrations/run';
import getDatabase from '../../../database';
export default async function migrate(direction: 'latest' | 'up' | 'down'): Promise<void> {
const database = require('../../../database').default;
const database = getDatabase();
try {
console.log('✨ Running migrations...');

View File

@@ -1,8 +1,9 @@
import { getSchema } from '../../../utils/get-schema';
import { RolesService } from '../../../services';
import getDatabase from '../../../database';
export default async function rolesCreate({ role: name, admin }: { role: string; admin: boolean }): Promise<void> {
const { default: database } = require('../../../database/index');
const { RolesService } = require('../../../services/roles');
const database = getDatabase();
if (!name) {
console.error('Name is required');

View File

@@ -1,4 +1,6 @@
import { getSchema } from '../../../utils/get-schema';
import { UsersService } from '../../../services';
import getDatabase from '../../../database';
export default async function usersCreate({
email,
@@ -9,8 +11,7 @@ export default async function usersCreate({
password?: string;
role?: string;
}): Promise<void> {
const { default: database } = require('../../../database/index');
const { UsersService } = require('../../../services/users');
const database = getDatabase();
if (!email || !password || !role) {
console.error('Email, password, role are required');

View File

@@ -1,9 +1,10 @@
import argon2 from 'argon2';
import { getSchema } from '../../../utils/get-schema';
import { UsersService } from '../../../services';
import getDatabase from '../../../database';
export default async function usersPasswd({ email, password }: { email?: string; password?: string }): Promise<void> {
const { default: database } = require('../../../database/index');
const { UsersService } = require('../../../services/users');
const database = getDatabase();
if (!email || !password) {
console.error('Email and password are required');

View File

@@ -4,7 +4,7 @@ import { pick } from 'lodash';
import ms from 'ms';
import validate from 'uuid-validate';
import { ASSET_TRANSFORM_QUERY_KEYS, SYSTEM_ASSET_ALLOW_LIST } from '../constants';
import database from '../database';
import getDatabase from '../database';
import env from '../env';
import { ForbiddenException, InvalidQueryException, RangeNotSatisfiableException } from '../exceptions';
import useCollection from '../middleware/use-collection';
@@ -32,11 +32,11 @@ router.get(
* This is a little annoying. Postgres will error out if you're trying to search in `where`
* with a wrong type. In case of directus_files where id is a uuid, we'll have to verify the
* validity of the uuid ahead of time.
* @todo move this to a validation middleware function
*/
const isValidUUID = validate(id, 4);
if (isValidUUID === false) throw new ForbiddenException();
const database = getDatabase();
const file = await database.select('id', 'storage', 'filename_disk').from('directus_files').where({ id }).first();
if (!file) throw new ForbiddenException();
@@ -51,6 +51,7 @@ router.get(
const payloadService = new PayloadService('directus_settings', { schema: req.schema });
const defaults = { storage_asset_presets: [], storage_asset_transform: 'all' };
const database = getDatabase();
const savedAssetSettings = await database
.select('storage_asset_presets', 'storage_asset_transform')
.from('directus_settings')

View File

@@ -1,76 +1,98 @@
import SchemaInspector from '@directus/schema';
import dotenv from 'dotenv';
import { knex, Knex } from 'knex';
import path from 'path';
import { performance } from 'perf_hooks';
import env from '../env';
import logger from '../logger';
import { getConfigFromEnv } from '../utils/get-config-from-env';
import { validateEnv } from '../utils/validate-env';
dotenv.config({ path: path.resolve(__dirname, '../../', '.env') });
let database: Knex | null = null;
let inspector: ReturnType<typeof SchemaInspector> | null = null;
const connectionConfig: Record<string, any> = getConfigFromEnv('DB_', [
'DB_CLIENT',
'DB_SEARCH_PATH',
'DB_CONNECTION_STRING',
'DB_POOL',
]);
const poolConfig = getConfigFromEnv('DB_POOL');
const requiredKeys = ['DB_CLIENT'];
if (env.DB_CLIENT && env.DB_CLIENT === 'sqlite3') {
requiredKeys.push('DB_FILENAME');
} else if (env.DB_CLIENT && env.DB_CLIENT === 'oracledb') {
requiredKeys.push('DB_USER', 'DB_PASSWORD', 'DB_CONNECT_STRING');
} else {
if (env.DB_CLIENT === 'pg') {
if (!env.DB_CONNECTION_STRING) {
requiredKeys.push('DB_HOST', 'DB_PORT', 'DB_DATABASE', 'DB_USER');
}
} else {
requiredKeys.push('DB_HOST', 'DB_PORT', 'DB_DATABASE', 'DB_USER', 'DB_PASSWORD');
export default function getDatabase() {
if (database) {
return database;
}
}
validateEnv(requiredKeys);
const connectionConfig: Record<string, any> = getConfigFromEnv('DB_', [
'DB_CLIENT',
'DB_SEARCH_PATH',
'DB_CONNECTION_STRING',
'DB_POOL',
]);
const knexConfig: Knex.Config = {
client: env.DB_CLIENT,
searchPath: env.DB_SEARCH_PATH,
connection: env.DB_CONNECTION_STRING || connectionConfig,
log: {
warn: (msg) => logger.warn(msg),
error: (msg) => logger.error(msg),
deprecate: (msg) => logger.info(msg),
debug: (msg) => logger.debug(msg),
},
pool: poolConfig,
};
const poolConfig = getConfigFromEnv('DB_POOL');
if (env.DB_CLIENT === 'sqlite3') {
knexConfig.useNullAsDefault = true;
poolConfig.afterCreate = (conn: any, cb: any) => {
conn.run('PRAGMA foreign_keys = ON', cb);
const requiredEnvVars = ['DB_CLIENT'];
if (env.DB_CLIENT && env.DB_CLIENT === 'sqlite3') {
requiredEnvVars.push('DB_FILENAME');
} else if (env.DB_CLIENT && env.DB_CLIENT === 'oracledb') {
requiredEnvVars.push('DB_USER', 'DB_PASSWORD', 'DB_CONNECT_STRING');
} else {
if (env.DB_CLIENT === 'pg') {
if (!env.DB_CONNECTION_STRING) {
requiredEnvVars.push('DB_HOST', 'DB_PORT', 'DB_DATABASE', 'DB_USER');
}
} else {
requiredEnvVars.push('DB_HOST', 'DB_PORT', 'DB_DATABASE', 'DB_USER', 'DB_PASSWORD');
}
}
validateEnv(requiredEnvVars);
const knexConfig: Knex.Config = {
client: env.DB_CLIENT,
searchPath: env.DB_SEARCH_PATH,
connection: env.DB_CONNECTION_STRING || connectionConfig,
log: {
warn: (msg) => logger.warn(msg),
error: (msg) => logger.error(msg),
deprecate: (msg) => logger.info(msg),
debug: (msg) => logger.debug(msg),
},
pool: poolConfig,
};
if (env.DB_CLIENT === 'sqlite3') {
knexConfig.useNullAsDefault = true;
poolConfig.afterCreate = (conn: any, cb: any) => {
conn.run('PRAGMA foreign_keys = ON', cb);
};
}
database = knex(knexConfig);
const times: Record<string, number> = {};
database
.on('query', (queryInfo) => {
times[queryInfo.__knexUid] = performance.now();
})
.on('query-response', (response, queryInfo) => {
const delta = performance.now() - times[queryInfo.__knexUid];
logger.trace(`[${delta.toFixed(3)}ms] ${queryInfo.sql} [${queryInfo.bindings.join(', ')}]`);
delete times[queryInfo.__knexUid];
});
return database;
}
const database = knex(knexConfig);
export function getSchemaInspector() {
if (inspector) {
return inspector;
}
const times: Record<string, number> = {};
const database = getDatabase();
database
.on('query', (queryInfo) => {
times[queryInfo.__knexUid] = performance.now();
})
.on('query-response', (response, queryInfo) => {
const delta = performance.now() - times[queryInfo.__knexUid];
logger.trace(`[${delta.toFixed(3)}ms] ${queryInfo.sql} [${queryInfo.bindings.join(', ')}]`);
});
inspector = SchemaInspector(database);
return inspector;
}
export async function hasDatabaseConnection(): Promise<boolean> {
const database = getDatabase();
try {
if (env.DB_CLIENT === 'oracledb') {
await database.raw('select 1 from DUAL');
@@ -93,13 +115,11 @@ export async function validateDBConnection(): Promise<void> {
}
}
export const schemaInspector = SchemaInspector(database);
export async function isInstalled(): Promise<boolean> {
const inspector = getSchemaInspector();
// The existence of a directus_collections table alone isn't a "proper" check to see if everything
// is installed correctly of course, but it's safe enough to assume that this collection only
// exists when using the installer CLI.
return await schemaInspector.hasTable('directus_collections');
return await inspector.hasTable('directus_collections');
}
export default database;

View File

@@ -1,6 +1,5 @@
import { Knex } from 'knex';
import SchemaInspector from 'knex-schema-inspector';
import { schemaInspector } from '..';
import logger from '../../logger';
import { RelationMeta } from '../../types';
@@ -22,8 +21,8 @@ export async function up(knex: Knex): Promise<void> {
for (const constraint of constraintsToAdd) {
if (!constraint.one_collection) continue;
const currentPrimaryKeyField = await schemaInspector.primary(constraint.many_collection);
const relatedPrimaryKeyField = await schemaInspector.primary(constraint.one_collection);
const currentPrimaryKeyField = await inspector.primary(constraint.many_collection);
const relatedPrimaryKeyField = await inspector.primary(constraint.one_collection);
if (!currentPrimaryKeyField || !relatedPrimaryKeyField) continue;
const rowsWithIllegalFKValues = await knex
@@ -67,8 +66,8 @@ export async function up(knex: Knex): Promise<void> {
// to `unsigned`, but defaults `.integer()` to `int`. This means that created m2o fields
// have the wrong type. This step will force the m2o `int` field into `unsigned`, but only
// if both types are integers, and only if we go from `int` to `int unsigned`.
const columnInfo = await schemaInspector.columnInfo(constraint.many_collection, constraint.many_field);
const relatedColumnInfo = await schemaInspector.columnInfo(constraint.one_collection!, relatedPrimaryKeyField);
const columnInfo = await inspector.columnInfo(constraint.many_collection, constraint.many_field);
const relatedColumnInfo = await inspector.columnInfo(constraint.one_collection!, relatedPrimaryKeyField);
try {
await knex.schema.alterTable(constraint.many_collection, (table) => {

View File

@@ -5,7 +5,7 @@ import { Item, Query, SchemaOverview } from '../types';
import { AST, FieldNode, NestedCollectionNode } from '../types/ast';
import applyQuery from '../utils/apply-query';
import { toArray } from '../utils/to-array';
import database from './index';
import getDatabase from './index';
type RunASTOptions = {
/**
@@ -39,7 +39,7 @@ export default async function runAST(
): Promise<null | Item | Item[]> {
const ast = cloneDeep(originalAST);
const knex = options?.knex || database;
const knex = options?.knex || getDatabase();
if (ast.type === 'm2a') {
const results: { [collection: string]: null | Item | Item[] } = {};

View File

@@ -92,6 +92,22 @@ env = processValues(env);
export default env;
/**
* When changes have been made during runtime, like in the CLI, we can refresh the env object with
* the newly created variables
*/
export function refreshEnv() {
env = {
...defaults,
...getEnv(),
...process.env,
};
process.env = env;
env = processValues(env);
}
function getEnv() {
const configPath = path.resolve(process.env.CONFIG_PATH || defaults.CONFIG_PATH);

View File

@@ -1,4 +1,4 @@
import database from '../../../database';
import getDatabase from '../../../database';
import { ContainsNullValuesException } from '../contains-null-values';
import { InvalidForeignKeyException } from '../invalid-foreign-key';
import { NotNullViolationException } from '../not-null-violation';
@@ -56,6 +56,8 @@ async function uniqueViolation(error: MSSQLError) {
const keyName = quoteMatches[1];
const database = getDatabase();
const constraintUsage = await database
.select('*')
.from('INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE')

View File

@@ -1,4 +1,4 @@
import database from '../../database';
import getDatabase from '../../database';
import { extractError as mssql } from './dialects/mssql';
import { extractError as mysql } from './dialects/mysql';
import { extractError as oracle } from './dialects/oracle';
@@ -16,6 +16,8 @@ import { SQLError } from './dialects/types';
* - Value Too Long
*/
export async function translateDatabaseError(error: SQLError): Promise<any> {
const database = getDatabase();
switch (database.client.constructor.name) {
case 'Client_MySQL':
return mysql(error);

View File

@@ -1,7 +1,7 @@
import express, { Router } from 'express';
import { ensureDir } from 'fs-extra';
import path from 'path';
import database from './database';
import getDatabase from './database';
import emitter from './emitter';
import env from './env';
import * as exceptions from './exceptions';
@@ -93,7 +93,7 @@ function registerHooks(hooks: string[]) {
}
}
const events = register({ services, exceptions, env, database, getSchema });
const events = register({ services, exceptions, env, database: getDatabase(), getSchema });
for (const [event, handler] of Object.entries(events)) {
emitter.on(event, handler);
}
@@ -126,6 +126,6 @@ function registerEndpoints(endpoints: string[], router: Router) {
const scopedRouter = express.Router();
router.use(`/${endpoint}/`, scopedRouter);
register(scopedRouter, { services, exceptions, env, database, getSchema });
register(scopedRouter, { services, exceptions, env, database: getDatabase(), getSchema });
}
}

View File

@@ -1,6 +1,6 @@
import { RequestHandler } from 'express';
import jwt, { JsonWebTokenError, TokenExpiredError } from 'jsonwebtoken';
import database from '../database';
import getDatabase from '../database';
import env from '../env';
import { InvalidCredentialsException } from '../exceptions';
import asyncHandler from '../utils/async-handler';
@@ -21,6 +21,8 @@ const authenticate: RequestHandler = asyncHandler(async (req, res, next) => {
if (!req.token) return next();
const database = getDatabase();
if (isJWT(req.token)) {
let payload: { id: string };

View File

@@ -1,9 +1,11 @@
import { RequestHandler } from 'express';
import database from '../database';
import getDatabase from '../database';
import { InvalidIPException } from '../exceptions';
import asyncHandler from '../utils/async-handler';
export const checkIP: RequestHandler = asyncHandler(async (req, res, next) => {
const database = getDatabase();
const role = await database
.select('ip_access')
.from('directus_roles')

View File

@@ -1,7 +1,7 @@
import expressSession, { Store } from 'express-session';
import env from '../env';
import { getConfigFromEnv } from '../utils/get-config-from-env';
import database from '../database';
import getDatabase from '../database';
let store: Store | undefined = undefined;
if (env.SESSION_STORE === 'redis') {
@@ -20,7 +20,7 @@ if (env.SESSION_STORE === 'memcache') {
if (env.SESSION_STORE === 'database') {
const KnexSessionStore = require('connect-session-knex')(expressSession);
store = new KnexSessionStore({
knex: database,
knex: getDatabase(),
tablename: 'oauth_sessions', // optional. Defaults to 'sessions'
});
}

View File

@@ -6,7 +6,7 @@ import { once } from 'lodash';
import qs from 'qs';
import url from 'url';
import createApp from './app';
import database from './database';
import getDatabase from './database';
import { emitAsyncSafe } from './emitter';
import logger from './logger';
@@ -94,6 +94,7 @@ export default async function createServer(): Promise<http.Server> {
}
async function onSignal() {
const database = getDatabase();
await database.destroy();
logger.info('Database connections destroyed');
}

View File

@@ -2,7 +2,7 @@ import { Range, StatResponse } from '@directus/drive';
import { Knex } from 'knex';
import path from 'path';
import sharp, { ResizeOptions } from 'sharp';
import database from '../database';
import getDatabase from '../database';
import { RangeNotSatisfiableException, IllegalAssetTransformation } from '../exceptions';
import storage from '../storage';
import { AbstractServiceOptions, Accountability, Transformation } from '../types';
@@ -23,7 +23,7 @@ export class AssetsService {
authorizationService: AuthorizationService;
constructor(options: AbstractServiceOptions) {
this.knex = options.knex || database;
this.knex = options.knex || getDatabase();
this.accountability = options.accountability || null;
this.authorizationService = new AuthorizationService(options);
}
@@ -44,7 +44,7 @@ export class AssetsService {
await this.authorizationService.checkAccess('read', 'directus_files', id);
}
const file = (await database.select('*').from('directus_files').where({ id }).first()) as File;
const file = (await this.knex.select('*').from('directus_files').where({ id }).first()) as File;
if (range) {
if (range.start >= file.filesize || (range.end && range.end >= file.filesize)) {

View File

@@ -4,7 +4,7 @@ import { Knex } from 'knex';
import ms from 'ms';
import { nanoid } from 'nanoid';
import { authenticator } from 'otplib';
import database from '../database';
import getDatabase from '../database';
import emitter, { emitAsyncSafe } from '../emitter';
import env from '../env';
import {
@@ -37,7 +37,7 @@ export class AuthenticationService {
schema: SchemaOverview;
constructor(options: AbstractServiceOptions) {
this.knex = options.knex || database;
this.knex = options.knex || getDatabase();
this.accountability = options.accountability || null;
this.activityService = new ActivityService({ knex: this.knex, schema: options.schema });
this.schema = options.schema;
@@ -59,7 +59,7 @@ export class AuthenticationService {
const { email, password, ip, userAgent, otp } = options;
let user = await database
let user = await this.knex
.select('id', 'password', 'role', 'tfa_secret', 'status')
.from('directus_users')
.whereRaw('LOWER(??) = ?', ['email', email.toLowerCase()])
@@ -114,7 +114,7 @@ export class AuthenticationService {
try {
await loginAttemptsLimiter.consume(user.id);
} catch (err) {
await database('directus_users').update({ status: 'suspended' }).where({ id: user.id });
await this.knex('directus_users').update({ status: 'suspended' }).where({ id: user.id });
user.status = 'suspended';
// This means that new attempts after the user has been re-activated will be accepted
@@ -164,7 +164,7 @@ export class AuthenticationService {
const refreshToken = nanoid(64);
const refreshTokenExpiration = new Date(Date.now() + ms(env.REFRESH_TOKEN_TTL as string));
await database('directus_sessions').insert({
await this.knex('directus_sessions').insert({
token: refreshToken,
user: user.id,
expires: refreshTokenExpiration,
@@ -172,7 +172,7 @@ export class AuthenticationService {
user_agent: userAgent,
});
await database('directus_sessions').delete().where('expires', '<', new Date());
await this.knex('directus_sessions').delete().where('expires', '<', new Date());
if (this.accountability) {
await this.activityService.createOne({
@@ -204,7 +204,7 @@ export class AuthenticationService {
throw new InvalidCredentialsException();
}
const record = await database
const record = await this.knex
.select<Session & { email: string; id: string }>(
'directus_sessions.*',
'directus_users.email',

View File

@@ -1,6 +1,6 @@
import { Knex } from 'knex';
import { cloneDeep, flatten, merge, uniq, uniqWith } from 'lodash';
import database from '../database';
import getDatabase from '../database';
import { FailedValidationException, ForbiddenException } from '../exceptions';
import {
AbstractServiceOptions,
@@ -28,7 +28,7 @@ export class AuthorizationService {
schema: SchemaOverview;
constructor(options: AbstractServiceOptions) {
this.knex = options.knex || database;
this.knex = options.knex || getDatabase();
this.accountability = options.accountability || null;
this.schema = options.schema;
this.payloadService = new PayloadService('directus_permissions', {

View File

@@ -2,7 +2,7 @@ import SchemaInspector from '@directus/schema';
import { Knex } from 'knex';
import cache from '../cache';
import { ALIAS_TYPES } from '../constants';
import database, { schemaInspector } from '../database';
import getDatabase, { getSchemaInspector } from '../database';
import { systemCollectionRows } from '../database/system-data/collections';
import env from '../env';
import { ForbiddenException, InvalidPayloadException } from '../exceptions';
@@ -27,13 +27,13 @@ export type RawCollection = {
export class CollectionsService {
knex: Knex;
accountability: Accountability | null;
schemaInspector: typeof schemaInspector;
schemaInspector: ReturnType<typeof SchemaInspector>;
schema: SchemaOverview;
constructor(options: AbstractServiceOptions) {
this.knex = options.knex || database;
this.knex = options.knex || getDatabase();
this.accountability = options.accountability || null;
this.schemaInspector = options.knex ? SchemaInspector(options.knex) : schemaInspector;
this.schemaInspector = options.knex ? SchemaInspector(options.knex) : getSchemaInspector();
this.schema = options.schema;
}

View File

@@ -3,7 +3,7 @@ import { Knex } from 'knex';
import { Column } from 'knex-schema-inspector/dist/types/column';
import cache from '../cache';
import { ALIAS_TYPES } from '../constants';
import database, { schemaInspector } from '../database';
import getDatabase, { getSchemaInspector } from '../database';
import { systemFieldRows } from '../database/system-data/fields/';
import emitter, { emitAsyncSafe } from '../emitter';
import env from '../env';
@@ -26,12 +26,12 @@ export class FieldsService {
accountability: Accountability | null;
itemsService: ItemsService;
payloadService: PayloadService;
schemaInspector: typeof schemaInspector;
schemaInspector: ReturnType<typeof SchemaInspector>;
schema: SchemaOverview;
constructor(options: AbstractServiceOptions) {
this.knex = options.knex || database;
this.schemaInspector = options.knex ? SchemaInspector(options.knex) : schemaInspector;
this.knex = options.knex || getDatabase();
this.schemaInspector = options.knex ? SchemaInspector(options.knex) : getSchemaInspector();
this.accountability = options.accountability || null;
this.itemsService = new ItemsService('directus_fields', options);
this.payloadService = new PayloadService('directus_fields', options);

View File

@@ -44,7 +44,7 @@ import {
import { Knex } from 'knex';
import { flatten, get, mapKeys, merge, set, uniq } from 'lodash';
import ms from 'ms';
import database from '../database';
import getDatabase from '../database';
import env from '../env';
import { BaseException, GraphQLValidationException, InvalidPayloadException } from '../exceptions';
import { listExtensions } from '../extensions';
@@ -115,7 +115,7 @@ export class GraphQLService {
constructor(options: AbstractServiceOptions & { scope: 'items' | 'system' }) {
this.accountability = options?.accountability || null;
this.knex = options?.knex || database;
this.knex = options?.knex || getDatabase();
this.schema = options.schema;
this.scope = options.scope;
}

View File

@@ -1,5 +1,5 @@
import { Knex } from 'knex';
import database from '../database';
import getDatabase from '../database';
import { AbstractServiceOptions, Accountability, SchemaOverview } from '../types';
import { ForbiddenException, InvalidPayloadException } from '../exceptions';
import StreamArray from 'stream-json/streamers/StreamArray';
@@ -15,7 +15,7 @@ export class ImportService {
schema: SchemaOverview;
constructor(options: AbstractServiceOptions) {
this.knex = options.knex || database;
this.knex = options.knex || getDatabase();
this.accountability = options.accountability || null;
this.schema = options.schema;
}

View File

@@ -1,7 +1,7 @@
import { Knex } from 'knex';
import { clone, cloneDeep, merge, pick, without } from 'lodash';
import cache from '../cache';
import database from '../database';
import getDatabase from '../database';
import runAST from '../database/run-ast';
import emitter, { emitAsyncSafe } from '../emitter';
import env from '../env';
@@ -55,7 +55,7 @@ export class ItemsService<Item extends AnyItem = AnyItem> implements AbstractSer
constructor(collection: string, options: AbstractServiceOptions) {
this.collection = collection;
this.knex = options.knex || database;
this.knex = options.knex || getDatabase();
this.accountability = options.accountability || null;
this.eventScope = this.collection.startsWith('directus_') ? this.collection.substring(9) : 'items';
this.schema = options.schema;
@@ -204,7 +204,7 @@ export class ItemsService<Item extends AnyItem = AnyItem> implements AbstractSer
schema: this.schema,
// This hook is called async. If we would pass the transaction here, the hook can be
// called after the transaction is done #5460
database: database,
database: getDatabase(),
});
}
@@ -516,7 +516,7 @@ export class ItemsService<Item extends AnyItem = AnyItem> implements AbstractSer
schema: this.schema,
// This hook is called async. If we would pass the transaction here, the hook can be
// called after the transaction is done #5460
database: database,
database: getDatabase(),
});
}
@@ -665,7 +665,7 @@ export class ItemsService<Item extends AnyItem = AnyItem> implements AbstractSer
schema: this.schema,
// This hook is called async. If we would pass the transaction here, the hook can be
// called after the transaction is done #5460
database: database,
database: getDatabase(),
});
}

View File

@@ -2,7 +2,7 @@ import fse from 'fs-extra';
import { Knex } from 'knex';
import { Liquid } from 'liquidjs';
import path from 'path';
import database from '../../database';
import getDatabase from '../../database';
import env from '../../env';
import { InvalidPayloadException } from '../../exceptions';
import logger from '../../logger';
@@ -30,7 +30,7 @@ export class MailService {
constructor(opts: AbstractServiceOptions) {
this.schema = opts.schema;
this.accountability = opts.accountability || null;
this.knex = opts?.knex || database;
this.knex = opts?.knex || getDatabase();
}
async send(options: EmailOptions): Promise<void> {

View File

@@ -1,5 +1,5 @@
import { Knex } from 'knex';
import database from '../database';
import getDatabase from '../database';
import { ForbiddenException } from '../exceptions';
import { AbstractServiceOptions, Accountability, SchemaOverview } from '../types';
import { Query } from '../types/query';
@@ -12,7 +12,7 @@ export class MetaService {
schema: SchemaOverview;
constructor(options: AbstractServiceOptions) {
this.knex = options.knex || database;
this.knex = options.knex || getDatabase();
this.accountability = options.accountability || null;
this.schema = options.schema;
}

View File

@@ -4,7 +4,7 @@ import Joi from 'joi';
import { Knex } from 'knex';
import { clone, cloneDeep, isObject, isPlainObject } from 'lodash';
import { v4 as uuidv4 } from 'uuid';
import database from '../database';
import getDatabase from '../database';
import { ForbiddenException, InvalidPayloadException } from '../exceptions';
import { AbstractServiceOptions, Accountability, Item, PrimaryKey, Query, SchemaOverview } from '../types';
import { toArray } from '../utils/to-array';
@@ -43,7 +43,7 @@ export class PayloadService {
constructor(collection: string, options: AbstractServiceOptions) {
this.accountability = options.accountability || null;
this.knex = options.knex || database;
this.knex = options.knex || getDatabase();
this.collection = collection;
this.schema = options.schema;

View File

@@ -7,20 +7,20 @@ import { ItemsService, QueryOptions } from './items';
import { PermissionsService } from './permissions';
import SchemaInspector from '@directus/schema';
import { ForeignKey } from 'knex-schema-inspector/dist/types/foreign-key';
import database, { schemaInspector } from '../database';
import getDatabase, { getSchemaInspector } from '../database';
export class RelationsService {
knex: Knex;
permissionsService: PermissionsService;
schemaInspector: typeof schemaInspector;
schemaInspector: ReturnType<typeof SchemaInspector>;
accountability: Accountability | null;
schema: SchemaOverview;
relationsItemService: ItemsService<RelationMeta>;
constructor(options: AbstractServiceOptions) {
this.knex = options.knex || database;
this.knex = options.knex || getDatabase();
this.permissionsService = new PermissionsService(options);
this.schemaInspector = options.knex ? SchemaInspector(options.knex) : schemaInspector;
this.schemaInspector = options.knex ? SchemaInspector(options.knex) : getSchemaInspector();
this.schema = options.schema;
this.accountability = options.accountability || null;
this.relationsItemService = new ItemsService('directus_relations', {

View File

@@ -7,7 +7,7 @@ import { performance } from 'perf_hooks';
// @ts-ignore
import { version } from '../../package.json';
import cache from '../cache';
import database, { hasDatabaseConnection } from '../database';
import getDatabase, { hasDatabaseConnection } from '../database';
import env from '../env';
import logger from '../logger';
import { rateLimiter } from '../middleware/rate-limiter';
@@ -24,7 +24,7 @@ export class ServerService {
schema: SchemaOverview;
constructor(options: AbstractServiceOptions) {
this.knex = options.knex || database;
this.knex = options.knex || getDatabase();
this.accountability = options.accountability || null;
this.schema = options.schema;
this.settingsService = new SettingsService({ knex: this.knex, schema: this.schema });
@@ -129,6 +129,7 @@ export class ServerService {
}
async function testDatabase(): Promise<Record<string, HealthCheck[]>> {
const database = getDatabase();
const client = env.DB_CLIENT;
const checks: Record<string, HealthCheck[]> = {};

View File

@@ -5,7 +5,7 @@ import { cloneDeep, mergeWith } from 'lodash';
import { OpenAPIObject, OperationObject, PathItemObject, SchemaObject, TagObject } from 'openapi3-ts';
// @ts-ignore
import { version } from '../../package.json';
import database from '../database';
import getDatabase from '../database';
import env from '../env';
import {
AbstractServiceOptions,
@@ -37,7 +37,7 @@ export class SpecificationService {
constructor(options: AbstractServiceOptions) {
this.accountability = options.accountability || null;
this.knex = options.knex || database;
this.knex = options.knex || getDatabase();
this.schema = options.schema;
this.fieldsService = new FieldsService(options);
@@ -80,7 +80,7 @@ class OASSpecsService implements SpecificationSubService {
}
) {
this.accountability = options.accountability || null;
this.knex = options.knex || database;
this.knex = options.knex || getDatabase();
this.schema = options.schema;
this.fieldsService = fieldsService;
@@ -541,7 +541,7 @@ class GraphQLSpecsService implements SpecificationSubService {
constructor(options: AbstractServiceOptions) {
this.accountability = options.accountability || null;
this.knex = options.knex || database;
this.knex = options.knex || getDatabase();
this.schema = options.schema;
this.items = new GraphQLService({ ...options, scope: 'items' });

View File

@@ -3,7 +3,7 @@ import jwt from 'jsonwebtoken';
import { Knex } from 'knex';
import { clone } from 'lodash';
import cache from '../cache';
import database from '../database';
import getDatabase from '../database';
import env from '../env';
import {
FailedValidationException,
@@ -29,7 +29,7 @@ export class UsersService extends ItemsService {
constructor(options: AbstractServiceOptions) {
super('directus_users', options);
this.knex = options.knex || database;
this.knex = options.knex || getDatabase();
this.accountability = options.accountability || null;
this.service = new ItemsService('directus_users', options);
this.schema = options.schema;

View File

@@ -1,5 +1,5 @@
import { Knex } from 'knex';
import database from '../database';
import getDatabase from '../database';
import { systemCollectionRows } from '../database/system-data/collections';
import { ForbiddenException, InvalidPayloadException } from '../exceptions';
import { AbstractServiceOptions, Accountability, PrimaryKey, SchemaOverview } from '../types';
@@ -10,7 +10,7 @@ export class UtilsService {
schema: SchemaOverview;
constructor(options: AbstractServiceOptions) {
this.knex = options.knex || database;
this.knex = options.knex || getDatabase();
this.accountability = options.accountability || null;
this.schema = options.schema;
}

View File

@@ -1,7 +1,6 @@
import { LocalFileSystemStorage, Storage, StorageManager, StorageManagerConfig } from '@directus/drive';
import { AzureBlobWebServicesStorage } from '@directus/drive-azure';
import { GoogleCloudStorage } from '@directus/drive-gcs';
/** @todo dynamically load these storage adapters */
import { AmazonWebServicesS3Storage } from '@directus/drive-s3';
import env from './env';
import { getConfigFromEnv } from './utils/get-config-from-env';

View File

@@ -11,13 +11,14 @@ import { toArray } from '../utils/to-array';
import getDefaultValue from './get-default-value';
import getLocalType from './get-local-type';
import { mergePermissions } from './merge-permissions';
import getDatabase from '../database';
export async function getSchema(options?: {
accountability?: Accountability;
database?: Knex;
}): Promise<SchemaOverview> {
// Allows for use in the CLI
const database = options?.database || (require('../database').default as Knex);
const database = options?.database || getDatabase();
const schemaInspector = SchemaInspector(database);
const result: SchemaOverview = {

View File

@@ -1,6 +1,6 @@
import axios from 'axios';
import { ListenerFn } from 'eventemitter2';
import database from './database';
import getDatabase from './database';
import emitter from './emitter';
import logger from './logger';
import { Webhook } from './types';
@@ -10,6 +10,8 @@ let registered: { event: string; handler: ListenerFn }[] = [];
export async function register(): Promise<void> {
unregister();
const database = getDatabase();
const webhooks = await database.select<Webhook[]>('*').from('directus_webhooks').where({ status: 'active' });
for (const webhook of webhooks) {

27
package-lock.json generated
View File

@@ -13076,6 +13076,7 @@
"version": "0.0.9",
"resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz",
"integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=",
"dev": true,
"optional": true,
"dependencies": {
"inherits": "~2.0.0"
@@ -21174,6 +21175,7 @@
"version": "1.0.12",
"resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz",
"integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==",
"dev": true,
"optional": true,
"dependencies": {
"graceful-fs": "^4.1.2",
@@ -21189,6 +21191,7 @@
"version": "0.5.5",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
"integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
"dev": true,
"optional": true,
"dependencies": {
"minimist": "^1.2.5"
@@ -21201,6 +21204,7 @@
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
"integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
"dev": true,
"optional": true,
"dependencies": {
"glob": "^7.1.3"
@@ -41963,6 +41967,7 @@
"version": "0.5.5",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
"integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
"dev": true,
"optional": true,
"dependencies": {
"minimist": "^1.2.5"
@@ -41975,6 +41980,7 @@
"version": "3.8.0",
"resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-3.8.0.tgz",
"integrity": "sha512-3g8lYefrRRzvGeSowdJKAKyks8oUpLEd/DyPV4eMhVlhJ0aNaZqIrNUIPuEWWTAoPqyFkfGrM67MC69baqn6vA==",
"dev": true,
"optional": true,
"dependencies": {
"fstream": "^1.0.0",
@@ -42001,6 +42007,7 @@
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz",
"integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=",
"dev": true,
"optional": true,
"dependencies": {
"abbrev": "1"
@@ -42013,6 +42020,7 @@
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
"integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
"dev": true,
"optional": true,
"dependencies": {
"glob": "^7.1.3"
@@ -42025,6 +42033,7 @@
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz",
"integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=",
"dev": true,
"optional": true,
"bin": {
"semver": "bin/semver"
@@ -42034,6 +42043,7 @@
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/tar/-/tar-2.2.2.tgz",
"integrity": "sha512-FCEhQ/4rE1zYv9rYXJw/msRqsnmlje5jHP6huWeBZ704jUTy02c5AZyWujpMR1ax6mVw9NyJMfuK2CMDWVIfgA==",
"dev": true,
"optional": true,
"dependencies": {
"block-stream": "*",
@@ -42045,6 +42055,7 @@
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
"integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
"dev": true,
"optional": true,
"dependencies": {
"isexe": "^2.0.0"
@@ -61062,6 +61073,7 @@
"resolved": "https://registry.npmjs.org/@oclif/command/-/command-1.8.0.tgz",
"integrity": "sha512-5vwpq6kbvwkQwKqAoOU3L72GZ3Ta8RRrewKj9OJRolx28KLJJ8Dg9Rf7obRwt5jQA9bkYd8gqzMTrI7H3xLfaw==",
"requires": {
"@oclif/config": "^1.15.1",
"@oclif/errors": "^1.3.3",
"@oclif/parser": "^3.8.3",
"@oclif/plugin-help": "^3",
@@ -63209,6 +63221,7 @@
"integrity": "sha512-pM7CR3yXB6L8Gfn6EmX7FLNE3+V/15I3o33GkSNsWvgsMp6HVGXKkXgojrcfUUauyL1LZOdvTmu4enU2RePGHw==",
"dev": true,
"requires": {
"@babel/core": "^7.11.0",
"@babel/helper-compilation-targets": "^7.9.6",
"@babel/helper-module-imports": "^7.8.3",
"@babel/plugin-proposal-class-properties": "^7.8.3",
@@ -63221,6 +63234,7 @@
"@vue/babel-plugin-jsx": "^1.0.3",
"@vue/babel-preset-jsx": "^1.2.4",
"babel-plugin-dynamic-import-node": "^2.3.3",
"core-js": "^3.6.5",
"core-js-compat": "^3.6.5",
"semver": "^6.1.0"
},
@@ -67288,6 +67302,7 @@
"version": "0.0.9",
"resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz",
"integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=",
"dev": true,
"optional": true,
"requires": {
"inherits": "~2.0.0"
@@ -71015,7 +71030,7 @@
"keyv": "^4.0.3",
"keyv-memcache": "^1.2.5",
"knex": "^0.95.6",
"knex-schema-inspector": "1.5.6",
"knex-schema-inspector": "^1.5.6",
"liquidjs": "^9.25.0",
"lodash": "^4.17.21",
"macos-release": "^2.4.1",
@@ -74027,6 +74042,7 @@
"version": "1.0.12",
"resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz",
"integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==",
"dev": true,
"optional": true,
"requires": {
"graceful-fs": "^4.1.2",
@@ -74039,6 +74055,7 @@
"version": "0.5.5",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
"integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
"dev": true,
"optional": true,
"requires": {
"minimist": "^1.2.5"
@@ -74048,6 +74065,7 @@
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
"integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
"dev": true,
"optional": true,
"requires": {
"glob": "^7.1.3"
@@ -90635,6 +90653,7 @@
"version": "0.5.5",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
"integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
"dev": true,
"optional": true,
"requires": {
"minimist": "^1.2.5"
@@ -90644,6 +90663,7 @@
"version": "3.8.0",
"resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-3.8.0.tgz",
"integrity": "sha512-3g8lYefrRRzvGeSowdJKAKyks8oUpLEd/DyPV4eMhVlhJ0aNaZqIrNUIPuEWWTAoPqyFkfGrM67MC69baqn6vA==",
"dev": true,
"optional": true,
"requires": {
"fstream": "^1.0.0",
@@ -90664,6 +90684,7 @@
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz",
"integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=",
"dev": true,
"optional": true,
"requires": {
"abbrev": "1"
@@ -90673,6 +90694,7 @@
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
"integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
"dev": true,
"optional": true,
"requires": {
"glob": "^7.1.3"
@@ -90682,12 +90704,14 @@
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz",
"integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=",
"dev": true,
"optional": true
},
"tar": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/tar/-/tar-2.2.2.tgz",
"integrity": "sha512-FCEhQ/4rE1zYv9rYXJw/msRqsnmlje5jHP6huWeBZ704jUTy02c5AZyWujpMR1ax6mVw9NyJMfuK2CMDWVIfgA==",
"dev": true,
"optional": true,
"requires": {
"block-stream": "*",
@@ -90699,6 +90723,7 @@
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
"integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
"dev": true,
"optional": true,
"requires": {
"isexe": "^2.0.0"