TS Config Modernization Program Part 3 of many (#17904)

* noImplicitOverride: true

* noImplicitReturns: true

* noPropertyAccessFromIndexSignature: true
This commit is contained in:
Rijk van Zanten
2023-03-23 16:47:55 -04:00
committed by GitHub
parent 8b0c5f1250
commit 80f4807a09
129 changed files with 1097 additions and 1017 deletions

View File

@@ -2,8 +2,8 @@
export const sqlFieldFormatter = (schema: Record<string, any>, table: string) => {
const fields = [];
// Exclude alias fields, unable to selected in DB
for (const field of Object.keys(schema.collections[table].fields)) {
if (schema.collections[table].fields[field].type !== 'alias') {
for (const field of Object.keys(schema['collections'][table].fields)) {
if (schema['collections'][table].fields[field].type !== 'alias') {
fields.push(field);
}
}
@@ -19,8 +19,8 @@ export const sqlFieldFormatter = (schema: Record<string, any>, table: string) =>
export const sqlFieldList = (schema: Record<string, any>, table: string) => {
const fields = [];
// Exclude alias fields, unable to selected in DB
for (const field of Object.keys(schema.collections[table].fields)) {
if (schema.collections[table].fields[field].type !== 'alias') {
for (const field of Object.keys(schema['collections'][table].fields)) {
if (schema['collections'][table].fields[field].type !== 'alias') {
fields.push(field);
}
}

View File

@@ -66,7 +66,7 @@ export default async function createApp(): Promise<express.Application> {
validateEnv(['KEY', 'SECRET']);
if (!new Url(env.PUBLIC_URL).isAbsolute()) {
if (!new Url(env['PUBLIC_URL']).isAbsolute()) {
logger.warn('PUBLIC_URL should be a full URL');
}
@@ -97,7 +97,7 @@ export default async function createApp(): Promise<express.Application> {
const app = express();
app.disable('x-powered-by');
app.set('trust proxy', env.IP_TRUST_PROXY);
app.set('trust proxy', env['IP_TRUST_PROXY']);
app.set('query parser', (str: string) => qs.parse(str, { depth: 10 }));
app.use(
@@ -128,7 +128,7 @@ export default async function createApp(): Promise<express.Application> {
)
);
if (env.HSTS_ENABLED) {
if (env['HSTS_ENABLED']) {
app.use(helmet.hsts(getConfigFromEnv('HSTS_', ['HSTS_ENABLED'])));
}
@@ -143,14 +143,14 @@ export default async function createApp(): Promise<express.Application> {
next();
});
if (env.CORS_ENABLED === true) {
if (env['CORS_ENABLED'] === true) {
app.use(cors);
}
app.use((req, res, next) => {
(
express.json({
limit: env.MAX_PAYLOAD_SIZE,
limit: env['MAX_PAYLOAD_SIZE'],
}) as RequestHandler
)(req, res, (err: any) => {
if (err) {
@@ -166,8 +166,8 @@ export default async function createApp(): Promise<express.Application> {
app.use(extractToken);
app.get('/', (_req, res, next) => {
if (env.ROOT_REDIRECT) {
res.redirect(env.ROOT_REDIRECT);
if (env['ROOT_REDIRECT']) {
res.redirect(env['ROOT_REDIRECT']);
} else {
next();
}
@@ -176,12 +176,12 @@ export default async function createApp(): Promise<express.Application> {
app.get('/robots.txt', (_, res) => {
res.set('Content-Type', 'text/plain');
res.status(200);
res.send(env.ROBOTS_TXT);
res.send(env['ROBOTS_TXT']);
});
if (env.SERVE_APP) {
if (env['SERVE_APP']) {
const adminPath = require.resolve('@directus/app');
const adminUrl = new Url(env.PUBLIC_URL).addPath('admin');
const adminUrl = new Url(env['PUBLIC_URL']).addPath('admin');
const embeds = extensionManager.getEmbeds();
@@ -209,10 +209,10 @@ export default async function createApp(): Promise<express.Application> {
}
// use the rate limiter - all routes for now
if (env.RATE_LIMITER_GLOBAL_ENABLED === true) {
if (env['RATE_LIMITER_GLOBAL_ENABLED'] === true) {
app.use(rateLimiterGlobal);
}
if (env.RATE_LIMITER_ENABLED === true) {
if (env['RATE_LIMITER_ENABLED'] === true) {
app.use(rateLimiter);
}

View File

@@ -10,7 +10,7 @@ import type { AuthDriverOptions } from './types';
import { getConfigFromEnv } from './utils/get-config-from-env';
import { getSchema } from './utils/get-schema';
const providerNames = toArray(env.AUTH_PROVIDERS);
const providerNames = toArray(env['AUTH_PROVIDERS']);
const providers: Map<string, AuthDriver> = new Map();
@@ -26,12 +26,12 @@ export async function registerAuthProviders(): Promise<void> {
const options = { knex: getDatabase(), schema: await getSchema() };
// Register default provider if not disabled
if (!env.AUTH_DISABLE_DEFAULT) {
if (!env['AUTH_DISABLE_DEFAULT']) {
const defaultProvider = getProviderInstance('local', options)!;
providers.set(DEFAULT_AUTH_PROVIDER, defaultProvider);
}
if (!env.AUTH_PROVIDERS) {
if (!env['AUTH_PROVIDERS']) {
return;
}
@@ -83,4 +83,6 @@ function getProviderInstance(
case 'saml':
return new SAMLAuthDriver(options, config);
}
return undefined;
}

View File

@@ -62,12 +62,12 @@ export class LDAPAuthDriver extends AuthDriver {
bindPassword === undefined ||
!userDn ||
!provider ||
(!clientUrl && !config.client?.socketPath)
(!clientUrl && !config['client']?.socketPath)
) {
throw new InvalidConfigException('Invalid provider config', { provider });
}
const clientConfig = typeof config.client === 'object' ? config.client : {};
const clientConfig = typeof config['client'] === 'object' ? config['client'] : {};
this.bindClient = ldap.createClient({ url: clientUrl, reconnect: true, ...clientConfig });
this.bindClient.on('error', (err: Error) => {
@@ -147,8 +147,8 @@ export class LDAPAuthDriver extends AuthDriver {
res.on('searchEntry', ({ object }: SearchEntry) => {
const user: UserInfo = {
dn: object.dn,
userAccountControl: Number(getEntryValue(object.userAccountControl) ?? 0),
dn: object['dn'],
userAccountControl: Number(getEntryValue(object['userAccountControl']) ?? 0),
};
const firstName = getEntryValue(object[firstNameAttribute]);
@@ -160,7 +160,7 @@ export class LDAPAuthDriver extends AuthDriver {
const email = getEntryValue(object[mailAttribute]);
if (email) user.email = email;
const uid = getEntryValue(object.uid);
const uid = getEntryValue(object['uid']);
if (uid) user.uid = uid;
resolve(user);
@@ -197,10 +197,10 @@ export class LDAPAuthDriver extends AuthDriver {
}
res.on('searchEntry', ({ object }: SearchEntry) => {
if (typeof object.cn === 'object') {
userGroups = [...userGroups, ...object.cn];
} else if (object.cn) {
userGroups.push(object.cn);
if (typeof object['cn'] === 'object') {
userGroups = [...userGroups, ...object['cn']];
} else if (object['cn']) {
userGroups.push(object['cn']);
}
});
@@ -227,7 +227,7 @@ export class LDAPAuthDriver extends AuthDriver {
}
async getUserID(payload: Record<string, any>): Promise<string> {
if (!payload.identifier) {
if (!payload['identifier']) {
throw new InvalidCredentialsException();
}
@@ -239,7 +239,7 @@ export class LDAPAuthDriver extends AuthDriver {
userDn,
new EqualityFilter({
attribute: userAttribute ?? 'cn',
value: payload.identifier,
value: payload['identifier'],
}),
userScope ?? 'one'
);
@@ -288,7 +288,7 @@ export class LDAPAuthDriver extends AuthDriver {
try {
await this.usersService.createOne({
provider: this.config.provider,
provider: this.config['provider'],
first_name: userInfo.firstName,
last_name: userInfo.lastName,
email: userInfo.email,
@@ -312,9 +312,9 @@ export class LDAPAuthDriver extends AuthDriver {
}
return new Promise((resolve, reject) => {
const clientConfig = typeof this.config.client === 'object' ? this.config.client : {};
const clientConfig = typeof this.config['client'] === 'object' ? this.config['client'] : {};
const client = ldap.createClient({
url: this.config.clientUrl,
url: this.config['clientUrl'],
...clientConfig,
reconnect: false,
});
@@ -334,11 +334,11 @@ export class LDAPAuthDriver extends AuthDriver {
});
}
async login(user: User, payload: Record<string, any>): Promise<void> {
await this.verify(user, payload.password);
override async login(user: User, payload: Record<string, any>): Promise<void> {
await this.verify(user, payload['password']);
}
async refresh(user: User): Promise<void> {
override async refresh(user: User): Promise<void> {
await this.validateBindClient();
const userInfo = await this.fetchUserInfo(user.external_identifier!);
@@ -415,20 +415,20 @@ export function createLDAPAuthRouter(provider: string): Router {
} as Record<string, Record<string, any>>;
if (mode === 'json') {
payload.data.refresh_token = refreshToken;
payload['data']['refresh_token'] = refreshToken;
}
if (mode === 'cookie') {
res.cookie(env.REFRESH_TOKEN_COOKIE_NAME, refreshToken, {
res.cookie(env['REFRESH_TOKEN_COOKIE_NAME'], refreshToken, {
httpOnly: true,
domain: env.REFRESH_TOKEN_COOKIE_DOMAIN,
maxAge: getMilliseconds(env.REFRESH_TOKEN_TTL),
secure: env.REFRESH_TOKEN_COOKIE_SECURE ?? false,
sameSite: (env.REFRESH_TOKEN_COOKIE_SAME_SITE as 'lax' | 'strict' | 'none') || 'strict',
domain: env['REFRESH_TOKEN_COOKIE_DOMAIN'],
maxAge: getMilliseconds(env['REFRESH_TOKEN_TTL']),
secure: env['REFRESH_TOKEN_COOKIE_SECURE'] ?? false,
sameSite: (env['REFRESH_TOKEN_COOKIE_SAME_SITE'] as 'lax' | 'strict' | 'none') || 'strict',
});
}
res.locals.payload = payload;
res.locals['payload'] = payload;
return next();
}),

View File

@@ -16,14 +16,14 @@ import { AuthDriver } from '../auth';
export class LocalAuthDriver extends AuthDriver {
async getUserID(payload: Record<string, any>): Promise<string> {
if (!payload.email) {
if (!payload['email']) {
throw new InvalidCredentialsException();
}
const user = await this.knex
.select('id')
.from('directus_users')
.whereRaw('LOWER(??) = ?', ['email', payload.email.toLowerCase()])
.whereRaw('LOWER(??) = ?', ['email', payload['email'].toLowerCase()])
.first();
if (!user) {
@@ -39,8 +39,8 @@ export class LocalAuthDriver extends AuthDriver {
}
}
async login(user: User, payload: Record<string, any>): Promise<void> {
await this.verify(user, payload.password);
override async login(user: User, payload: Record<string, any>): Promise<void> {
await this.verify(user, payload['password']);
}
}
@@ -57,7 +57,7 @@ export function createLocalAuthRouter(provider: string): Router {
router.post(
'/',
asyncHandler(async (req, res, next) => {
const STALL_TIME = env.LOGIN_STALL_TIME;
const STALL_TIME = env['LOGIN_STALL_TIME'];
const timeStart = performance.now();
const accountability: Accountability = {
@@ -96,14 +96,14 @@ export function createLocalAuthRouter(provider: string): Router {
} as Record<string, Record<string, any>>;
if (mode === 'json') {
payload.data.refresh_token = refreshToken;
payload['data']['refresh_token'] = refreshToken;
}
if (mode === 'cookie') {
res.cookie(env.REFRESH_TOKEN_COOKIE_NAME, refreshToken, COOKIE_OPTIONS);
res.cookie(env['REFRESH_TOKEN_COOKIE_NAME'], refreshToken, COOKIE_OPTIONS);
}
res.locals.payload = payload;
res.locals['payload'] = payload;
return next();
}),

View File

@@ -37,11 +37,11 @@ export class OAuth2AuthDriver extends LocalAuthDriver {
const { authorizeUrl, accessUrl, profileUrl, clientId, clientSecret, ...additionalConfig } = config;
if (!authorizeUrl || !accessUrl || !profileUrl || !clientId || !clientSecret || !additionalConfig.provider) {
throw new InvalidConfigException('Invalid provider config', { provider: additionalConfig.provider });
if (!authorizeUrl || !accessUrl || !profileUrl || !clientId || !clientSecret || !additionalConfig['provider']) {
throw new InvalidConfigException('Invalid provider config', { provider: additionalConfig['provider'] });
}
const redirectUrl = new Url(env.PUBLIC_URL).addPath('auth', 'login', additionalConfig.provider, 'callback');
const redirectUrl = new Url(env['PUBLIC_URL']).addPath('auth', 'login', additionalConfig['provider'], 'callback');
this.redirectUrl = redirectUrl.toString();
this.usersService = new UsersService({ knex: this.knex, schema: this.schema });
@@ -51,12 +51,12 @@ export class OAuth2AuthDriver extends LocalAuthDriver {
authorization_endpoint: authorizeUrl,
token_endpoint: accessUrl,
userinfo_endpoint: profileUrl,
issuer: additionalConfig.provider,
issuer: additionalConfig['provider'],
});
const clientOptionsOverrides = getConfigFromEnv(
`AUTH_${config.provider.toUpperCase()}_CLIENT_`,
[`AUTH_${config.provider.toUpperCase()}_CLIENT_ID`, `AUTH_${config.provider.toUpperCase()}_CLIENT_SECRET`],
`AUTH_${config['provider'].toUpperCase()}_CLIENT_`,
[`AUTH_${config['provider'].toUpperCase()}_CLIENT_ID`, `AUTH_${config['provider'].toUpperCase()}_CLIENT_SECRET`],
'underscore'
);
@@ -76,10 +76,10 @@ export class OAuth2AuthDriver extends LocalAuthDriver {
generateAuthUrl(codeVerifier: string, prompt = false): string {
try {
const codeChallenge = generators.codeChallenge(codeVerifier);
const paramsConfig = typeof this.config.params === 'object' ? this.config.params : {};
const paramsConfig = typeof this.config['params'] === 'object' ? this.config['params'] : {};
return this.client.authorizationUrl({
scope: this.config.scope ?? 'email',
scope: this.config['scope'] ?? 'email',
access_type: 'offline',
prompt: prompt ? 'consent' : undefined,
...paramsConfig,
@@ -103,8 +103,8 @@ export class OAuth2AuthDriver extends LocalAuthDriver {
return user?.id;
}
async getUserID(payload: Record<string, any>): Promise<string> {
if (!payload.code || !payload.codeVerifier || !payload.state) {
override async getUserID(payload: Record<string, any>): Promise<string> {
if (!payload['code'] || !payload['codeVerifier'] || !payload['state']) {
logger.warn('[OAuth2] No code, codeVerifier or state in payload');
throw new InvalidCredentialsException();
}
@@ -115,8 +115,8 @@ export class OAuth2AuthDriver extends LocalAuthDriver {
try {
tokenSet = await this.client.oauthCallback(
this.redirectUrl,
{ code: payload.code, state: payload.state },
{ code_verifier: payload.codeVerifier, state: generators.codeChallenge(payload.codeVerifier) }
{ code: payload['code'], state: payload['state'] },
{ code_verifier: payload['codeVerifier'], state: generators.codeChallenge(payload['codeVerifier']) }
);
userInfo = await this.client.userinfo(tokenSet.access_token!);
} catch (e) {
@@ -158,11 +158,11 @@ export class OAuth2AuthDriver extends LocalAuthDriver {
try {
await this.usersService.createOne({
provider,
first_name: userInfo[this.config.firstNameKey],
last_name: userInfo[this.config.lastNameKey],
first_name: userInfo[this.config['firstNameKey']],
last_name: userInfo[this.config['lastNameKey']],
email: email,
external_identifier: identifier,
role: this.config.defaultRoleId,
role: this.config['defaultRoleId'],
auth_data: tokenSet.refresh_token && JSON.stringify({ refreshToken: tokenSet.refresh_token }),
});
} catch (e) {
@@ -176,11 +176,11 @@ export class OAuth2AuthDriver extends LocalAuthDriver {
return (await this.fetchUserId(identifier)) as string;
}
async login(user: User): Promise<void> {
override async login(user: User): Promise<void> {
return this.refresh(user);
}
async refresh(user: User): Promise<void> {
override async refresh(user: User): Promise<void> {
let authData = user.auth_data as AuthData;
if (typeof authData === 'string') {
@@ -191,9 +191,9 @@ export class OAuth2AuthDriver extends LocalAuthDriver {
}
}
if (authData?.refreshToken) {
if (authData?.['refreshToken']) {
try {
const tokenSet = await this.client.refresh(authData.refreshToken);
const tokenSet = await this.client.refresh(authData['refreshToken']);
// Update user refreshToken if provided
if (tokenSet.refresh_token) {
await this.usersService.updateOne(user.id, {
@@ -239,11 +239,15 @@ export function createOAuth2AuthRouter(providerName: string): Router {
(req, res) => {
const provider = getAuthProvider(providerName) as OAuth2AuthDriver;
const codeVerifier = provider.generateCodeVerifier();
const prompt = !!req.query.prompt;
const token = jwt.sign({ verifier: codeVerifier, redirect: req.query.redirect, prompt }, env.SECRET as string, {
expiresIn: '5m',
issuer: 'directus',
});
const prompt = !!req.query['prompt'];
const token = jwt.sign(
{ verifier: codeVerifier, redirect: req.query['redirect'], prompt },
env['SECRET'] as string,
{
expiresIn: '5m',
issuer: 'directus',
}
);
res.cookie(`oauth2.${providerName}`, token, {
httpOnly: true,
@@ -269,7 +273,9 @@ export function createOAuth2AuthRouter(providerName: string): Router {
let tokenData;
try {
tokenData = jwt.verify(req.cookies[`oauth2.${providerName}`], env.SECRET as string, { issuer: 'directus' }) as {
tokenData = jwt.verify(req.cookies[`oauth2.${providerName}`], env['SECRET'] as string, {
issuer: 'directus',
}) as {
verifier: string;
redirect?: string;
prompt: boolean;
@@ -302,9 +308,9 @@ export function createOAuth2AuthRouter(providerName: string): Router {
try {
res.clearCookie(`oauth2.${providerName}`);
authResponse = await authenticationService.login(providerName, {
code: req.query.code,
code: req.query['code'],
codeVerifier: verifier,
state: req.query.state,
state: req.query['state'],
});
} catch (error: any) {
// Prompt user for a new refresh_token if invalidated
@@ -331,18 +337,18 @@ export function createOAuth2AuthRouter(providerName: string): Router {
const { accessToken, refreshToken, expires } = authResponse;
if (redirect) {
res.cookie(env.REFRESH_TOKEN_COOKIE_NAME, refreshToken, {
res.cookie(env['REFRESH_TOKEN_COOKIE_NAME'], refreshToken, {
httpOnly: true,
domain: env.REFRESH_TOKEN_COOKIE_DOMAIN,
maxAge: getMilliseconds(env.REFRESH_TOKEN_TTL),
secure: env.REFRESH_TOKEN_COOKIE_SECURE ?? false,
sameSite: (env.REFRESH_TOKEN_COOKIE_SAME_SITE as 'lax' | 'strict' | 'none') || 'strict',
domain: env['REFRESH_TOKEN_COOKIE_DOMAIN'],
maxAge: getMilliseconds(env['REFRESH_TOKEN_TTL']),
secure: env['REFRESH_TOKEN_COOKIE_SECURE'] ?? false,
sameSite: (env['REFRESH_TOKEN_COOKIE_SAME_SITE'] as 'lax' | 'strict' | 'none') || 'strict',
});
return res.redirect(redirect);
}
res.locals.payload = {
res.locals['payload'] = {
data: { access_token: accessToken, refresh_token: refreshToken, expires },
};

View File

@@ -37,15 +37,15 @@ export class OpenIDAuthDriver extends LocalAuthDriver {
const { issuerUrl, clientId, clientSecret, ...additionalConfig } = config;
if (!issuerUrl || !clientId || !clientSecret || !additionalConfig.provider) {
throw new InvalidConfigException('Invalid provider config', { provider: additionalConfig.provider });
if (!issuerUrl || !clientId || !clientSecret || !additionalConfig['provider']) {
throw new InvalidConfigException('Invalid provider config', { provider: additionalConfig['provider'] });
}
const redirectUrl = new Url(env.PUBLIC_URL).addPath('auth', 'login', additionalConfig.provider, 'callback');
const redirectUrl = new Url(env['PUBLIC_URL']).addPath('auth', 'login', additionalConfig['provider'], 'callback');
const clientOptionsOverrides = getConfigFromEnv(
`AUTH_${config.provider.toUpperCase()}_CLIENT_`,
[`AUTH_${config.provider.toUpperCase()}_CLIENT_ID`, `AUTH_${config.provider.toUpperCase()}_CLIENT_SECRET`],
`AUTH_${config['provider'].toUpperCase()}_CLIENT_`,
[`AUTH_${config['provider'].toUpperCase()}_CLIENT_ID`, `AUTH_${config['provider'].toUpperCase()}_CLIENT_SECRET`],
'underscore'
);
@@ -55,11 +55,11 @@ export class OpenIDAuthDriver extends LocalAuthDriver {
this.client = new Promise((resolve, reject) => {
Issuer.discover(issuerUrl)
.then((issuer) => {
const supportedTypes = issuer.metadata.response_types_supported as string[] | undefined;
const supportedTypes = issuer.metadata['response_types_supported'] as string[] | undefined;
if (!supportedTypes?.includes('code')) {
reject(
new InvalidConfigException('OpenID provider does not support required code flow', {
provider: additionalConfig.provider,
provider: additionalConfig['provider'],
})
);
}
@@ -89,10 +89,10 @@ export class OpenIDAuthDriver extends LocalAuthDriver {
try {
const client = await this.client;
const codeChallenge = generators.codeChallenge(codeVerifier);
const paramsConfig = typeof this.config.params === 'object' ? this.config.params : {};
const paramsConfig = typeof this.config['params'] === 'object' ? this.config['params'] : {};
return client.authorizationUrl({
scope: this.config.scope ?? 'openid profile email',
scope: this.config['scope'] ?? 'openid profile email',
access_type: 'offline',
prompt: prompt ? 'consent' : undefined,
...paramsConfig,
@@ -117,8 +117,8 @@ export class OpenIDAuthDriver extends LocalAuthDriver {
return user?.id;
}
async getUserID(payload: Record<string, any>): Promise<string> {
if (!payload.code || !payload.codeVerifier || !payload.state) {
override async getUserID(payload: Record<string, any>): Promise<string> {
if (!payload['code'] || !payload['codeVerifier'] || !payload['state']) {
logger.warn('[OpenID] No code, codeVerifier or state in payload');
throw new InvalidCredentialsException();
}
@@ -128,15 +128,15 @@ export class OpenIDAuthDriver extends LocalAuthDriver {
try {
const client = await this.client;
const codeChallenge = generators.codeChallenge(payload.codeVerifier);
const codeChallenge = generators.codeChallenge(payload['codeVerifier']);
tokenSet = await client.callback(
this.redirectUrl,
{ code: payload.code, state: payload.state },
{ code_verifier: payload.codeVerifier, state: codeChallenge, nonce: codeChallenge }
{ code: payload['code'], state: payload['state'] },
{ code_verifier: payload['codeVerifier'], state: codeChallenge, nonce: codeChallenge }
);
userInfo = tokenSet.claims();
if (client.issuer.metadata.userinfo_endpoint) {
if (client.issuer.metadata['userinfo_endpoint']) {
userInfo = {
...userInfo,
...(await client.userinfo(tokenSet.access_token!)),
@@ -151,7 +151,7 @@ export class OpenIDAuthDriver extends LocalAuthDriver {
const { provider, identifierKey, allowPublicRegistration, requireVerifiedEmail } = this.config;
const email = userInfo.email ? String(userInfo.email) : undefined;
const email = userInfo['email'] ? String(userInfo['email']) : undefined;
// Fallback to email if explicit identifier not found
const identifier = userInfo[identifierKey ?? 'sub'] ? String(userInfo[identifierKey ?? 'sub']) : email;
@@ -172,7 +172,7 @@ export class OpenIDAuthDriver extends LocalAuthDriver {
return userId;
}
const isEmailVerified = !requireVerifiedEmail || userInfo.email_verified;
const isEmailVerified = !requireVerifiedEmail || userInfo['email_verified'];
// Is public registration allowed?
if (!allowPublicRegistration || !isEmailVerified) {
@@ -183,11 +183,11 @@ export class OpenIDAuthDriver extends LocalAuthDriver {
try {
await this.usersService.createOne({
provider,
first_name: userInfo.given_name,
last_name: userInfo.family_name,
first_name: userInfo['given_name'],
last_name: userInfo['family_name'],
email: email,
external_identifier: identifier,
role: this.config.defaultRoleId,
role: this.config['defaultRoleId'],
auth_data: tokenSet.refresh_token && JSON.stringify({ refreshToken: tokenSet.refresh_token }),
});
} catch (e) {
@@ -201,11 +201,11 @@ export class OpenIDAuthDriver extends LocalAuthDriver {
return (await this.fetchUserId(identifier)) as string;
}
async login(user: User): Promise<void> {
override async login(user: User): Promise<void> {
return this.refresh(user);
}
async refresh(user: User): Promise<void> {
override async refresh(user: User): Promise<void> {
let authData = user.auth_data as AuthData;
if (typeof authData === 'string') {
@@ -216,10 +216,10 @@ export class OpenIDAuthDriver extends LocalAuthDriver {
}
}
if (authData?.refreshToken) {
if (authData?.['refreshToken']) {
try {
const client = await this.client;
const tokenSet = await client.refresh(authData.refreshToken);
const tokenSet = await client.refresh(authData['refreshToken']);
// Update user refreshToken if provided
if (tokenSet.refresh_token) {
await this.usersService.updateOne(user.id, {
@@ -265,11 +265,15 @@ export function createOpenIDAuthRouter(providerName: string): Router {
asyncHandler(async (req, res) => {
const provider = getAuthProvider(providerName) as OpenIDAuthDriver;
const codeVerifier = provider.generateCodeVerifier();
const prompt = !!req.query.prompt;
const token = jwt.sign({ verifier: codeVerifier, redirect: req.query.redirect, prompt }, env.SECRET as string, {
expiresIn: '5m',
issuer: 'directus',
});
const prompt = !!req.query['prompt'];
const token = jwt.sign(
{ verifier: codeVerifier, redirect: req.query['redirect'], prompt },
env['SECRET'] as string,
{
expiresIn: '5m',
issuer: 'directus',
}
);
res.cookie(`openid.${providerName}`, token, {
httpOnly: true,
@@ -296,7 +300,9 @@ export function createOpenIDAuthRouter(providerName: string): Router {
let tokenData;
try {
tokenData = jwt.verify(req.cookies[`openid.${providerName}`], env.SECRET as string, { issuer: 'directus' }) as {
tokenData = jwt.verify(req.cookies[`openid.${providerName}`], env['SECRET'] as string, {
issuer: 'directus',
}) as {
verifier: string;
redirect?: string;
prompt: boolean;
@@ -329,9 +335,9 @@ export function createOpenIDAuthRouter(providerName: string): Router {
try {
res.clearCookie(`openid.${providerName}`);
authResponse = await authenticationService.login(providerName, {
code: req.query.code,
code: req.query['code'],
codeVerifier: verifier,
state: req.query.state,
state: req.query['state'],
});
} catch (error: any) {
// Prompt user for a new refresh_token if invalidated
@@ -360,18 +366,18 @@ export function createOpenIDAuthRouter(providerName: string): Router {
const { accessToken, refreshToken, expires } = authResponse;
if (redirect) {
res.cookie(env.REFRESH_TOKEN_COOKIE_NAME, refreshToken, {
res.cookie(env['REFRESH_TOKEN_COOKIE_NAME'], refreshToken, {
httpOnly: true,
domain: env.REFRESH_TOKEN_COOKIE_DOMAIN,
maxAge: getMilliseconds(env.REFRESH_TOKEN_TTL),
secure: env.REFRESH_TOKEN_COOKIE_SECURE ?? false,
sameSite: (env.REFRESH_TOKEN_COOKIE_SAME_SITE as 'lax' | 'strict' | 'none') || 'strict',
domain: env['REFRESH_TOKEN_COOKIE_DOMAIN'],
maxAge: getMilliseconds(env['REFRESH_TOKEN_TTL']),
secure: env['REFRESH_TOKEN_COOKIE_SECURE'] ?? false,
sameSite: (env['REFRESH_TOKEN_COOKIE_SAME_SITE'] as 'lax' | 'strict' | 'none') || 'strict',
});
return res.redirect(redirect);
}
res.locals.payload = {
res.locals['payload'] = {
data: { access_token: accessToken, refresh_token: refreshToken, expires },
};

View File

@@ -30,8 +30,8 @@ export class SAMLAuthDriver extends LocalAuthDriver {
this.config = config;
this.usersService = new UsersService({ knex: this.knex, schema: this.schema });
this.sp = samlify.ServiceProvider(getConfigFromEnv(`AUTH_${config.provider.toUpperCase()}_SP`));
this.idp = samlify.IdentityProvider(getConfigFromEnv(`AUTH_${config.provider.toUpperCase()}_IDP`));
this.sp = samlify.ServiceProvider(getConfigFromEnv(`AUTH_${config['provider'].toUpperCase()}_SP`));
this.idp = samlify.IdentityProvider(getConfigFromEnv(`AUTH_${config['provider'].toUpperCase()}_IDP`));
}
async fetchUserID(identifier: string) {
@@ -44,7 +44,7 @@ export class SAMLAuthDriver extends LocalAuthDriver {
return user?.id;
}
async getUserID(payload: Record<string, any>) {
override async getUserID(payload: Record<string, any>) {
const { provider, emailKey, identifierKey, givenNameKey, familyNameKey, allowPublicRegistration } = this.config;
const email = payload[emailKey ?? 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress'];
@@ -69,7 +69,7 @@ export class SAMLAuthDriver extends LocalAuthDriver {
last_name: lastName,
email: email,
external_identifier: identifier.toLowerCase(),
role: this.config.defaultRoleId,
role: this.config['defaultRoleId'],
});
} catch (error) {
if (error instanceof RecordNotUniqueException) {
@@ -82,7 +82,7 @@ export class SAMLAuthDriver extends LocalAuthDriver {
}
// There's no local checks to be done when the user is authenticated in the IDP
async login(_user: User): Promise<void> {
override async login(_user: User): Promise<void> {
return;
}
}
@@ -105,8 +105,8 @@ export function createSAMLAuthRouter(providerName: string) {
const { context: url } = await sp.createLoginRequest(idp, 'redirect');
const parsedUrl = new URL(url);
if (req.query.redirect) {
parsedUrl.searchParams.append('RelayState', req.query.redirect as string);
if (req.query['redirect']) {
parsedUrl.searchParams.append('RelayState', req.query['redirect'] as string);
}
return res.redirect(parsedUrl.toString());
@@ -121,12 +121,12 @@ export function createSAMLAuthRouter(providerName: string) {
const authService = new AuthenticationService({ accountability: req.accountability, schema: req.schema });
if (req.cookies[env.REFRESH_TOKEN_COOKIE_NAME]) {
const currentRefreshToken = req.cookies[env.REFRESH_TOKEN_COOKIE_NAME];
if (req.cookies[env['REFRESH_TOKEN_COOKIE_NAME']]) {
const currentRefreshToken = req.cookies[env['REFRESH_TOKEN_COOKIE_NAME']];
if (currentRefreshToken) {
await authService.logout(currentRefreshToken);
res.clearCookie(env.REFRESH_TOKEN_COOKIE_NAME, COOKIE_OPTIONS);
res.clearCookie(env['REFRESH_TOKEN_COOKIE_NAME'], COOKIE_OPTIONS);
}
}
@@ -147,7 +147,7 @@ export function createSAMLAuthRouter(providerName: string) {
const authService = new AuthenticationService({ accountability: req.accountability, schema: req.schema });
const { accessToken, refreshToken, expires } = await authService.login(providerName, extract.attributes);
res.locals.payload = {
res.locals['payload'] = {
data: {
access_token: accessToken,
refresh_token: refreshToken,
@@ -156,7 +156,7 @@ export function createSAMLAuthRouter(providerName: string) {
};
if (relayState) {
res.cookie(env.REFRESH_TOKEN_COOKIE_NAME, refreshToken, COOKIE_OPTIONS);
res.cookie(env['REFRESH_TOKEN_COOKIE_NAME'], refreshToken, COOKIE_OPTIONS);
return res.redirect(relayState);
}

View File

@@ -20,11 +20,16 @@ type Store = 'memory' | 'redis' | 'memcache';
const messenger = getMessenger();
if (env.MESSENGER_STORE === 'redis' && env.CACHE_STORE === 'memory' && env.CACHE_AUTO_PURGE && !messengerSubscribed) {
if (
env['MESSENGER_STORE'] === 'redis' &&
env['CACHE_STORE'] === 'memory' &&
env['CACHE_AUTO_PURGE'] &&
!messengerSubscribed
) {
messengerSubscribed = true;
messenger.subscribe('schemaChanged', async (opts) => {
if (cache && opts?.autoPurgeCache !== false) {
if (cache && opts?.['autoPurgeCache'] !== false) {
await cache.clear();
}
});
@@ -37,29 +42,29 @@ export function getCache(): {
localSchemaCache: Keyv;
lockCache: Keyv;
} {
if (env.CACHE_ENABLED === true && cache === null) {
if (env['CACHE_ENABLED'] === true && cache === null) {
validateEnv(['CACHE_NAMESPACE', 'CACHE_TTL', 'CACHE_STORE']);
cache = getKeyvInstance(env.CACHE_STORE, getMilliseconds(env.CACHE_TTL));
cache = getKeyvInstance(env['CACHE_STORE'], getMilliseconds(env['CACHE_TTL']));
cache.on('error', (err) => logger.warn(err, `[cache] ${err}`));
}
if (systemCache === null) {
systemCache = getKeyvInstance(env.CACHE_STORE, getMilliseconds(env.CACHE_SYSTEM_TTL), '_system');
systemCache = getKeyvInstance(env['CACHE_STORE'], getMilliseconds(env['CACHE_SYSTEM_TTL']), '_system');
systemCache.on('error', (err) => logger.warn(err, `[system-cache] ${err}`));
}
if (sharedSchemaCache === null) {
sharedSchemaCache = getKeyvInstance(env.CACHE_STORE, getMilliseconds(env.CACHE_SYSTEM_TTL), '_schema_shared');
sharedSchemaCache = getKeyvInstance(env['CACHE_STORE'], getMilliseconds(env['CACHE_SYSTEM_TTL']), '_schema_shared');
sharedSchemaCache.on('error', (err) => logger.warn(err, `[shared-schema-cache] ${err}`));
}
if (localSchemaCache === null) {
localSchemaCache = getKeyvInstance('memory', getMilliseconds(env.CACHE_SYSTEM_TTL), '_schema');
localSchemaCache = getKeyvInstance('memory', getMilliseconds(env['CACHE_SYSTEM_TTL']), '_schema');
localSchemaCache.on('error', (err) => logger.warn(err, `[schema-cache] ${err}`));
}
if (lockCache === null) {
lockCache = getKeyvInstance(env.CACHE_STORE, undefined, '_lock');
lockCache = getKeyvInstance(env['CACHE_STORE'], undefined, '_lock');
lockCache.on('error', (err) => logger.warn(err, `[lock-cache] ${err}`));
}
@@ -156,14 +161,14 @@ function getKeyvInstance(store: Store, ttl: number | undefined, namespaceSuffix?
function getConfig(store: Store = 'memory', ttl: number | undefined, namespaceSuffix = ''): Options<any> {
const config: Options<any> = {
namespace: `${env.CACHE_NAMESPACE}${namespaceSuffix}`,
namespace: `${env['CACHE_NAMESPACE']}${namespaceSuffix}`,
ttl,
};
if (store === 'redis') {
const KeyvRedis = require('@keyv/redis');
config.store = new KeyvRedis(env.CACHE_REDIS || getConfigFromEnv('CACHE_REDIS_'));
config.store = new KeyvRedis(env['CACHE_REDIS'] || getConfigFromEnv('CACHE_REDIS_'));
}
if (store === 'memcache') {
@@ -171,7 +176,9 @@ function getConfig(store: Store = 'memory', ttl: number | undefined, namespaceSu
// keyv-memcache uses memjs which only accepts a comma separated string instead of an array,
// so we need to join array into a string when applicable. See #7986
const cacheMemcache = Array.isArray(env.CACHE_MEMCACHE) ? env.CACHE_MEMCACHE.join(',') : env.CACHE_MEMCACHE;
const cacheMemcache = Array.isArray(env['CACHE_MEMCACHE'])
? env['CACHE_MEMCACHE'].join(',')
: env['CACHE_MEMCACHE'];
config.store = new KeyvMemcache(cacheMemcache);
}

View File

@@ -32,9 +32,9 @@ export default async function bootstrap({ skipAdminInit }: { skipAdminInit?: boo
logger.info('Skipping creation of default Admin user and role...');
}
if (env.PROJECT_NAME && typeof env.PROJECT_NAME === 'string' && env.PROJECT_NAME.length > 0) {
if (env['PROJECT_NAME'] && typeof env['PROJECT_NAME'] === 'string' && env['PROJECT_NAME'].length > 0) {
const settingsService = new SettingsService({ schema });
await settingsService.upsertSingleton({ project_name: env.PROJECT_NAME });
await settingsService.upsertSingleton({ project_name: env['PROJECT_NAME'] });
}
} else {
logger.info('Database already initialized, skipping install');
@@ -60,6 +60,8 @@ async function waitForDatabase(database: Knex) {
// This will throw and exit the process if the database is not available
await validateDatabaseConnection(database);
return database;
}
async function createDefaultAdmin(schema: SchemaOverview) {
@@ -72,14 +74,14 @@ async function createDefaultAdmin(schema: SchemaOverview) {
logger.info('Adding first admin user...');
const usersService = new UsersService({ schema });
let adminEmail = env.ADMIN_EMAIL;
let adminEmail = env['ADMIN_EMAIL'];
if (!adminEmail) {
logger.info('No admin email provided. Defaulting to "admin@example.com"');
adminEmail = 'admin@example.com';
}
let adminPassword = env.ADMIN_PASSWORD;
let adminPassword = env['ADMIN_PASSWORD'];
if (!adminPassword) {
adminPassword = nanoid(12);

View File

@@ -35,7 +35,7 @@ export default function createDBConnection(client: Driver, credentials: Credenti
if (client === 'pg' || client === 'cockroachdb') {
const { ssl } = credentials as Credentials;
connection['ssl'] = ssl;
connection.ssl = ssl;
}
if (client === 'mssql') {

View File

@@ -33,7 +33,7 @@ export default async function createEnv(
};
for (const [key, value] of Object.entries(credentials)) {
config.database[`DB_${key.toUpperCase()}`] = value;
config['database'][`DB_${key.toUpperCase()}`] = value;
}
const configAsStrings: any = {};

View File

@@ -55,10 +55,10 @@ export const UUID_REGEX = '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a
export const COOKIE_OPTIONS: CookieOptions = {
httpOnly: true,
domain: env.REFRESH_TOKEN_COOKIE_DOMAIN,
maxAge: getMilliseconds(env.REFRESH_TOKEN_TTL),
secure: env.REFRESH_TOKEN_COOKIE_SECURE ?? false,
sameSite: (env.REFRESH_TOKEN_COOKIE_SAME_SITE as 'lax' | 'strict' | 'none') || 'strict',
domain: env['REFRESH_TOKEN_COOKIE_DOMAIN'],
maxAge: getMilliseconds(env['REFRESH_TOKEN_TTL']),
secure: env['REFRESH_TOKEN_COOKIE_SECURE'] ?? false,
sameSite: (env['REFRESH_TOKEN_COOKIE_SAME_SITE'] as 'lax' | 'strict' | 'none') || 'strict',
};
export const OAS_REQUIRED_SCHEMAS = ['Diff', 'Schema', 'Query', 'x-metadata'];

View File

@@ -36,7 +36,7 @@ const readHandler = asyncHandler(async (req, res, next) => {
const meta = await metaService.getMetaForQuery('directus_activity', req.sanitizedQuery);
res.locals.payload = {
res.locals['payload'] = {
data: result,
meta,
};
@@ -55,9 +55,9 @@ router.get(
schema: req.schema,
});
const record = await service.readOne(req.params.pk, req.sanitizedQuery);
const record = await service.readOne(req.params['pk'], req.sanitizedQuery);
res.locals.payload = {
res.locals['payload'] = {
data: record || null,
};
@@ -98,7 +98,7 @@ router.post(
try {
const record = await service.readOne(primaryKey, req.sanitizedQuery);
res.locals.payload = {
res.locals['payload'] = {
data: record || null,
};
} catch (error: any) {
@@ -132,12 +132,12 @@ router.patch(
throw new InvalidPayloadException(error.message);
}
const primaryKey = await service.updateOne(req.params.pk, req.body);
const primaryKey = await service.updateOne(req.params['pk'], req.body);
try {
const record = await service.readOne(primaryKey, req.sanitizedQuery);
res.locals.payload = {
res.locals['payload'] = {
data: record || null,
};
} catch (error: any) {
@@ -165,13 +165,13 @@ router.delete(
schema: req.schema,
});
const item = await adminService.readOne(req.params.pk, { fields: ['action'] });
const item = await adminService.readOne(req.params['pk'], { fields: ['action'] });
if (!item || item.action !== 'comment') {
if (!item || item['action'] !== 'comment') {
throw new ForbiddenException();
}
await service.deleteOne(req.params.pk);
await service.deleteOne(req.params['pk']);
return next();
}),

View File

@@ -62,9 +62,9 @@ router.get(
}
// Check against ASSETS_TRANSFORM_MAX_OPERATIONS
if (transforms.length > Number(env.ASSETS_TRANSFORM_MAX_OPERATIONS)) {
if (transforms.length > Number(env['ASSETS_TRANSFORM_MAX_OPERATIONS'])) {
throw new InvalidQueryException(
`"transforms" Parameter is only allowed ${env.ASSETS_TRANSFORM_MAX_OPERATIONS} transformations.`
`"transforms" Parameter is only allowed ${env['ASSETS_TRANSFORM_MAX_OPERATIONS']} transformations.`
);
}
@@ -77,37 +77,39 @@ router.get(
}
});
transformation.transforms = transforms;
transformation['transforms'] = transforms;
}
const systemKeys = SYSTEM_ASSET_ALLOW_LIST.map((transformation) => transformation.key!);
const systemKeys = SYSTEM_ASSET_ALLOW_LIST.map((transformation) => transformation['key']!);
const allKeys: string[] = [
...systemKeys,
...(assetSettings.storage_asset_presets || []).map((transformation: TransformationParams) => transformation.key),
...(assetSettings.storage_asset_presets || []).map(
(transformation: TransformationParams) => transformation['key']
),
];
// For use in the next request handler
res.locals.shortcuts = [...SYSTEM_ASSET_ALLOW_LIST, ...(assetSettings.storage_asset_presets || [])];
res.locals.transformation = transformation;
res.locals['shortcuts'] = [...SYSTEM_ASSET_ALLOW_LIST, ...(assetSettings.storage_asset_presets || [])];
res.locals['transformation'] = transformation;
if (
Object.keys(transformation).length === 0 ||
('transforms' in transformation && transformation.transforms!.length === 0)
('transforms' in transformation && transformation['transforms']!.length === 0)
) {
return next();
}
if (assetSettings.storage_asset_transform === 'all') {
if (transformation.key && allKeys.includes(transformation.key as string) === false) {
throw new InvalidQueryException(`Key "${transformation.key}" isn't configured.`);
if (transformation['key'] && allKeys.includes(transformation['key'] as string) === false) {
throw new InvalidQueryException(`Key "${transformation['key']}" isn't configured.`);
}
return next();
} else if (assetSettings.storage_asset_transform === 'presets') {
if (allKeys.includes(transformation.key as string)) return next();
if (allKeys.includes(transformation['key'] as string)) return next();
throw new InvalidQueryException(`Only configured presets can be used in asset generation.`);
} else {
if (transformation.key && systemKeys.includes(transformation.key as string)) return next();
if (transformation['key'] && systemKeys.includes(transformation['key'] as string)) return next();
throw new InvalidQueryException(`Dynamic asset generation has been disabled for this project.`);
}
}),
@@ -130,18 +132,18 @@ router.get(
// Return file
asyncHandler(async (req, res) => {
const id = req.params.pk?.substring(0, 36);
const id = req.params['pk']?.substring(0, 36);
const service = new AssetsService({
accountability: req.accountability,
schema: req.schema,
});
const transformation: TransformationParams | TransformationPreset = res.locals.transformation.key
? (res.locals.shortcuts as TransformationPreset[]).find(
(transformation) => transformation.key === res.locals.transformation.key
const transformation: TransformationParams | TransformationPreset = res.locals['transformation'].key
? (res.locals['shortcuts'] as TransformationPreset[]).find(
(transformation) => transformation['key'] === res.locals['transformation'].key
)
: res.locals.transformation;
: res.locals['transformation'];
let range: Range | undefined = undefined;
@@ -165,10 +167,10 @@ router.get(
const { stream, file, stat } = await service.getAsset(id, transformation, range);
res.attachment(req.params.filename ?? file.filename_download);
res.attachment(req.params['filename'] ?? file.filename_download);
res.setHeader('Content-Type', file.type);
res.setHeader('Accept-Ranges', 'bytes');
res.setHeader('Cache-Control', getCacheControlHeader(req, getMilliseconds(env.ASSETS_CACHE_TTL), false, true));
res.setHeader('Cache-Control', getCacheControlHeader(req, getMilliseconds(env['ASSETS_CACHE_TTL']), false, true));
const unixTime = Date.parse(file.modified_on);
if (!Number.isNaN(unixTime)) {
@@ -227,6 +229,8 @@ router.get(
});
}
});
return undefined;
})
);

View File

@@ -54,7 +54,7 @@ for (const authProvider of authProviders) {
router.use(`/login/${authProvider.name}`, authRouter);
}
if (!env.AUTH_DISABLE_DEFAULT) {
if (!env['AUTH_DISABLE_DEFAULT']) {
router.use('/login', createLocalAuthRouter(DEFAULT_AUTH_PROVIDER));
}
@@ -77,7 +77,7 @@ router.post(
schema: req.schema,
});
const currentRefreshToken = req.body.refresh_token || req.cookies[env.REFRESH_TOKEN_COOKIE_NAME];
const currentRefreshToken = req.body.refresh_token || req.cookies[env['REFRESH_TOKEN_COOKIE_NAME']];
if (!currentRefreshToken) {
throw new InvalidPayloadException(`"refresh_token" is required in either the JSON payload or Cookie`);
@@ -92,14 +92,14 @@ router.post(
} as Record<string, Record<string, any>>;
if (mode === 'json') {
payload.data.refresh_token = refreshToken;
payload['data']['refresh_token'] = refreshToken;
}
if (mode === 'cookie') {
res.cookie(env.REFRESH_TOKEN_COOKIE_NAME, refreshToken, COOKIE_OPTIONS);
res.cookie(env['REFRESH_TOKEN_COOKIE_NAME'], refreshToken, COOKIE_OPTIONS);
}
res.locals.payload = payload;
res.locals['payload'] = payload;
return next();
}),
respond
@@ -124,7 +124,7 @@ router.post(
schema: req.schema,
});
const currentRefreshToken = req.body.refresh_token || req.cookies[env.REFRESH_TOKEN_COOKIE_NAME];
const currentRefreshToken = req.body.refresh_token || req.cookies[env['REFRESH_TOKEN_COOKIE_NAME']];
if (!currentRefreshToken) {
throw new InvalidPayloadException(`"refresh_token" is required in either the JSON payload or Cookie`);
@@ -132,12 +132,12 @@ router.post(
await authenticationService.logout(currentRefreshToken);
if (req.cookies[env.REFRESH_TOKEN_COOKIE_NAME]) {
res.clearCookie(env.REFRESH_TOKEN_COOKIE_NAME, {
if (req.cookies[env['REFRESH_TOKEN_COOKIE_NAME']]) {
res.clearCookie(env['REFRESH_TOKEN_COOKIE_NAME'], {
httpOnly: true,
domain: env.REFRESH_TOKEN_COOKIE_DOMAIN,
secure: env.REFRESH_TOKEN_COOKIE_SECURE ?? false,
sameSite: (env.REFRESH_TOKEN_COOKIE_SAME_SITE as 'lax' | 'strict' | 'none') || 'strict',
domain: env['REFRESH_TOKEN_COOKIE_DOMAIN'],
secure: env['REFRESH_TOKEN_COOKIE_SECURE'] ?? false,
sameSite: (env['REFRESH_TOKEN_COOKIE_SAME_SITE'] as 'lax' | 'strict' | 'none') || 'strict',
});
}
@@ -213,9 +213,9 @@ router.post(
router.get(
'/',
asyncHandler(async (req, res, next) => {
res.locals.payload = {
res.locals['payload'] = {
data: getAuthProviders(),
disableDefault: env.AUTH_DISABLE_DEFAULT,
disableDefault: env['AUTH_DISABLE_DEFAULT'],
};
return next();
}),

View File

@@ -19,11 +19,11 @@ router.post(
if (Array.isArray(req.body)) {
const collectionKey = await collectionsService.createMany(req.body);
const records = await collectionsService.readMany(collectionKey);
res.locals.payload = { data: records || null };
res.locals['payload'] = { data: records || null };
} else {
const collectionKey = await collectionsService.createOne(req.body);
const record = await collectionsService.readOne(collectionKey);
res.locals.payload = { data: record || null };
res.locals['payload'] = { data: record || null };
}
return next();
@@ -52,7 +52,7 @@ const readHandler = asyncHandler(async (req, res, next) => {
const meta = await metaService.getMetaForQuery('directus_collections', {});
res.locals.payload = { data: result, meta };
res.locals['payload'] = { data: result, meta };
return next();
});
@@ -67,8 +67,8 @@ router.get(
schema: req.schema,
});
const collection = await collectionsService.readOne(req.params.collection);
res.locals.payload = { data: collection || null };
const collection = await collectionsService.readOne(req.params['collection']);
res.locals['payload'] = { data: collection || null };
return next();
}),
@@ -87,7 +87,7 @@ router.patch(
try {
const collections = await collectionsService.readMany(collectionKeys);
res.locals.payload = { data: collections || null };
res.locals['payload'] = { data: collections || null };
} catch (error: any) {
if (error instanceof ForbiddenException) {
return next();
@@ -109,11 +109,11 @@ router.patch(
schema: req.schema,
});
await collectionsService.updateOne(req.params.collection, req.body);
await collectionsService.updateOne(req.params['collection'], req.body);
try {
const collection = await collectionsService.readOne(req.params.collection);
res.locals.payload = { data: collection || null };
const collection = await collectionsService.readOne(req.params['collection']);
res.locals['payload'] = { data: collection || null };
} catch (error: any) {
if (error instanceof ForbiddenException) {
return next();
@@ -135,7 +135,7 @@ router.delete(
schema: req.schema,
});
await collectionsService.deleteOne(req.params.collection);
await collectionsService.deleteOne(req.params['collection']);
return next();
}),

View File

@@ -33,10 +33,10 @@ router.post(
try {
if (Array.isArray(req.body)) {
const items = await service.readMany(savedKeys, req.sanitizedQuery);
res.locals.payload = { data: items };
res.locals['payload'] = { data: items };
} else {
const item = await service.readOne(savedKeys[0], req.sanitizedQuery);
res.locals.payload = { data: item };
res.locals['payload'] = { data: item };
}
} catch (error) {
if (error instanceof ForbiddenException) {
@@ -64,7 +64,7 @@ const readHandler = asyncHandler(async (req, res, next) => {
const records = await service.readByQuery(req.sanitizedQuery);
const meta = await metaService.getMetaForQuery(req.collection, req.sanitizedQuery);
res.locals.payload = { data: records || null, meta };
res.locals['payload'] = { data: records || null, meta };
return next();
});
@@ -79,9 +79,9 @@ router.get(
schema: req.schema,
});
const record = await service.readOne(req.params.pk, req.sanitizedQuery);
const record = await service.readOne(req.params['pk'], req.sanitizedQuery);
res.locals.payload = { data: record || null };
res.locals['payload'] = { data: record || null };
return next();
}),
respond
@@ -109,7 +109,7 @@ router.patch(
try {
const result = await service.readMany(keys, req.sanitizedQuery);
res.locals.payload = { data: result };
res.locals['payload'] = { data: result };
} catch (error) {
if (error instanceof ForbiddenException) {
return next();
@@ -131,11 +131,11 @@ router.patch(
schema: req.schema,
});
const primaryKey = await service.updateOne(req.params.pk, req.body);
const primaryKey = await service.updateOne(req.params['pk'], req.body);
try {
const item = await service.readOne(primaryKey, req.sanitizedQuery);
res.locals.payload = { data: item || null };
res.locals['payload'] = { data: item || null };
} catch (error) {
if (error instanceof ForbiddenException) {
return next();
@@ -179,7 +179,7 @@ router.delete(
schema: req.schema,
});
await service.deleteOne(req.params.pk);
await service.deleteOne(req.params['pk']);
return next();
}),

View File

@@ -15,7 +15,7 @@ const router = Router();
router.get(
'/:type',
asyncHandler(async (req, res, next) => {
const type = depluralize(req.params.type as Plural<string>);
const type = depluralize(req.params['type'] as Plural<string>);
if (!isIn(type, EXTENSION_TYPES)) {
throw new RouteNotFoundException(req.path);
@@ -25,7 +25,7 @@ router.get(
const extensions = extensionManager.getExtensionsList(type);
res.locals.payload = {
res.locals['payload'] = {
data: extensions,
};
@@ -45,7 +45,10 @@ router.get(
}
res.setHeader('Content-Type', 'application/javascript; charset=UTF-8');
res.setHeader('Cache-Control', getCacheControlHeader(req, getMilliseconds(env.EXTENSIONS_CACHE_TTL), false, false));
res.setHeader(
'Cache-Control',
getCacheControlHeader(req, getMilliseconds(env['EXTENSIONS_CACHE_TTL']), false, false)
);
res.setHeader('Vary', 'Origin, Cache-Control');
res.end(extensionSource);
})

View File

@@ -23,7 +23,7 @@ router.get(
});
const fields = await service.readAll();
res.locals.payload = { data: fields || null };
res.locals['payload'] = { data: fields || null };
return next();
}),
respond
@@ -37,9 +37,9 @@ router.get(
accountability: req.accountability,
schema: req.schema,
});
const fields = await service.readAll(req.params.collection);
const fields = await service.readAll(req.params['collection']);
res.locals.payload = { data: fields || null };
res.locals['payload'] = { data: fields || null };
return next();
}),
respond
@@ -54,9 +54,9 @@ router.get(
schema: req.schema,
});
const field = await service.readOne(req.params.collection, req.params.field);
const field = await service.readOne(req.params['collection'], req.params['field']);
res.locals.payload = { data: field || null };
res.locals['payload'] = { data: field || null };
return next();
}),
respond
@@ -96,11 +96,11 @@ router.post(
const field: Partial<Field> & { field: string; type: Type | null } = req.body;
await service.createField(req.params.collection, field);
await service.createField(req.params['collection'], field);
try {
const createdField = await service.readOne(req.params.collection, field.field);
res.locals.payload = { data: createdField || null };
const createdField = await service.readOne(req.params['collection'], field.field);
res.locals['payload'] = { data: createdField || null };
} catch (error: any) {
if (error instanceof ForbiddenException) {
return next();
@@ -128,15 +128,15 @@ router.patch(
}
for (const field of req.body) {
await service.updateField(req.params.collection, field);
await service.updateField(req.params['collection'], field);
}
try {
const results: any = [];
for (const field of req.body) {
const updatedField = await service.readOne(req.params.collection, field.field);
const updatedField = await service.readOne(req.params['collection'], field.field);
results.push(updatedField);
res.locals.payload = { data: results || null };
res.locals['payload'] = { data: results || null };
}
} catch (error: any) {
if (error instanceof ForbiddenException) {
@@ -186,13 +186,13 @@ router.patch(
const fieldData: Partial<Field> & { field: string; type: Type } = req.body;
if (!fieldData.field) fieldData.field = req.params.field;
if (!fieldData.field) fieldData.field = req.params['field'];
await service.updateField(req.params.collection, fieldData);
await service.updateField(req.params['collection'], fieldData);
try {
const updatedField = await service.readOne(req.params.collection, req.params.field);
res.locals.payload = { data: updatedField || null };
const updatedField = await service.readOne(req.params['collection'], req.params['field']);
res.locals['payload'] = { data: updatedField || null };
} catch (error: any) {
if (error instanceof ForbiddenException) {
return next();
@@ -214,7 +214,7 @@ router.delete(
accountability: req.accountability,
schema: req.schema,
});
await service.deleteField(req.params.collection, req.params.field);
await service.deleteField(req.params['collection'], req.params['field']);
return next();
}),
respond

View File

@@ -38,7 +38,7 @@ export const multipartHandler: RequestHandler = (req, res, next) => {
const savedFiles: PrimaryKey[] = [];
const service = new FilesService({ accountability: req.accountability, schema: req.schema });
const existingPrimaryKey = req.params.pk || undefined;
const existingPrimaryKey = req.params['pk'] || undefined;
/**
* The order of the fields in multipart/form-data is important. We require that all fields
@@ -46,7 +46,7 @@ export const multipartHandler: RequestHandler = (req, res, next) => {
* the row in directus_files async during the upload of the actual file.
*/
let disk: string = toArray(env.STORAGE_LOCATIONS)[0];
let disk: string = toArray(env['STORAGE_LOCATIONS'])[0];
let payload: any = {};
let fileCount = 0;
@@ -95,6 +95,8 @@ export const multipartHandler: RequestHandler = (req, res, next) => {
} catch (error: any) {
busboy.emit('error', error);
}
return undefined;
});
busboy.on('error', (error: Error) => {
@@ -113,7 +115,7 @@ export const multipartHandler: RequestHandler = (req, res, next) => {
return next(new InvalidPayloadException(`No files were included in the body`));
}
res.locals.savedFiles = savedFiles;
res.locals['savedFiles'] = savedFiles;
return next();
}
}
@@ -130,7 +132,7 @@ router.post(
let keys: PrimaryKey | PrimaryKey[] = [];
if (req.is('multipart/form-data')) {
keys = res.locals.savedFiles;
keys = res.locals['savedFiles'];
} else {
keys = await service.createOne(req.body);
}
@@ -139,14 +141,14 @@ router.post(
if (Array.isArray(keys) && keys.length > 1) {
const records = await service.readMany(keys, req.sanitizedQuery);
res.locals.payload = {
res.locals['payload'] = {
data: records,
};
} else {
const key = Array.isArray(keys) ? keys[0] : keys;
const record = await service.readOne(key, req.sanitizedQuery);
res.locals.payload = {
res.locals['payload'] = {
data: record,
};
}
@@ -186,7 +188,7 @@ router.post(
try {
const record = await service.readOne(primaryKey, req.sanitizedQuery);
res.locals.payload = { data: record || null };
res.locals['payload'] = { data: record || null };
} catch (error: any) {
if (error instanceof ForbiddenException) {
return next();
@@ -223,7 +225,7 @@ const readHandler = asyncHandler(async (req, res, next) => {
const meta = await metaService.getMetaForQuery('directus_files', req.sanitizedQuery);
res.locals.payload = { data: result, meta };
res.locals['payload'] = { data: result, meta };
return next();
});
@@ -238,8 +240,8 @@ router.get(
schema: req.schema,
});
const record = await service.readOne(req.params.pk, req.sanitizedQuery);
res.locals.payload = { data: record || null };
const record = await service.readOne(req.params['pk'], req.sanitizedQuery);
res.locals['payload'] = { data: record || null };
return next();
}),
respond
@@ -267,7 +269,7 @@ router.patch(
try {
const result = await service.readMany(keys, req.sanitizedQuery);
res.locals.payload = { data: result || null };
res.locals['payload'] = { data: result || null };
} catch (error: any) {
if (error instanceof ForbiddenException) {
return next();
@@ -290,11 +292,11 @@ router.patch(
schema: req.schema,
});
await service.updateOne(req.params.pk, req.body);
await service.updateOne(req.params['pk'], req.body);
try {
const record = await service.readOne(req.params.pk, req.sanitizedQuery);
res.locals.payload = { data: record || null };
const record = await service.readOne(req.params['pk'], req.sanitizedQuery);
res.locals['payload'] = { data: record || null };
} catch (error: any) {
if (error instanceof ForbiddenException) {
return next();
@@ -339,7 +341,7 @@ router.delete(
schema: req.schema,
});
await service.deleteOne(req.params.pk);
await service.deleteOne(req.params['pk']);
return next();
}),

View File

@@ -18,7 +18,7 @@ const webhookFlowHandler = asyncHandler(async (req, res, next) => {
const flowManager = getFlowManager();
const result = await flowManager.runWebhookFlow(
`${req.method}-${req.params.pk}`,
`${req.method}-${req.params['pk']}`,
{
path: req.path,
query: req.query,
@@ -32,7 +32,7 @@ const webhookFlowHandler = asyncHandler(async (req, res, next) => {
}
);
res.locals.payload = result;
res.locals['payload'] = result;
return next();
});
@@ -60,10 +60,10 @@ router.post(
try {
if (Array.isArray(req.body)) {
const items = await service.readMany(savedKeys, req.sanitizedQuery);
res.locals.payload = { data: items };
res.locals['payload'] = { data: items };
} else {
const item = await service.readOne(savedKeys[0], req.sanitizedQuery);
res.locals.payload = { data: item };
res.locals['payload'] = { data: item };
}
} catch (error) {
if (error instanceof ForbiddenException) {
@@ -91,7 +91,7 @@ const readHandler = asyncHandler(async (req, res, next) => {
const records = await service.readByQuery(req.sanitizedQuery);
const meta = await metaService.getMetaForQuery(req.collection, req.sanitizedQuery);
res.locals.payload = { data: records || null, meta };
res.locals['payload'] = { data: records || null, meta };
return next();
});
@@ -106,9 +106,9 @@ router.get(
schema: req.schema,
});
const record = await service.readOne(req.params.pk, req.sanitizedQuery);
const record = await service.readOne(req.params['pk'], req.sanitizedQuery);
res.locals.payload = { data: record || null };
res.locals['payload'] = { data: record || null };
return next();
}),
respond
@@ -136,7 +136,7 @@ router.patch(
try {
const result = await service.readMany(keys, req.sanitizedQuery);
res.locals.payload = { data: result };
res.locals['payload'] = { data: result };
} catch (error) {
if (error instanceof ForbiddenException) {
return next();
@@ -158,11 +158,11 @@ router.patch(
schema: req.schema,
});
const primaryKey = await service.updateOne(req.params.pk, req.body);
const primaryKey = await service.updateOne(req.params['pk'], req.body);
try {
const item = await service.readOne(primaryKey, req.sanitizedQuery);
res.locals.payload = { data: item || null };
res.locals['payload'] = { data: item || null };
} catch (error) {
if (error instanceof ForbiddenException) {
return next();
@@ -206,7 +206,7 @@ router.delete(
schema: req.schema,
});
await service.deleteOne(req.params.pk);
await service.deleteOne(req.params['pk']);
return next();
}),

View File

@@ -33,10 +33,10 @@ router.post(
try {
if (Array.isArray(req.body)) {
const records = await service.readMany(savedKeys, req.sanitizedQuery);
res.locals.payload = { data: records };
res.locals['payload'] = { data: records };
} else {
const record = await service.readOne(savedKeys[0], req.sanitizedQuery);
res.locals.payload = { data: record };
res.locals['payload'] = { data: record };
}
} catch (error: any) {
if (error instanceof ForbiddenException) {
@@ -73,7 +73,7 @@ const readHandler = asyncHandler(async (req, res, next) => {
const meta = await metaService.getMetaForQuery('directus_folders', req.sanitizedQuery);
res.locals.payload = { data: result, meta };
res.locals['payload'] = { data: result, meta };
return next();
});
@@ -87,9 +87,9 @@ router.get(
accountability: req.accountability,
schema: req.schema,
});
const record = await service.readOne(req.params.pk, req.sanitizedQuery);
const record = await service.readOne(req.params['pk'], req.sanitizedQuery);
res.locals.payload = { data: record || null };
res.locals['payload'] = { data: record || null };
return next();
}),
respond
@@ -117,7 +117,7 @@ router.patch(
try {
const result = await service.readMany(keys, req.sanitizedQuery);
res.locals.payload = { data: result || null };
res.locals['payload'] = { data: result || null };
} catch (error: any) {
if (error instanceof ForbiddenException) {
return next();
@@ -139,11 +139,11 @@ router.patch(
schema: req.schema,
});
const primaryKey = await service.updateOne(req.params.pk, req.body);
const primaryKey = await service.updateOne(req.params['pk'], req.body);
try {
const record = await service.readOne(primaryKey, req.sanitizedQuery);
res.locals.payload = { data: record || null };
res.locals['payload'] = { data: record || null };
} catch (error: any) {
if (error instanceof ForbiddenException) {
return next();
@@ -188,7 +188,7 @@ router.delete(
schema: req.schema,
});
await service.deleteOne(req.params.pk);
await service.deleteOne(req.params['pk']);
return next();
}),

View File

@@ -16,10 +16,10 @@ router.use(
scope: 'system',
});
res.locals.payload = await service.execute(res.locals.graphqlParams);
res.locals['payload'] = await service.execute(res.locals['graphqlParams']);
if (res.locals.payload?.errors?.length > 0) {
res.locals.cache = false;
if (res.locals['payload']?.errors?.length > 0) {
res.locals['cache'] = false;
}
return next();
@@ -37,10 +37,10 @@ router.use(
scope: 'items',
});
res.locals.payload = await service.execute(res.locals.graphqlParams);
res.locals['payload'] = await service.execute(res.locals['graphqlParams']);
if (res.locals.payload?.errors?.length > 0) {
res.locals.cache = false;
if (res.locals['payload']?.errors?.length > 0) {
res.locals['cache'] = false;
}
return next();

View File

@@ -14,7 +14,7 @@ router.post(
'/:collection',
collectionExists,
asyncHandler(async (req, res, next) => {
if (req.params.collection.startsWith('directus_')) throw new ForbiddenException();
if (req.params['collection'].startsWith('directus_')) throw new ForbiddenException();
if (req.singleton) {
throw new RouteNotFoundException(req.path);
@@ -38,10 +38,10 @@ router.post(
try {
if (Array.isArray(req.body)) {
const result = await service.readMany(savedKeys, req.sanitizedQuery);
res.locals.payload = { data: result || null };
res.locals['payload'] = { data: result || null };
} else {
const result = await service.readOne(savedKeys[0], req.sanitizedQuery);
res.locals.payload = { data: result || null };
res.locals['payload'] = { data: result || null };
}
} catch (error: any) {
if (error instanceof ForbiddenException) {
@@ -57,7 +57,7 @@ router.post(
);
const readHandler = asyncHandler(async (req, res, next) => {
if (req.params.collection.startsWith('directus_')) throw new ForbiddenException();
if (req.params['collection'].startsWith('directus_')) throw new ForbiddenException();
const service = new ItemsService(req.collection, {
accountability: req.accountability,
@@ -81,7 +81,7 @@ const readHandler = asyncHandler(async (req, res, next) => {
const meta = await metaService.getMetaForQuery(req.collection, req.sanitizedQuery);
res.locals.payload = {
res.locals['payload'] = {
meta: meta,
data: result,
};
@@ -96,16 +96,16 @@ router.get(
'/:collection/:pk',
collectionExists,
asyncHandler(async (req, res, next) => {
if (req.params.collection.startsWith('directus_')) throw new ForbiddenException();
if (req.params['collection'].startsWith('directus_')) throw new ForbiddenException();
const service = new ItemsService(req.collection, {
accountability: req.accountability,
schema: req.schema,
});
const result = await service.readOne(req.params.pk, req.sanitizedQuery);
const result = await service.readOne(req.params['pk'], req.sanitizedQuery);
res.locals.payload = {
res.locals['payload'] = {
data: result || null,
};
@@ -119,7 +119,7 @@ router.patch(
collectionExists,
validateBatch('update'),
asyncHandler(async (req, res, next) => {
if (req.params.collection.startsWith('directus_')) throw new ForbiddenException();
if (req.params['collection'].startsWith('directus_')) throw new ForbiddenException();
const service = new ItemsService(req.collection, {
accountability: req.accountability,
@@ -130,7 +130,7 @@ router.patch(
await service.upsertSingleton(req.body);
const item = await service.readSingleton(req.sanitizedQuery);
res.locals.payload = { data: item || null };
res.locals['payload'] = { data: item || null };
return next();
}
@@ -147,7 +147,7 @@ router.patch(
try {
const result = await service.readMany(keys, req.sanitizedQuery);
res.locals.payload = { data: result };
res.locals['payload'] = { data: result };
} catch (error: any) {
if (error instanceof ForbiddenException) {
return next();
@@ -165,7 +165,7 @@ router.patch(
'/:collection/:pk',
collectionExists,
asyncHandler(async (req, res, next) => {
if (req.params.collection.startsWith('directus_')) throw new ForbiddenException();
if (req.params['collection'].startsWith('directus_')) throw new ForbiddenException();
if (req.singleton) {
throw new RouteNotFoundException(req.path);
@@ -176,11 +176,11 @@ router.patch(
schema: req.schema,
});
const updatedPrimaryKey = await service.updateOne(req.params.pk, req.body);
const updatedPrimaryKey = await service.updateOne(req.params['pk'], req.body);
try {
const result = await service.readOne(updatedPrimaryKey, req.sanitizedQuery);
res.locals.payload = { data: result || null };
res.locals['payload'] = { data: result || null };
} catch (error: any) {
if (error instanceof ForbiddenException) {
return next();
@@ -199,7 +199,7 @@ router.delete(
collectionExists,
validateBatch('delete'),
asyncHandler(async (req, res, next) => {
if (req.params.collection.startsWith('directus_')) throw new ForbiddenException();
if (req.params['collection'].startsWith('directus_')) throw new ForbiddenException();
const service = new ItemsService(req.collection, {
accountability: req.accountability,
@@ -224,14 +224,14 @@ router.delete(
'/:collection/:pk',
collectionExists,
asyncHandler(async (req, res, next) => {
if (req.params.collection.startsWith('directus_')) throw new ForbiddenException();
if (req.params['collection'].startsWith('directus_')) throw new ForbiddenException();
const service = new ItemsService(req.collection, {
accountability: req.accountability,
schema: req.schema,
});
await service.deleteOne(req.params.pk);
await service.deleteOne(req.params['pk']);
return next();
}),
respond

View File

@@ -33,10 +33,10 @@ router.post(
try {
if (Array.isArray(req.body)) {
const records = await service.readMany(savedKeys, req.sanitizedQuery);
res.locals.payload = { data: records };
res.locals['payload'] = { data: records };
} else {
const record = await service.readOne(savedKeys[0], req.sanitizedQuery);
res.locals.payload = { data: record };
res.locals['payload'] = { data: record };
}
} catch (error: any) {
if (error instanceof ForbiddenException) {
@@ -73,7 +73,7 @@ const readHandler = asyncHandler(async (req, res, next) => {
const meta = await metaService.getMetaForQuery('directus_notifications', req.sanitizedQuery);
res.locals.payload = { data: result, meta };
res.locals['payload'] = { data: result, meta };
return next();
});
@@ -88,9 +88,9 @@ router.get(
schema: req.schema,
});
const record = await service.readOne(req.params.pk, req.sanitizedQuery);
const record = await service.readOne(req.params['pk'], req.sanitizedQuery);
res.locals.payload = { data: record || null };
res.locals['payload'] = { data: record || null };
return next();
}),
respond
@@ -118,7 +118,7 @@ router.patch(
try {
const result = await service.readMany(keys, req.sanitizedQuery);
res.locals.payload = { data: result };
res.locals['payload'] = { data: result };
} catch (error: any) {
if (error instanceof ForbiddenException) {
return next();
@@ -140,11 +140,11 @@ router.patch(
schema: req.schema,
});
const primaryKey = await service.updateOne(req.params.pk, req.body);
const primaryKey = await service.updateOne(req.params['pk'], req.body);
try {
const record = await service.readOne(primaryKey, req.sanitizedQuery);
res.locals.payload = { data: record };
res.locals['payload'] = { data: record };
} catch (error: any) {
if (error instanceof ForbiddenException) {
return next();
@@ -189,7 +189,7 @@ router.delete(
schema: req.schema,
});
await service.deleteOne(req.params.pk);
await service.deleteOne(req.params['pk']);
return next();
}),

View File

@@ -33,10 +33,10 @@ router.post(
try {
if (Array.isArray(req.body)) {
const items = await service.readMany(savedKeys, req.sanitizedQuery);
res.locals.payload = { data: items };
res.locals['payload'] = { data: items };
} else {
const item = await service.readOne(savedKeys[0], req.sanitizedQuery);
res.locals.payload = { data: item };
res.locals['payload'] = { data: item };
}
} catch (error) {
if (error instanceof ForbiddenException) {
@@ -64,7 +64,7 @@ const readHandler = asyncHandler(async (req, res, next) => {
const records = await service.readByQuery(req.sanitizedQuery);
const meta = await metaService.getMetaForQuery(req.collection, req.sanitizedQuery);
res.locals.payload = { data: records || null, meta };
res.locals['payload'] = { data: records || null, meta };
return next();
});
@@ -79,9 +79,9 @@ router.get(
schema: req.schema,
});
const record = await service.readOne(req.params.pk, req.sanitizedQuery);
const record = await service.readOne(req.params['pk'], req.sanitizedQuery);
res.locals.payload = { data: record || null };
res.locals['payload'] = { data: record || null };
return next();
}),
respond
@@ -109,7 +109,7 @@ router.patch(
try {
const result = await service.readMany(keys, req.sanitizedQuery);
res.locals.payload = { data: result };
res.locals['payload'] = { data: result };
} catch (error) {
if (error instanceof ForbiddenException) {
return next();
@@ -131,11 +131,11 @@ router.patch(
schema: req.schema,
});
const primaryKey = await service.updateOne(req.params.pk, req.body);
const primaryKey = await service.updateOne(req.params['pk'], req.body);
try {
const item = await service.readOne(primaryKey, req.sanitizedQuery);
res.locals.payload = { data: item || null };
res.locals['payload'] = { data: item || null };
} catch (error) {
if (error instanceof ForbiddenException) {
return next();
@@ -179,7 +179,7 @@ router.delete(
schema: req.schema,
});
await service.deleteOne(req.params.pk);
await service.deleteOne(req.params['pk']);
return next();
}),

View File

@@ -33,10 +33,10 @@ router.post(
try {
if (Array.isArray(req.body)) {
const items = await service.readMany(savedKeys, req.sanitizedQuery);
res.locals.payload = { data: items };
res.locals['payload'] = { data: items };
} else {
const item = await service.readOne(savedKeys[0], req.sanitizedQuery);
res.locals.payload = { data: item };
res.locals['payload'] = { data: item };
}
} catch (error) {
if (error instanceof ForbiddenException) {
@@ -64,7 +64,7 @@ const readHandler = asyncHandler(async (req, res, next) => {
const records = await service.readByQuery(req.sanitizedQuery);
const meta = await metaService.getMetaForQuery(req.collection, req.sanitizedQuery);
res.locals.payload = { data: records || null, meta };
res.locals['payload'] = { data: records || null, meta };
return next();
});
@@ -79,9 +79,9 @@ router.get(
schema: req.schema,
});
const record = await service.readOne(req.params.pk, req.sanitizedQuery);
const record = await service.readOne(req.params['pk'], req.sanitizedQuery);
res.locals.payload = { data: record || null };
res.locals['payload'] = { data: record || null };
return next();
}),
respond
@@ -109,7 +109,7 @@ router.patch(
try {
const result = await service.readMany(keys, req.sanitizedQuery);
res.locals.payload = { data: result };
res.locals['payload'] = { data: result };
} catch (error) {
if (error instanceof ForbiddenException) {
return next();
@@ -131,11 +131,11 @@ router.patch(
schema: req.schema,
});
const primaryKey = await service.updateOne(req.params.pk, req.body);
const primaryKey = await service.updateOne(req.params['pk'], req.body);
try {
const item = await service.readOne(primaryKey, req.sanitizedQuery);
res.locals.payload = { data: item || null };
res.locals['payload'] = { data: item || null };
} catch (error) {
if (error instanceof ForbiddenException) {
return next();
@@ -179,7 +179,7 @@ router.delete(
schema: req.schema,
});
await service.deleteOne(req.params.pk);
await service.deleteOne(req.params['pk']);
return next();
}),

View File

@@ -33,10 +33,10 @@ router.post(
try {
if (Array.isArray(req.body)) {
const items = await service.readMany(savedKeys, req.sanitizedQuery);
res.locals.payload = { data: items };
res.locals['payload'] = { data: items };
} else {
const item = await service.readOne(savedKeys[0], req.sanitizedQuery);
res.locals.payload = { data: item };
res.locals['payload'] = { data: item };
}
} catch (error: any) {
if (error instanceof ForbiddenException) {
@@ -73,7 +73,7 @@ const readHandler = asyncHandler(async (req, res, next) => {
const meta = await metaService.getMetaForQuery('directus_permissions', req.sanitizedQuery);
res.locals.payload = { data: result, meta };
res.locals['payload'] = { data: result, meta };
return next();
});
@@ -89,9 +89,9 @@ router.get(
schema: req.schema,
});
const record = await service.readOne(req.params.pk, req.sanitizedQuery);
const record = await service.readOne(req.params['pk'], req.sanitizedQuery);
res.locals.payload = { data: record };
res.locals['payload'] = { data: record };
return next();
}),
respond
@@ -119,7 +119,7 @@ router.patch(
try {
const result = await service.readMany(keys, req.sanitizedQuery);
res.locals.payload = { data: result };
res.locals['payload'] = { data: result };
} catch (error: any) {
if (error instanceof ForbiddenException) {
return next();
@@ -141,11 +141,11 @@ router.patch(
schema: req.schema,
});
const primaryKey = await service.updateOne(req.params.pk, req.body);
const primaryKey = await service.updateOne(req.params['pk'], req.body);
try {
const item = await service.readOne(primaryKey, req.sanitizedQuery);
res.locals.payload = { data: item || null };
res.locals['payload'] = { data: item || null };
} catch (error: any) {
if (error instanceof ForbiddenException) {
return next();
@@ -190,7 +190,7 @@ router.delete(
schema: req.schema,
});
await service.deleteOne(req.params.pk);
await service.deleteOne(req.params['pk']);
return next();
}),

View File

@@ -33,10 +33,10 @@ router.post(
try {
if (Array.isArray(req.body)) {
const records = await service.readMany(savedKeys, req.sanitizedQuery);
res.locals.payload = { data: records };
res.locals['payload'] = { data: records };
} else {
const record = await service.readOne(savedKeys[0], req.sanitizedQuery);
res.locals.payload = { data: record };
res.locals['payload'] = { data: record };
}
} catch (error: any) {
if (error instanceof ForbiddenException) {
@@ -73,7 +73,7 @@ const readHandler = asyncHandler(async (req, res, next) => {
const meta = await metaService.getMetaForQuery('directus_presets', req.sanitizedQuery);
res.locals.payload = { data: result, meta };
res.locals['payload'] = { data: result, meta };
return next();
});
@@ -88,9 +88,9 @@ router.get(
schema: req.schema,
});
const record = await service.readOne(req.params.pk, req.sanitizedQuery);
const record = await service.readOne(req.params['pk'], req.sanitizedQuery);
res.locals.payload = { data: record || null };
res.locals['payload'] = { data: record || null };
return next();
}),
respond
@@ -118,7 +118,7 @@ router.patch(
try {
const result = await service.readMany(keys, req.sanitizedQuery);
res.locals.payload = { data: result };
res.locals['payload'] = { data: result };
} catch (error: any) {
if (error instanceof ForbiddenException) {
return next();
@@ -140,11 +140,11 @@ router.patch(
schema: req.schema,
});
const primaryKey = await service.updateOne(req.params.pk, req.body);
const primaryKey = await service.updateOne(req.params['pk'], req.body);
try {
const record = await service.readOne(primaryKey, req.sanitizedQuery);
res.locals.payload = { data: record };
res.locals['payload'] = { data: record };
} catch (error: any) {
if (error instanceof ForbiddenException) {
return next();
@@ -189,7 +189,7 @@ router.delete(
schema: req.schema,
});
await service.deleteOne(req.params.pk);
await service.deleteOne(req.params['pk']);
return next();
}),

View File

@@ -20,7 +20,7 @@ router.get(
});
const relations = await service.readAll();
res.locals.payload = { data: relations || null };
res.locals['payload'] = { data: relations || null };
return next();
}),
respond
@@ -34,9 +34,9 @@ router.get(
accountability: req.accountability,
schema: req.schema,
});
const relations = await service.readAll(req.params.collection);
const relations = await service.readAll(req.params['collection']);
res.locals.payload = { data: relations || null };
res.locals['payload'] = { data: relations || null };
return next();
}),
respond
@@ -51,9 +51,9 @@ router.get(
schema: req.schema,
});
const relation = await service.readOne(req.params.collection, req.params.field);
const relation = await service.readOne(req.params['collection'], req.params['field']);
res.locals.payload = { data: relation || null };
res.locals['payload'] = { data: relation || null };
return next();
}),
respond
@@ -89,7 +89,7 @@ router.post(
try {
const createdRelation = await service.readOne(req.body.collection, req.body.field);
res.locals.payload = { data: createdRelation || null };
res.locals['payload'] = { data: createdRelation || null };
} catch (error: any) {
if (error instanceof ForbiddenException) {
return next();
@@ -130,11 +130,11 @@ router.patch(
throw new InvalidPayloadException(error.message);
}
await service.updateOne(req.params.collection, req.params.field, req.body);
await service.updateOne(req.params['collection'], req.params['field'], req.body);
try {
const updatedField = await service.readOne(req.params.collection, req.params.field);
res.locals.payload = { data: updatedField || null };
const updatedField = await service.readOne(req.params['collection'], req.params['field']);
res.locals['payload'] = { data: updatedField || null };
} catch (error: any) {
if (error instanceof ForbiddenException) {
return next();
@@ -156,7 +156,7 @@ router.delete(
accountability: req.accountability,
schema: req.schema,
});
await service.deleteOne(req.params.collection, req.params.field);
await service.deleteOne(req.params['collection'], req.params['field']);
return next();
}),
respond

View File

@@ -22,7 +22,7 @@ const readHandler = asyncHandler(async (req, res, next) => {
const records = await service.readByQuery(req.sanitizedQuery);
const meta = await metaService.getMetaForQuery('directus_revisions', req.sanitizedQuery);
res.locals.payload = { data: records || null, meta };
res.locals['payload'] = { data: records || null, meta };
return next();
});
@@ -37,9 +37,9 @@ router.get(
schema: req.schema,
});
const record = await service.readOne(req.params.pk, req.sanitizedQuery);
const record = await service.readOne(req.params['pk'], req.sanitizedQuery);
res.locals.payload = { data: record || null };
res.locals['payload'] = { data: record || null };
return next();
}),
respond

View File

@@ -33,10 +33,10 @@ router.post(
try {
if (Array.isArray(req.body)) {
const items = await service.readMany(savedKeys, req.sanitizedQuery);
res.locals.payload = { data: items };
res.locals['payload'] = { data: items };
} else {
const item = await service.readOne(savedKeys[0], req.sanitizedQuery);
res.locals.payload = { data: item };
res.locals['payload'] = { data: item };
}
} catch (error: any) {
if (error instanceof ForbiddenException) {
@@ -65,7 +65,7 @@ const readHandler = asyncHandler(async (req, res, next) => {
const records = await service.readByQuery(req.sanitizedQuery);
const meta = await metaService.getMetaForQuery('directus_roles', req.sanitizedQuery);
res.locals.payload = { data: records || null, meta };
res.locals['payload'] = { data: records || null, meta };
return next();
});
@@ -80,9 +80,9 @@ router.get(
schema: req.schema,
});
const record = await service.readOne(req.params.pk, req.sanitizedQuery);
const record = await service.readOne(req.params['pk'], req.sanitizedQuery);
res.locals.payload = { data: record || null };
res.locals['payload'] = { data: record || null };
return next();
}),
respond
@@ -110,7 +110,7 @@ router.patch(
try {
const result = await service.readMany(keys, req.sanitizedQuery);
res.locals.payload = { data: result };
res.locals['payload'] = { data: result };
} catch (error: any) {
if (error instanceof ForbiddenException) {
return next();
@@ -132,11 +132,11 @@ router.patch(
schema: req.schema,
});
const primaryKey = await service.updateOne(req.params.pk, req.body);
const primaryKey = await service.updateOne(req.params['pk'], req.body);
try {
const item = await service.readOne(primaryKey, req.sanitizedQuery);
res.locals.payload = { data: item || null };
res.locals['payload'] = { data: item || null };
} catch (error: any) {
if (error instanceof ForbiddenException) {
return next();
@@ -181,7 +181,7 @@ router.delete(
schema: req.schema,
});
await service.deleteOne(req.params.pk);
await service.deleteOne(req.params['pk']);
return next();
}),

View File

@@ -17,7 +17,7 @@ router.get(
asyncHandler(async (req, res, next) => {
const service = new SchemaService({ accountability: req.accountability });
const currentSnapshot = await service.snapshot();
res.locals.payload = { data: currentSnapshot };
res.locals['payload'] = { data: currentSnapshot };
return next();
}),
respond
@@ -36,7 +36,7 @@ router.post(
const schemaMultipartHandler: RequestHandler = (req, res, next) => {
if (req.is('application/json')) {
if (Object.keys(req.body).length === 0) throw new InvalidPayloadException(`No data was included in the body`);
res.locals.uploadedSnapshot = req.body;
res.locals['uploadedSnapshot'] = req.body;
return next();
}
@@ -82,7 +82,7 @@ const schemaMultipartHandler: RequestHandler = (req, res, next) => {
if (!uploadedSnapshot) throw new InvalidPayloadException(`No file was included in the body`);
res.locals.uploadedSnapshot = uploadedSnapshot;
res.locals['uploadedSnapshot'] = uploadedSnapshot;
return next();
} catch (error: any) {
@@ -104,14 +104,14 @@ router.post(
asyncHandler(schemaMultipartHandler),
asyncHandler(async (req, res, next) => {
const service = new SchemaService({ accountability: req.accountability });
const snapshot: Snapshot = res.locals.uploadedSnapshot;
const snapshot: Snapshot = res.locals['uploadedSnapshot'];
const currentSnapshot = await service.snapshot();
const snapshotDiff = await service.diff(snapshot, { currentSnapshot, force: 'force' in req.query });
if (!snapshotDiff) return next();
const currentSnapshotHash = getVersionedHash(currentSnapshot);
res.locals.payload = { data: { hash: currentSnapshotHash, diff: snapshotDiff } };
res.locals['payload'] = { data: { hash: currentSnapshotHash, diff: snapshotDiff } };
return next();
}),
respond

View File

@@ -15,7 +15,7 @@ router.get(
schema: req.schema,
});
res.locals.payload = await service.oas.generate();
res.locals['payload'] = await service.oas.generate();
return next();
}),
respond
@@ -34,13 +34,13 @@ router.get(
schema: req.schema,
});
const scope = req.params.scope || 'items';
const scope = req.params['scope'] || 'items';
if (['items', 'system'].includes(scope) === false) throw new RouteNotFoundException(req.path);
const info = await serverService.serverInfo();
const result = await service.graphql.generate(scope as 'items' | 'system');
const filename = info.project.project_name + '_' + format(new Date(), 'yyyy-MM-dd') + '.graphql';
const filename = info['project'].project_name + '_' + format(new Date(), 'yyyy-MM-dd') + '.graphql';
res.attachment(filename);
res.send(result);
@@ -55,7 +55,7 @@ router.get(
schema: req.schema,
});
const data = await service.serverInfo();
res.locals.payload = { data };
res.locals['payload'] = { data };
return next();
}),
respond
@@ -73,9 +73,9 @@ router.get(
res.setHeader('Content-Type', 'application/health+json');
if (data.status === 'error') res.status(503);
res.locals.payload = data;
res.locals.cache = false;
if (data['status'] === 'error') res.status(503);
res.locals['payload'] = data;
res.locals['cache'] = false;
return next();
}),
respond

View File

@@ -17,7 +17,7 @@ router.get(
schema: req.schema,
});
const records = await service.readSingleton(req.sanitizedQuery);
res.locals.payload = { data: records || null };
res.locals['payload'] = { data: records || null };
return next();
}),
respond
@@ -34,7 +34,7 @@ router.patch(
try {
const record = await service.readSingleton(req.sanitizedQuery);
res.locals.payload = { data: record || null };
res.locals['payload'] = { data: record || null };
} catch (error: any) {
if (error instanceof ForbiddenException) {
return next();

View File

@@ -36,9 +36,9 @@ router.post(
const { accessToken, refreshToken, expires } = await service.login(req.body);
res.cookie(env.REFRESH_TOKEN_COOKIE_NAME, refreshToken, COOKIE_OPTIONS);
res.cookie(env['REFRESH_TOKEN_COOKIE_NAME'], refreshToken, COOKIE_OPTIONS);
res.locals.payload = { data: { access_token: accessToken, expires } };
res.locals['payload'] = { data: { access_token: accessToken, expires } };
return next();
}),
@@ -92,10 +92,10 @@ router.post(
try {
if (Array.isArray(req.body)) {
const items = await service.readMany(savedKeys, req.sanitizedQuery);
res.locals.payload = { data: items };
res.locals['payload'] = { data: items };
} else {
const item = await service.readOne(savedKeys[0], req.sanitizedQuery);
res.locals.payload = { data: item };
res.locals['payload'] = { data: item };
}
} catch (error) {
if (error instanceof ForbiddenException) {
@@ -118,7 +118,7 @@ const readHandler = asyncHandler(async (req, res, next) => {
const records = await service.readByQuery(req.sanitizedQuery);
res.locals.payload = { data: records || null };
res.locals['payload'] = { data: records || null };
return next();
});
@@ -132,7 +132,7 @@ router.get(
schema: req.schema,
});
const record = await service.readOne(req.params.pk, {
const record = await service.readOne(req.params['pk'], {
fields: ['id', 'collection', 'item', 'password', 'max_uses', 'times_used', 'date_start', 'date_end'],
filter: {
_and: [
@@ -168,7 +168,7 @@ router.get(
},
});
res.locals.payload = { data: record || null };
res.locals['payload'] = { data: record || null };
return next();
}),
respond
@@ -182,9 +182,9 @@ router.get(
schema: req.schema,
});
const record = await service.readOne(req.params.pk, req.sanitizedQuery);
const record = await service.readOne(req.params['pk'], req.sanitizedQuery);
res.locals.payload = { data: record || null };
res.locals['payload'] = { data: record || null };
return next();
}),
respond
@@ -212,7 +212,7 @@ router.patch(
try {
const result = await service.readMany(keys, req.sanitizedQuery);
res.locals.payload = { data: result };
res.locals['payload'] = { data: result };
} catch (error) {
if (error instanceof ForbiddenException) {
return next();
@@ -234,11 +234,11 @@ router.patch(
schema: req.schema,
});
const primaryKey = await service.updateOne(req.params.pk, req.body);
const primaryKey = await service.updateOne(req.params['pk'], req.body);
try {
const item = await service.readOne(primaryKey, req.sanitizedQuery);
res.locals.payload = { data: item || null };
res.locals['payload'] = { data: item || null };
} catch (error) {
if (error instanceof ForbiddenException) {
return next();
@@ -282,7 +282,7 @@ router.delete(
schema: req.schema,
});
await service.deleteOne(req.params.pk);
await service.deleteOne(req.params['pk']);
return next();
}),

View File

@@ -35,10 +35,10 @@ router.post(
try {
if (Array.isArray(req.body)) {
const items = await service.readMany(savedKeys, req.sanitizedQuery);
res.locals.payload = { data: items };
res.locals['payload'] = { data: items };
} else {
const item = await service.readOne(savedKeys[0], req.sanitizedQuery);
res.locals.payload = { data: item };
res.locals['payload'] = { data: item };
}
} catch (error: any) {
if (error instanceof ForbiddenException) {
@@ -67,7 +67,7 @@ const readHandler = asyncHandler(async (req, res, next) => {
const item = await service.readByQuery(req.sanitizedQuery);
const meta = await metaService.getMetaForQuery('directus_users', req.sanitizedQuery);
res.locals.payload = { data: item || null, meta };
res.locals['payload'] = { data: item || null, meta };
return next();
});
@@ -86,7 +86,7 @@ router.get(
app_access: false,
},
};
res.locals.payload = { data: user };
res.locals['payload'] = { data: user };
return next();
}
@@ -101,10 +101,10 @@ router.get(
try {
const item = await service.readOne(req.accountability.user, req.sanitizedQuery);
res.locals.payload = { data: item || null };
res.locals['payload'] = { data: item || null };
} catch (error: any) {
if (error instanceof ForbiddenException) {
res.locals.payload = { data: { id: req.accountability.user } };
res.locals['payload'] = { data: { id: req.accountability.user } };
return next();
}
@@ -125,9 +125,9 @@ router.get(
schema: req.schema,
});
const items = await service.readOne(req.params.pk, req.sanitizedQuery);
const items = await service.readOne(req.params['pk'], req.sanitizedQuery);
res.locals.payload = { data: items || null };
res.locals['payload'] = { data: items || null };
return next();
}),
respond
@@ -148,7 +148,7 @@ router.patch(
const primaryKey = await service.updateOne(req.accountability.user, req.body);
const item = await service.readOne(primaryKey, req.sanitizedQuery);
res.locals.payload = { data: item || null };
res.locals['payload'] = { data: item || null };
return next();
}),
respond
@@ -195,7 +195,7 @@ router.patch(
try {
const result = await service.readMany(keys, req.sanitizedQuery);
res.locals.payload = { data: result };
res.locals['payload'] = { data: result };
} catch (error: any) {
if (error instanceof ForbiddenException) {
return next();
@@ -217,11 +217,11 @@ router.patch(
schema: req.schema,
});
const primaryKey = await service.updateOne(req.params.pk, req.body);
const primaryKey = await service.updateOne(req.params['pk'], req.body);
try {
const item = await service.readOne(primaryKey, req.sanitizedQuery);
res.locals.payload = { data: item || null };
res.locals['payload'] = { data: item || null };
} catch (error: any) {
if (error instanceof ForbiddenException) {
return next();
@@ -266,7 +266,7 @@ router.delete(
schema: req.schema,
});
await service.deleteOne(req.params.pk);
await service.deleteOne(req.params['pk']);
return next();
}),
@@ -339,7 +339,7 @@ router.post(
const { url, secret } = await service.generateTFA(req.accountability.user);
res.locals.payload = { data: { secret, otpauth_url: url } };
res.locals['payload'] = { data: { secret, otpauth_url: url } };
return next();
}),
respond
@@ -469,7 +469,7 @@ router.post(
throw new InvalidCredentialsException();
}
if (!req.accountability.admin || !req.params.pk) {
if (!req.accountability.admin || !req.params['pk']) {
throw new ForbiddenException();
}
@@ -478,7 +478,7 @@ router.post(
schema: req.schema,
});
await service.disableTFA(req.params.pk);
await service.disableTFA(req.params['pk']);
return next();
}),
respond

View File

@@ -23,10 +23,10 @@ router.get(
asyncHandler(async (req, res) => {
const { nanoid } = await import('nanoid');
if (req.query && req.query.length && Number(req.query.length) > 500)
if (req.query && req.query['length'] && Number(req.query['length']) > 500)
throw new InvalidQueryException(`"length" can't be more than 500 characters`);
const string = nanoid(req.query?.length ? Number(req.query.length) : 32);
const string = nanoid(req.query?.['length'] ? Number(req.query['length']) : 32);
return res.json({ data: string });
})
@@ -91,7 +91,7 @@ router.post(
accountability: req.accountability,
schema: req.schema,
});
await service.revert(req.params.revision);
await service.revert(req.params['revision']);
next();
}),
respond
@@ -124,7 +124,7 @@ router.post(
busboy.on('file', async (_fieldname, fileStream, { mimeType }) => {
try {
await service.import(req.params.collection, mimeType, fileStream);
await service.import(req.params['collection'], mimeType, fileStream);
} catch (err: any) {
return next(err);
}
@@ -158,7 +158,7 @@ router.post(
const sanitizedQuery = sanitizeQuery(req.body.query, req.accountability ?? null);
// We're not awaiting this, as it's supposed to run async in the background
service.exportToFile(req.params.collection, sanitizedQuery, req.body.format, {
service.exportToFile(req.params['collection'], sanitizedQuery, req.body.format, {
file: req.body.file,
});

View File

@@ -33,10 +33,10 @@ router.post(
try {
if (Array.isArray(req.body)) {
const items = await service.readMany(savedKeys, req.sanitizedQuery);
res.locals.payload = { data: items };
res.locals['payload'] = { data: items };
} else {
const item = await service.readOne(savedKeys[0], req.sanitizedQuery);
res.locals.payload = { data: item };
res.locals['payload'] = { data: item };
}
} catch (error: any) {
if (error instanceof ForbiddenException) {
@@ -64,7 +64,7 @@ const readHandler = asyncHandler(async (req, res, next) => {
const records = await service.readByQuery(req.sanitizedQuery);
const meta = await metaService.getMetaForQuery(req.collection, req.sanitizedQuery);
res.locals.payload = { data: records || null, meta };
res.locals['payload'] = { data: records || null, meta };
return next();
});
@@ -79,9 +79,9 @@ router.get(
schema: req.schema,
});
const record = await service.readOne(req.params.pk, req.sanitizedQuery);
const record = await service.readOne(req.params['pk'], req.sanitizedQuery);
res.locals.payload = { data: record || null };
res.locals['payload'] = { data: record || null };
return next();
}),
respond
@@ -107,7 +107,7 @@ router.patch(
try {
const result = await service.readMany(keys, req.sanitizedQuery);
res.locals.payload = { data: result };
res.locals['payload'] = { data: result };
} catch (error: any) {
if (error instanceof ForbiddenException) {
return next();
@@ -129,11 +129,11 @@ router.patch(
schema: req.schema,
});
const primaryKey = await service.updateOne(req.params.pk, req.body);
const primaryKey = await service.updateOne(req.params['pk'], req.body);
try {
const item = await service.readOne(primaryKey, req.sanitizedQuery);
res.locals.payload = { data: item || null };
res.locals['payload'] = { data: item || null };
} catch (error: any) {
if (error instanceof ForbiddenException) {
return next();
@@ -177,7 +177,7 @@ router.delete(
schema: req.schema,
});
await service.deleteOne(req.params.pk);
await service.deleteOne(req.params['pk']);
return next();
}),

View File

@@ -2,7 +2,7 @@ import { DateHelper } from '../types';
import { parseISO } from 'date-fns';
export class DateHelperMSSQL extends DateHelper {
writeTimestamp(date: string): Date {
override writeTimestamp(date: string): Date {
const parsedDate = parseISO(date);
return new Date(parsedDate.getTime() + parsedDate.getTimezoneOffset() * 60000);
}

View File

@@ -2,12 +2,12 @@ import { DateHelper } from '../types';
import { parseISO } from 'date-fns';
export class DateHelperMySQL extends DateHelper {
readTimestampString(date: string): string {
override readTimestampString(date: string): string {
const parsedDate = new Date(date);
return new Date(parsedDate.getTime() - parsedDate.getTimezoneOffset() * 60000).toISOString();
}
writeTimestamp(date: string): Date {
override writeTimestamp(date: string): Date {
const parsedDate = parseISO(date);
return new Date(parsedDate.getTime() + parsedDate.getTimezoneOffset() * 60000);
}

View File

@@ -1,7 +1,7 @@
import { DateHelper } from '../types';
export class DateHelperOracle extends DateHelper {
fieldFlagForField(fieldType: string): string {
override fieldFlagForField(fieldType: string): string {
switch (fieldType) {
case 'dateTime':
return 'cast-datetime';

View File

@@ -1,7 +1,7 @@
import { DateHelper } from '../types';
export class DateHelperSQLite extends DateHelper {
parse(date: string | Date): string {
override parse(date: string | Date): string {
if (!date) {
return date;
}
@@ -20,7 +20,7 @@ export class DateHelperSQLite extends DateHelper {
return String(new Date(date).getTime());
}
fieldFlagForField(fieldType: string): string {
override fieldFlagForField(fieldType: string): string {
switch (fieldType) {
case 'timestamp':
return 'cast-timestamp';

View File

@@ -10,7 +10,7 @@ export type FnHelperOptions = {
};
export abstract class FnHelper extends DatabaseHelper {
constructor(protected knex: Knex, protected schema: SchemaOverview) {
constructor(knex: Knex, protected schema: SchemaOverview) {
super(knex);
this.schema = schema;
}

View File

@@ -4,33 +4,40 @@ import type { GeoJSONGeometry } from 'wellknown';
import { GeometryHelper } from '../types';
export class GeometryHelperMSSQL extends GeometryHelper {
isTrue(expression: Knex.Raw) {
override isTrue(expression: Knex.Raw) {
return expression.wrap(``, ` = 1`);
}
isFalse(expression: Knex.Raw) {
override isFalse(expression: Knex.Raw) {
return expression.wrap(``, ` = 0`);
}
createColumn(table: Knex.CreateTableBuilder, field: RawField | Field) {
override createColumn(table: Knex.CreateTableBuilder, field: RawField | Field) {
if (field.type.split('.')[1]) {
field.meta!.special = [field.type];
}
return table.specificType(field.field, 'geometry');
}
asText(table: string, column: string): Knex.Raw {
override asText(table: string, column: string): Knex.Raw {
return this.knex.raw('??.??.STAsText() as ??', [table, column, column]);
}
fromText(text: string): Knex.Raw {
override fromText(text: string): Knex.Raw {
return this.knex.raw('geometry::STGeomFromText(?, 4326)', text);
}
_intersects(key: string, geojson: GeoJSONGeometry): Knex.Raw {
override _intersects(key: string, geojson: GeoJSONGeometry): Knex.Raw {
const geometry = this.fromGeoJSON(geojson);
return this.knex.raw('??.STIntersects(?)', [key, geometry]);
}
_intersects_bbox(key: string, geojson: GeoJSONGeometry): Knex.Raw {
override _intersects_bbox(key: string, geojson: GeoJSONGeometry): Knex.Raw {
const geometry = this.fromGeoJSON(geojson);
return this.knex.raw('??.STEnvelope().STIntersects(?.STEnvelope())', [key, geometry]);
}
collect(table: string, column: string): Knex.Raw {
override collect(table: string, column: string): Knex.Raw {
return this.knex.raw('geometry::CollectionAggregate(??.??).STAsText()', [table, column]);
}
}

View File

@@ -2,15 +2,17 @@ import type { Knex } from 'knex';
import { GeometryHelper } from '../types';
export class GeometryHelperMySQL extends GeometryHelper {
collect(table: string, column: string): Knex.Raw {
override collect(table: string, column: string): Knex.Raw {
return this.knex.raw(
`concat('geometrycollection(', group_concat(? separator ', '), ')'`,
this.asText(table, column)
);
}
fromText(text: string): Knex.Raw {
override fromText(text: string): Knex.Raw {
return this.knex.raw('st_geomfromtext(?)', text);
}
asGeoJSON(table: string, column: string): Knex.Raw {
return this.knex.raw('st_asgeojson(??.??) as ??', [table, column, column]);
}

View File

@@ -4,36 +4,44 @@ import type { GeoJSONGeometry } from 'wellknown';
import { GeometryHelper } from '../types';
export class GeometryHelperOracle extends GeometryHelper {
isTrue(expression: Knex.Raw) {
override isTrue(expression: Knex.Raw) {
return expression.wrap(``, ` = 'TRUE'`);
}
isFalse(expression: Knex.Raw) {
override isFalse(expression: Knex.Raw) {
return expression.wrap(``, ` = 'FALSE'`);
}
createColumn(table: Knex.CreateTableBuilder, field: RawField | Field) {
override createColumn(table: Knex.CreateTableBuilder, field: RawField | Field) {
if (field.type.split('.')[1]) {
field.meta!.special = [field.type];
}
return table.specificType(field.field, 'sdo_geometry');
}
asText(table: string, column: string): Knex.Raw {
override asText(table: string, column: string): Knex.Raw {
return this.knex.raw('sdo_util.to_wktgeometry(??.??) as ??', [table, column, column]);
}
asGeoJSON(table: string, column: string): Knex.Raw {
return this.knex.raw('sdo_util.to_geojson(??.??) as ??', [table, column, column]);
}
fromText(text: string): Knex.Raw {
override fromText(text: string): Knex.Raw {
return this.knex.raw('sdo_geometry(?, 4326)', text);
}
_intersects(key: string, geojson: GeoJSONGeometry): Knex.Raw {
override _intersects(key: string, geojson: GeoJSONGeometry): Knex.Raw {
const geometry = this.fromGeoJSON(geojson);
return this.knex.raw(`sdo_overlapbdyintersect(??, ?)`, [key, geometry]);
}
_intersects_bbox(key: string, geojson: GeoJSONGeometry): Knex.Raw {
override _intersects_bbox(key: string, geojson: GeoJSONGeometry): Knex.Raw {
const geometry = this.fromGeoJSON(geojson);
return this.knex.raw(`sdo_overlapbdyintersect(sdo_geom.sdo_mbr(??), sdo_geom.sdo_mbr(?))`, [key, geometry]);
}
collect(table: string, column: string): Knex.Raw {
override collect(table: string, column: string): Knex.Raw {
return this.knex.raw(`concat('geometrycollection(', listagg(?, ', '), ')'`, this.asText(table, column));
}
}

View File

@@ -4,18 +4,21 @@ import type { GeoJSONGeometry } from 'wellknown';
import { GeometryHelper } from '../types';
export class GeometryHelperPostgres extends GeometryHelper {
async supported() {
override async supported() {
const res = await this.knex.select('oid').from('pg_proc').where({ proname: 'postgis_version' });
return res.length > 0;
}
createColumn(table: Knex.CreateTableBuilder, field: RawField | Field) {
override createColumn(table: Knex.CreateTableBuilder, field: RawField | Field) {
const type = field.type.split('.')[1] ?? 'geometry';
return table.specificType(field.field, `geometry(${type}, 4326)`);
}
_intersects_bbox(key: string, geojson: GeoJSONGeometry): Knex.Raw {
override _intersects_bbox(key: string, geojson: GeoJSONGeometry): Knex.Raw {
const geometry = this.fromGeoJSON(geojson);
return this.knex.raw('?? && ?', [key, geometry]);
}
asGeoJSON(table: string, column: string): Knex.Raw {
return this.knex.raw('st_asgeojson(??.??) as ??', [table, column, column]);
}

View File

@@ -3,12 +3,13 @@ import type { Knex } from 'knex';
import { GeometryHelper } from '../types';
export class GeometryHelperRedshift extends GeometryHelper {
createColumn(table: Knex.CreateTableBuilder, field: RawField | Field) {
override createColumn(table: Knex.CreateTableBuilder, field: RawField | Field) {
if (field.type.split('.')[1]) {
field.meta!.special = [field.type];
}
return table.specificType(field.field, 'geometry');
}
asGeoJSON(table: string, column: string): Knex.Raw {
return this.knex.raw('st_asgeojson(??.??) as ??', [table, column, column]);
}

View File

@@ -2,10 +2,11 @@ import type { Knex } from 'knex';
import { GeometryHelper } from '../types';
export class GeometryHelperSQLite extends GeometryHelper {
async supported() {
override async supported() {
const res = await this.knex.select('name').from('pragma_function_list').where({ name: 'spatialite_version' });
return res.length > 0;
}
asGeoJSON(table: string, column: string): Knex.Raw {
return this.knex.raw('asgeojson(??.??) as ??', [table, column, column]);
}

View File

@@ -2,7 +2,7 @@ import type { KNEX_TYPES } from '@directus/shared/constants';
import { Options, SchemaHelper } from '../types';
export class SchemaHelperCockroachDb extends SchemaHelper {
async changeToType(
override async changeToType(
table: string,
column: string,
type: (typeof KNEX_TYPES)[number],
@@ -11,7 +11,7 @@ export class SchemaHelperCockroachDb extends SchemaHelper {
await this.changeToTypeByCopy(table, column, type, options);
}
constraintName(existingName: string): string {
override constraintName(existingName: string): string {
const suffix = '_replaced';
// CockroachDB does not allow for dropping/creating constraints with the same
// name in a single transaction. reference issue #14873

View File

@@ -2,7 +2,7 @@ import type { Knex } from 'knex';
import { SchemaHelper } from '../types';
export class SchemaHelperMSSQL extends SchemaHelper {
applyLimit(rootQuery: Knex.QueryBuilder, limit: number): void {
override applyLimit(rootQuery: Knex.QueryBuilder, limit: number): void {
// The ORDER BY clause is invalid in views, inline functions, derived tables, subqueries,
// and common table expressions, unless TOP, OFFSET or FOR XML is also specified.
if (limit === -1) {
@@ -12,12 +12,12 @@ export class SchemaHelperMSSQL extends SchemaHelper {
}
}
applyOffset(rootQuery: Knex.QueryBuilder, offset: number): void {
override applyOffset(rootQuery: Knex.QueryBuilder, offset: number): void {
rootQuery.offset(offset);
rootQuery.orderBy(1);
}
formatUUID(uuid: string): string {
override formatUUID(uuid: string): string {
return uuid.toUpperCase();
}
}

View File

@@ -3,7 +3,7 @@ import { getDatabaseVersion } from '../../../../database';
import { SchemaHelper } from '../types';
export class SchemaHelperMySQL extends SchemaHelper {
applyMultiRelationalSort(
override applyMultiRelationalSort(
knex: Knex,
dbQuery: Knex.QueryBuilder,
table: string,

View File

@@ -3,7 +3,7 @@ import type { Field, Relation, Type } from '@directus/shared/types';
import { Options, SchemaHelper } from '../types';
export class SchemaHelperOracle extends SchemaHelper {
async changeToType(
override async changeToType(
table: string,
column: string,
type: (typeof KNEX_TYPES)[number],
@@ -12,11 +12,11 @@ export class SchemaHelperOracle extends SchemaHelper {
await this.changeToTypeByCopy(table, column, type, options);
}
castA2oPrimaryKey(): string {
override castA2oPrimaryKey(): string {
return 'CAST(?? AS VARCHAR2(255))';
}
preRelationChange(relation: Partial<Relation>): void {
override preRelationChange(relation: Partial<Relation>): void {
if (relation.collection === relation.related_collection) {
// Constraints are not allowed on self referencing relationships
// Setting NO ACTION throws - ORA-00905: missing keyword
@@ -26,7 +26,7 @@ export class SchemaHelperOracle extends SchemaHelper {
}
}
processFieldType(field: Field): Type {
override processFieldType(field: Field): Type {
if (field.type === 'integer') {
if (field.schema?.numeric_precision === 20) {
return 'bigInteger';

View File

@@ -1,7 +1,7 @@
import { SchemaHelper } from '../types';
export class SchemaHelperSQLite extends SchemaHelper {
async preColumnChange(): Promise<boolean> {
override async preColumnChange(): Promise<boolean> {
const foreignCheckStatus = (await this.knex.raw('PRAGMA foreign_keys'))[0].foreign_keys === 1;
if (foreignCheckStatus) {
@@ -11,7 +11,7 @@ export class SchemaHelperSQLite extends SchemaHelper {
return foreignCheckStatus;
}
async postColumnChange(): Promise<void> {
override async postColumnChange(): Promise<void> {
await this.knex.raw('PRAGMA foreign_keys = ON');
}
}

View File

@@ -38,7 +38,7 @@ export default function getDatabase(): Knex {
break;
case 'oracledb':
if (!env.DB_CONNECT_STRING) {
if (!env['DB_CONNECT_STRING']) {
requiredEnvVars.push('DB_HOST', 'DB_PORT', 'DB_DATABASE', 'DB_USER', 'DB_PASSWORD');
} else {
requiredEnvVars.push('DB_USER', 'DB_PASSWORD', 'DB_CONNECT_STRING');
@@ -54,7 +54,7 @@ export default function getDatabase(): Knex {
}
break;
case 'mssql':
if (!env.DB_TYPE || env.DB_TYPE === 'default') {
if (!env['DB_TYPE'] || env['DB_TYPE'] === 'default') {
requiredEnvVars.push('DB_HOST', 'DB_PORT', 'DB_DATABASE', 'DB_USER', 'DB_PASSWORD');
}
break;
@@ -241,7 +241,7 @@ export async function validateMigrations(): Promise<boolean> {
try {
let migrationFiles = await fse.readdir(path.join(__dirname, 'migrations'));
const customMigrationsPath = path.resolve(env.EXTENSIONS_PATH, 'migrations');
const customMigrationsPath = path.resolve(env['EXTENSIONS_PATH'], 'migrations');
let customMigrationFiles =
((await fse.pathExists(customMigrationsPath)) && (await fse.readdir(customMigrationsPath))) || [];
@@ -297,11 +297,11 @@ async function validateDatabaseCharset(database?: Knex): Promise<void> {
const tables = await database('information_schema.tables')
.select({ name: 'TABLE_NAME', collation: 'TABLE_COLLATION' })
.where({ TABLE_SCHEMA: env.DB_DATABASE });
.where({ TABLE_SCHEMA: env['DB_DATABASE'] });
const columns = await database('information_schema.columns')
.select({ table_name: 'TABLE_NAME', name: 'COLUMN_NAME', collation: 'COLLATION_NAME' })
.where({ TABLE_SCHEMA: env.DB_DATABASE })
.where({ TABLE_SCHEMA: env['DB_DATABASE'] })
.whereNot({ COLLATION_NAME: collation });
let inconsistencies = '';

View File

@@ -66,25 +66,25 @@ export async function down(knex: Knex): Promise<void> {
// Put all data under 'exif' and rename/move keys afterwards
const newMetadata: { exif: Record<string, unknown>; icc?: unknown; iptc?: unknown } = { exif: prevMetadata };
if (newMetadata.exif.ifd0) {
newMetadata.exif.image = newMetadata.exif.ifd0;
delete newMetadata.exif.ifd0;
if (newMetadata.exif['ifd0']) {
newMetadata.exif['image'] = newMetadata.exif['ifd0'];
delete newMetadata.exif['ifd0'];
}
if (newMetadata.exif.ifd1) {
newMetadata.exif.thumbnail = newMetadata.exif.ifd1;
delete newMetadata.exif.ifd1;
if (newMetadata.exif['ifd1']) {
newMetadata.exif['thumbnail'] = newMetadata.exif['ifd1'];
delete newMetadata.exif['ifd1'];
}
if (newMetadata.exif.interop) {
newMetadata.exif.interoperability = newMetadata.exif.interop;
delete newMetadata.exif.interop;
if (newMetadata.exif['interop']) {
newMetadata.exif['interoperability'] = newMetadata.exif['interop'];
delete newMetadata.exif['interop'];
}
if (newMetadata.exif.icc) {
newMetadata.icc = newMetadata.exif.icc;
delete newMetadata.exif.icc;
if (newMetadata.exif['icc']) {
newMetadata.icc = newMetadata.exif['icc'];
delete newMetadata.exif['icc'];
}
if (newMetadata.exif.iptc) {
newMetadata.iptc = newMetadata.exif.iptc;
delete newMetadata.exif.iptc;
if (newMetadata.exif['iptc']) {
newMetadata.iptc = newMetadata.exif['iptc'];
delete newMetadata.exif['iptc'];
}
await knex('directus_files')

View File

@@ -13,7 +13,7 @@ import formatTitle from '@directus/format-title';
export default async function run(database: Knex, direction: 'up' | 'down' | 'latest', log = true): Promise<void> {
let migrationFiles = await fse.readdir(__dirname);
const customMigrationsPath = path.resolve(env.EXTENSIONS_PATH, 'migrations');
const customMigrationsPath = path.resolve(env['EXTENSIONS_PATH'], 'migrations');
let customMigrationFiles =
((await fse.pathExists(customMigrationsPath)) && (await fse.readdir(customMigrationsPath))) || [];

View File

@@ -84,7 +84,7 @@ export default async function runAST(
const payloadService = new PayloadService(collection, { knex, schema });
let items: null | Item | Item[] = await payloadService.processValues('read', rawItems);
if (!items || items.length === 0) return items;
if (!items || (Array.isArray(items) && items.length === 0)) return items;
// Apply the `_in` filters to the nested collection batches
const nestedNodes = applyParentFilters(schema, nestedCollectionNodes, items);
@@ -100,8 +100,8 @@ export default async function runAST(
while (hasMore) {
const node = merge({}, nestedNode, {
query: {
limit: env.RELATIONAL_BATCH_SIZE,
offset: batchCount * env.RELATIONAL_BATCH_SIZE,
limit: env['RELATIONAL_BATCH_SIZE'],
offset: batchCount * env['RELATIONAL_BATCH_SIZE'],
page: null,
},
});
@@ -112,7 +112,7 @@ export default async function runAST(
items = mergeWithParentItems(schema, nestedItems, items, nestedNode);
}
if (!nestedItems || nestedItems.length < env.RELATIONAL_BATCH_SIZE) {
if (!nestedItems || nestedItems.length < env['RELATIONAL_BATCH_SIZE']) {
hasMore = false;
}

View File

@@ -4,6 +4,6 @@ import { requireYAML } from '../../../utils/require-yaml';
const systemData = requireYAML(require.resolve('./collections.yaml'));
export const systemCollectionRows: CollectionMeta[] = systemData.data.map((row: Record<string, any>) => {
return merge({ system: true }, systemData.defaults, row);
export const systemCollectionRows: CollectionMeta[] = systemData['data'].map((row: Record<string, any>) => {
return merge({ system: true }, systemData['defaults'], row);
});

View File

@@ -18,16 +18,16 @@ for (const filepath of fieldData) {
const systemFields = requireYAML(path.resolve(__dirname, filepath));
(systemFields.fields as FieldMeta[]).forEach((field, index) => {
(systemFields['fields'] as FieldMeta[]).forEach((field, index) => {
const systemField = merge({ system: true }, defaults, field, {
collection: systemFields.table,
collection: systemFields['table'],
sort: index + 1,
});
// Dynamically populate auth providers field
if (systemField.collection === 'directus_users' && systemField.field === 'provider') {
getAuthProviders().forEach(({ name }) => {
systemField.options?.choices?.push({
systemField.options?.['choices']?.push({
text: formatTitle(name),
value: name,
});

View File

@@ -14,26 +14,26 @@ describe('env processed values', async () => {
const env = ((await vi.importActual('../src/env')) as { default: Record<string, any> }).default;
test('Number value should be a number', () => {
expect(env.NUMBER).toStrictEqual(1234);
expect(env['NUMBER']).toStrictEqual(1234);
});
test('Number value casted as string should be a string', () => {
expect(env.NUMBER_CAST_AS_STRING).toStrictEqual('1234');
expect(env['NUMBER_CAST_AS_STRING']).toStrictEqual('1234');
});
test('Value casted as regex', () => {
expect(env.REGEX).toBeInstanceOf(RegExp);
expect(env['REGEX']).toBeInstanceOf(RegExp);
});
test('CSV value should be an array', () => {
expect(env.CSV).toStrictEqual(['one', 'two', 'three', 'four']);
expect(env['CSV']).toStrictEqual(['one', 'two', 'three', 'four']);
});
test('CSV value casted as string should be a string', () => {
expect(env.CSV_CAST_AS_STRING).toStrictEqual('one,two,three,four');
expect(env['CSV_CAST_AS_STRING']).toStrictEqual('one,two,three,four');
});
test('Multiple type cast', () => {
expect(env.MULTIPLE).toStrictEqual(['https://example.com', /\.example2\.com$/]);
expect(env['MULTIPLE']).toStrictEqual(['https://example.com', /\.example2\.com$/]);
});
});

View File

@@ -343,7 +343,7 @@ export function refreshEnv(): void {
}
function processConfiguration() {
const configPath = path.resolve(process.env.CONFIG_PATH || defaults.CONFIG_PATH);
const configPath = path.resolve(process.env['CONFIG_PATH'] || defaults['CONFIG_PATH']);
if (fs.existsSync(configPath) === false) return {};

View File

@@ -84,7 +84,7 @@ type Options = {
const defaultOptions: Options = {
schedule: true,
watch: env.EXTENSIONS_AUTO_RELOAD && env.NODE_ENV !== 'development',
watch: env['EXTENSIONS_AUTO_RELOAD'] && env['NODE_ENV'] !== 'development',
};
class ExtensionManager {
@@ -237,7 +237,7 @@ class ExtensionManager {
private async load(): Promise<void> {
try {
await ensureExtensionDirs(env.EXTENSIONS_PATH, NESTED_EXTENSION_TYPES);
await ensureExtensionDirs(env['EXTENSIONS_PATH'], NESTED_EXTENSION_TYPES);
this.extensions = await this.getExtensions();
} catch (err: any) {
@@ -250,7 +250,7 @@ class ExtensionManager {
await this.registerOperations();
await this.registerBundles();
if (env.SERVE_APP) {
if (env['SERVE_APP']) {
this.appExtensions = await this.generateExtensionBundle();
}
@@ -262,7 +262,7 @@ class ExtensionManager {
this.apiEmitter.offAll();
if (env.SERVE_APP) {
if (env['SERVE_APP']) {
this.appExtensions = null;
}
@@ -274,7 +274,7 @@ class ExtensionManager {
logger.info('Watching extensions for changes...');
const localExtensionPaths = NESTED_EXTENSION_TYPES.flatMap((type) => {
const typeDir = path.posix.join(pathToRelativeUrl(env.EXTENSIONS_PATH), pluralize(type));
const typeDir = path.posix.join(pathToRelativeUrl(env['EXTENSIONS_PATH']), pluralize(type));
if (isIn(type, HYBRID_EXTENSION_TYPES)) {
return [path.posix.join(typeDir, '*', 'app.js'), path.posix.join(typeDir, '*', 'api.js')];
@@ -323,12 +323,12 @@ class ExtensionManager {
}
private async getExtensions(): Promise<Extension[]> {
const packageExtensions = await getPackageExtensions(env.PACKAGE_FILE_LOCATION);
const localPackageExtensions = await resolvePackageExtensions(env.EXTENSIONS_PATH);
const localExtensions = await getLocalExtensions(env.EXTENSIONS_PATH);
const packageExtensions = await getPackageExtensions(env['PACKAGE_FILE_LOCATION']);
const localPackageExtensions = await resolvePackageExtensions(env['EXTENSIONS_PATH']);
const localExtensions = await getLocalExtensions(env['EXTENSIONS_PATH']);
return [...packageExtensions, ...localPackageExtensions, ...localExtensions].filter(
(extension) => env.SERVE_APP || APP_EXTENSION_TYPES.includes(extension.type as any) === false
(extension) => env['SERVE_APP'] || APP_EXTENSION_TYPES.includes(extension.type as any) === false
);
}
@@ -370,7 +370,7 @@ class ExtensionManager {
const depName = appDir.find((file) => depRegex.test(file));
if (depName) {
const depUrl = new Url(env.PUBLIC_URL).addPath('admin', 'assets', depName);
const depUrl = new Url(env['PUBLIC_URL']).addPath('admin', 'assets', depName);
depsMapping[dep] = depUrl.toString({ rootRelative: true });
} else {

View File

@@ -76,7 +76,7 @@ class FlowManager {
const messenger = getMessenger();
messenger.subscribe('flows', (event) => {
if (event.type === 'reload') {
if (event['type'] === 'reload') {
this.reloadQueue.enqueue(async () => {
if (this.isLoaded) {
await this.unload();
@@ -146,13 +146,13 @@ class FlowManager {
if (flow.trigger === 'event') {
let events: string[] = [];
if (flow.options?.scope) {
events = toArray(flow.options.scope)
if (flow.options?.['scope']) {
events = toArray(flow.options['scope'])
.map((scope: string) => {
if (['items.create', 'items.update', 'items.delete'].includes(scope)) {
if (!flow.options?.collections) return [];
if (!flow.options?.['collections']) return [];
return toArray(flow.options.collections).map((collection: string) => {
return toArray(flow.options['collections']).map((collection: string) => {
if (collection.startsWith('directus_')) {
const action = scope.split('.')[1];
return collection.substring(9) + '.' + action;
@@ -167,15 +167,15 @@ class FlowManager {
.flat();
}
if (flow.options.type === 'filter') {
if (flow.options['type'] === 'filter') {
const handler: FilterHandler = (payload, meta, context) =>
this.executeFlow(
flow,
{ payload, ...meta },
{
accountability: context.accountability,
database: context.database,
getSchema: context.schema ? () => context.schema : getSchema,
accountability: context['accountability'],
database: context['database'],
getSchema: context['schema'] ? () => context['schema'] : getSchema,
}
);
@@ -184,12 +184,12 @@ class FlowManager {
id: flow.id,
events: events.map((event) => ({ type: 'filter', name: event, handler })),
});
} else if (flow.options.type === 'action') {
} else if (flow.options['type'] === 'action') {
const handler: ActionHandler = (meta, context) =>
this.executeFlow(flow, meta, {
accountability: context.accountability,
accountability: context['accountability'],
database: getDatabase(),
getSchema: context.schema ? () => context.schema : getSchema,
getSchema: context['schema'] ? () => context['schema'] : getSchema,
});
events.forEach((event) => emitter.onAction(event, handler));
@@ -199,8 +199,8 @@ class FlowManager {
});
}
} else if (flow.trigger === 'schedule') {
if (validate(flow.options.cron)) {
const task = schedule(flow.options.cron, async () => {
if (validate(flow.options['cron'])) {
const task = schedule(flow.options['cron'], async () => {
try {
await this.executeFlow(flow);
} catch (error: any) {
@@ -210,7 +210,7 @@ class FlowManager {
this.triggerHandlers.push({ id: flow.id, events: [{ type: flow.trigger, task }] });
} else {
logger.warn(`Couldn't register cron trigger. Provided cron is invalid: ${flow.options.cron}`);
logger.warn(`Couldn't register cron trigger. Provided cron is invalid: ${flow.options['cron']}`);
}
} else if (flow.trigger === 'operation') {
const handler = (data: unknown, context: Record<string, unknown>) => this.executeFlow(flow, data, context);
@@ -218,23 +218,24 @@ class FlowManager {
this.operationFlowHandlers[flow.id] = handler;
} else if (flow.trigger === 'webhook') {
const handler = (data: unknown, context: Record<string, unknown>) => {
if (flow.options.async) {
if (flow.options['async']) {
this.executeFlow(flow, data, context);
return undefined;
} else {
return this.executeFlow(flow, data, context);
}
};
const method = flow.options?.method ?? 'GET';
const method = flow.options?.['method'] ?? 'GET';
// Default return to $last for webhooks
flow.options.return = flow.options.return ?? '$last';
flow.options['return'] = flow.options['return'] ?? '$last';
this.webhookFlowHandlers[`${method}-${flow.id}`] = handler;
} else if (flow.trigger === 'manual') {
const handler = (data: unknown, context: Record<string, unknown>) => {
const enabledCollections = flow.options?.collections ?? [];
const targetCollection = (data as Record<string, any>)?.body.collection;
const enabledCollections = flow.options?.['collections'] ?? [];
const targetCollection = (data as Record<string, any>)?.['body'].collection;
if (!targetCollection) {
logger.warn(`Manual trigger requires "collection" to be specified in the payload`);
@@ -251,15 +252,16 @@ class FlowManager {
throw new exceptions.ForbiddenException();
}
if (flow.options.async) {
if (flow.options['async']) {
this.executeFlow(flow, data, context);
return undefined;
} else {
return this.executeFlow(flow, data, context);
}
};
// Default return to $last for manual
flow.options.return = '$last';
flow.options['return'] = '$last';
this.webhookFlowHandlers[`POST-${flow.id}`] = handler;
}
@@ -293,14 +295,14 @@ class FlowManager {
}
private async executeFlow(flow: Flow, data: unknown = null, context: Record<string, unknown> = {}): Promise<unknown> {
const database = (context.database as Knex) ?? getDatabase();
const schema = (context.schema as SchemaOverview) ?? (await getSchema({ database }));
const database = (context['database'] as Knex) ?? getDatabase();
const schema = (context['schema'] as SchemaOverview) ?? (await getSchema({ database }));
const keyedData: Record<string, unknown> = {
[TRIGGER_KEY]: data,
[LAST_KEY]: data,
[ACCOUNTABILITY_KEY]: context?.accountability ?? null,
[ENV_KEY]: pick(env, env.FLOWS_ENV_ALLOW_LIST ? toArray(env.FLOWS_ENV_ALLOW_LIST) : []),
[ACCOUNTABILITY_KEY]: context?.['accountability'] ?? null,
[ENV_KEY]: pick(env, env['FLOWS_ENV_ALLOW_LIST'] ? toArray(env['FLOWS_ENV_ALLOW_LIST']) : []),
};
let nextOperation = flow.operation;
@@ -330,7 +332,7 @@ class FlowManager {
schema: schema,
});
const accountability = context?.accountability as Accountability | undefined;
const accountability = context?.['accountability'] as Accountability | undefined;
const activity = await activityService.createOne({
action: Action.RUN,
@@ -360,14 +362,14 @@ class FlowManager {
}
}
if (flow.trigger === 'event' && flow.options.type === 'filter' && lastOperationStatus === 'reject') {
if (flow.trigger === 'event' && flow.options['type'] === 'filter' && lastOperationStatus === 'reject') {
throw keyedData[LAST_KEY];
}
if (flow.options.return === '$all') {
if (flow.options['return'] === '$all') {
return keyedData;
} else if (flow.options.return) {
return get(keyedData, flow.options.return);
} else if (flow.options['return']) {
return get(keyedData, flow.options['return']);
}
return undefined;

View File

@@ -9,21 +9,21 @@ import { getConfigFromEnv } from './utils/get-config-from-env';
import { redactHeaderCookie } from './utils/redact-header-cookies';
const pinoOptions: LoggerOptions = {
level: env.LOG_LEVEL || 'info',
level: env['LOG_LEVEL'] || 'info',
redact: {
paths: ['req.headers.authorization', `req.cookies.${env.REFRESH_TOKEN_COOKIE_NAME}`],
paths: ['req.headers.authorization', `req.cookies.${env['REFRESH_TOKEN_COOKIE_NAME']}`],
censor: '--redact--',
},
};
const httpLoggerOptions: LoggerOptions = {
level: env.LOG_LEVEL || 'info',
level: env['LOG_LEVEL'] || 'info',
redact: {
paths: ['req.headers.authorization', `req.cookies.${env.REFRESH_TOKEN_COOKIE_NAME}`],
paths: ['req.headers.authorization', `req.cookies.${env['REFRESH_TOKEN_COOKIE_NAME']}`],
censor: '--redact--',
},
};
if (env.LOG_STYLE !== 'raw') {
if (env['LOG_STYLE'] !== 'raw') {
pinoOptions.transport = {
target: 'pino-pretty',
options: {
@@ -48,10 +48,10 @@ if (env.LOG_STYLE !== 'raw') {
const loggerEnvConfig = getConfigFromEnv('LOGGER_', 'LOGGER_HTTP');
// Expose custom log levels into formatter function
if (loggerEnvConfig.levels) {
if (loggerEnvConfig['levels']) {
const customLogLevels: { [key: string]: string } = {};
for (const el of toArray(loggerEnvConfig.levels)) {
for (const el of toArray(loggerEnvConfig['levels'])) {
const key_val = el.split(':');
customLogLevels[key_val[0].trim()] = key_val[1].trim();
}
@@ -73,7 +73,7 @@ if (loggerEnvConfig.levels) {
},
};
delete loggerEnvConfig.levels;
delete loggerEnvConfig['levels'];
}
const logger = pino(merge(pinoOptions, loggerEnvConfig));
@@ -87,10 +87,10 @@ export const expressLogger = pinoHTTP({
req(request: Request) {
const output = stdSerializers.req(request);
output.url = redactQuery(output.url);
if (output.headers?.cookie) {
output.headers.cookie = redactHeaderCookie(output.headers.cookie, [
if (output.headers?.['cookie']) {
output.headers['cookie'] = redactHeaderCookie(output.headers['cookie'], [
'access_token',
`${env.REFRESH_TOKEN_COOKIE_NAME}`,
`${env['REFRESH_TOKEN_COOKIE_NAME']}`,
]);
}
return output;
@@ -99,7 +99,7 @@ export const expressLogger = pinoHTTP({
if (response.headers?.['set-cookie']) {
response.headers['set-cookie'] = redactHeaderCookie(response.headers['set-cookie'], [
'access_token',
`${env.REFRESH_TOKEN_COOKIE_NAME}`,
`${env['REFRESH_TOKEN_COOKIE_NAME']}`,
]);
}
return response;

View File

@@ -8,13 +8,13 @@ let transporter: Transporter;
export default function getMailer(): Transporter {
if (transporter) return transporter;
const transportName = env.EMAIL_TRANSPORT.toLowerCase();
const transportName = env['EMAIL_TRANSPORT'].toLowerCase();
if (transportName === 'sendmail') {
transporter = nodemailer.createTransport({
sendmail: true,
newline: env.EMAIL_SENDMAIL_NEW_LINE || 'unix',
path: env.EMAIL_SENDMAIL_PATH || '/usr/sbin/sendmail',
newline: env['EMAIL_SENDMAIL_NEW_LINE'] || 'unix',
path: env['EMAIL_SENDMAIL_PATH'] || '/usr/sbin/sendmail',
});
} else if (transportName === 'ses') {
const aws = require('@aws-sdk/client-ses');
@@ -29,22 +29,22 @@ export default function getMailer(): Transporter {
} else if (transportName === 'smtp') {
let auth: boolean | { user?: string; pass?: string } = false;
if (env.EMAIL_SMTP_USER || env.EMAIL_SMTP_PASSWORD) {
if (env['EMAIL_SMTP_USER'] || env['EMAIL_SMTP_PASSWORD']) {
auth = {
user: env.EMAIL_SMTP_USER,
pass: env.EMAIL_SMTP_PASSWORD,
user: env['EMAIL_SMTP_USER'],
pass: env['EMAIL_SMTP_PASSWORD'],
};
}
const tls: Record<string, unknown> = getConfigFromEnv('EMAIL_SMTP_TLS_');
transporter = nodemailer.createTransport({
name: env.EMAIL_SMTP_NAME,
pool: env.EMAIL_SMTP_POOL,
host: env.EMAIL_SMTP_HOST,
port: env.EMAIL_SMTP_PORT,
secure: env.EMAIL_SMTP_SECURE,
ignoreTLS: env.EMAIL_SMTP_IGNORE_TLS,
name: env['EMAIL_SMTP_NAME'],
pool: env['EMAIL_SMTP_POOL'],
host: env['EMAIL_SMTP_HOST'],
port: env['EMAIL_SMTP_PORT'],
secure: env['EMAIL_SMTP_SECURE'],
ignoreTLS: env['EMAIL_SMTP_IGNORE_TLS'],
auth,
tls,
} as Record<string, unknown>);
@@ -53,17 +53,17 @@ export default function getMailer(): Transporter {
transporter = nodemailer.createTransport(
mg({
auth: {
api_key: env.EMAIL_MAILGUN_API_KEY,
domain: env.EMAIL_MAILGUN_DOMAIN,
api_key: env['EMAIL_MAILGUN_API_KEY'],
domain: env['EMAIL_MAILGUN_DOMAIN'],
},
host: env.EMAIL_MAILGUN_HOST || 'api.mailgun.net',
host: env['EMAIL_MAILGUN_HOST'] || 'api.mailgun.net',
}) as any
);
} else if (transportName === 'sendgrid') {
const sg = require('nodemailer-sendgrid');
transporter = nodemailer.createTransport(
sg({
apiKey: env.EMAIL_SENDGRID_API_KEY,
apiKey: env['EMAIL_SENDGRID_API_KEY'],
}) as any
);
} else {

View File

@@ -40,9 +40,9 @@ export class MessengerRedis implements Messenger {
constructor() {
const config = getConfigFromEnv('MESSENGER_REDIS');
this.pub = new IORedis(env.MESSENGER_REDIS ?? config);
this.sub = new IORedis(env.MESSENGER_REDIS ?? config);
this.namespace = env.MESSENGER_NAMESPACE ?? 'directus';
this.pub = new IORedis(env['MESSENGER_REDIS'] ?? config);
this.sub = new IORedis(env['MESSENGER_REDIS'] ?? config);
this.namespace = env['MESSENGER_NAMESPACE'] ?? 'directus';
}
publish(channel: string, payload: Record<string, any>) {
@@ -71,7 +71,7 @@ let messenger: Messenger;
export function getMessenger() {
if (messenger) return messenger;
if (env.MESSENGER_STORE === 'redis') {
if (env['MESSENGER_STORE'] === 'redis') {
messenger = new MessengerRedis();
} else {
messenger = new MessengerMemory();

View File

@@ -96,7 +96,7 @@ test('Sets accountability to payload contents if valid token is passed', async (
share,
share_scope: shareScope,
},
env.SECRET,
env['SECRET'],
{ issuer: 'directus' }
);
@@ -145,7 +145,7 @@ test('Sets accountability to payload contents if valid token is passed', async (
share,
share_scope: shareScope,
},
env.SECRET,
env['SECRET'],
{ issuer: 'directus' }
);

View File

@@ -52,7 +52,7 @@ export const handler = async (req: Request, res: Response, next: NextFunction) =
if (req.token) {
if (isDirectusJWT(req.token)) {
const payload = verifyAccessJWT(req.token, env.SECRET);
const payload = verifyAccessJWT(req.token, env['SECRET']);
req.accountability.role = payload.role;
req.accountability.admin = payload.admin_access === true || payload.admin_access == 1;

View File

@@ -11,11 +11,11 @@ const checkCacheMiddleware: RequestHandler = asyncHandler(async (req, res, next)
const { cache } = getCache();
if (req.method.toLowerCase() !== 'get' && req.originalUrl?.startsWith('/graphql') === false) return next();
if (env.CACHE_ENABLED !== true) return next();
if (env['CACHE_ENABLED'] !== true) return next();
if (!cache) return next();
if (shouldSkipCache(req)) {
if (env.CACHE_STATUS_HEADER) res.setHeader(`${env.CACHE_STATUS_HEADER}`, 'MISS');
if (env['CACHE_STATUS_HEADER']) res.setHeader(`${env['CACHE_STATUS_HEADER']}`, 'MISS');
return next();
}
@@ -27,7 +27,7 @@ const checkCacheMiddleware: RequestHandler = asyncHandler(async (req, res, next)
cachedData = await getCacheValue(cache, key);
} catch (err: any) {
logger.warn(err, `[cache] Couldn't read key ${key}. ${err.message}`);
if (env.CACHE_STATUS_HEADER) res.setHeader(`${env.CACHE_STATUS_HEADER}`, 'MISS');
if (env['CACHE_STATUS_HEADER']) res.setHeader(`${env['CACHE_STATUS_HEADER']}`, 'MISS');
return next();
}
@@ -38,7 +38,7 @@ const checkCacheMiddleware: RequestHandler = asyncHandler(async (req, res, next)
cacheExpiryDate = (await getCacheValue(cache, `${key}__expires_at`))?.exp;
} catch (err: any) {
logger.warn(err, `[cache] Couldn't read key ${`${key}__expires_at`}. ${err.message}`);
if (env.CACHE_STATUS_HEADER) res.setHeader(`${env.CACHE_STATUS_HEADER}`, 'MISS');
if (env['CACHE_STATUS_HEADER']) res.setHeader(`${env['CACHE_STATUS_HEADER']}`, 'MISS');
return next();
}
@@ -46,11 +46,11 @@ const checkCacheMiddleware: RequestHandler = asyncHandler(async (req, res, next)
res.setHeader('Cache-Control', getCacheControlHeader(req, cacheTTL, true, true));
res.setHeader('Vary', 'Origin, Cache-Control');
if (env.CACHE_STATUS_HEADER) res.setHeader(`${env.CACHE_STATUS_HEADER}`, 'HIT');
if (env['CACHE_STATUS_HEADER']) res.setHeader(`${env['CACHE_STATUS_HEADER']}`, 'HIT');
return res.json(cachedData);
} else {
if (env.CACHE_STATUS_HEADER) res.setHeader(`${env.CACHE_STATUS_HEADER}`, 'MISS');
if (env['CACHE_STATUS_HEADER']) res.setHeader(`${env['CACHE_STATUS_HEADER']}`, 'MISS');
return next();
}
});

View File

@@ -8,13 +8,13 @@ import { ForbiddenException } from '../exceptions';
import asyncHandler from '../utils/async-handler';
const collectionExists: RequestHandler = asyncHandler(async (req, res, next) => {
if (!req.params.collection) return next();
if (!req.params['collection']) return next();
if (req.params.collection in req.schema.collections === false) {
if (req.params['collection'] in req.schema.collections === false) {
throw new ForbiddenException();
}
req.collection = req.params.collection;
req.collection = req.params['collection'];
if (req.collection.startsWith('directus_')) {
const systemRow = systemCollectionRows.find((collection) => {

View File

@@ -4,14 +4,14 @@ import env from '../env';
let corsMiddleware: RequestHandler = (req, res, next) => next();
if (env.CORS_ENABLED === true) {
if (env['CORS_ENABLED'] === true) {
corsMiddleware = cors({
origin: env.CORS_ORIGIN || true,
methods: env.CORS_METHODS || 'GET,POST,PATCH,DELETE',
allowedHeaders: env.CORS_ALLOWED_HEADERS,
exposedHeaders: env.CORS_EXPOSED_HEADERS,
credentials: env.CORS_CREDENTIALS || undefined,
maxAge: env.CORS_MAX_AGE || undefined,
origin: env['CORS_ORIGIN'] || true,
methods: env['CORS_METHODS'] || 'GET,POST,PATCH,DELETE',
allowedHeaders: env['CORS_ALLOWED_HEADERS'],
exposedHeaders: env['CORS_EXPOSED_HEADERS'],
credentials: env['CORS_CREDENTIALS'] || undefined,
maxAge: env['CORS_MAX_AGE'] || undefined,
});
}

View File

@@ -33,7 +33,7 @@ const errorHandler: ErrorRequestHandler = (err, req, res, _next) => {
}
for (const err of errors) {
if (env.NODE_ENV === 'development') {
if (env['NODE_ENV'] === 'development') {
err.extensions = {
...(err.extensions || {}),
stack: err.stack,
@@ -54,7 +54,7 @@ const errorHandler: ErrorRequestHandler = (err, req, res, _next) => {
});
if (err instanceof MethodNotAllowedException) {
res.header('Allow', err.extensions.allow.join(', '));
res.header('Allow', err.extensions['allow'].join(', '));
}
} else {
logger.error(err);

View File

@@ -12,8 +12,8 @@ import type { RequestHandler } from 'express';
const extractToken: RequestHandler = (req, res, next) => {
let token: string | null = null;
if (req.query && req.query.access_token) {
token = req.query.access_token as string;
if (req.query && req.query['access_token']) {
token = req.query['access_token'] as string;
}
if (req.headers && req.headers.authorization) {

View File

@@ -16,11 +16,11 @@ export const parseGraphQL: RequestHandler = asyncHandler(async (req, res, next)
let document: DocumentNode;
if (req.method === 'GET') {
query = (req.query.query as string | undefined) || null;
query = (req.query['query'] as string | undefined) || null;
if (req.query.variables) {
if (req.query['variables']) {
try {
variables = parseJSON(req.query.variables as string);
variables = parseJSON(req.query['variables'] as string);
} catch {
throw new InvalidQueryException(`Variables are invalid JSON.`);
}
@@ -28,7 +28,7 @@ export const parseGraphQL: RequestHandler = asyncHandler(async (req, res, next)
variables = {};
}
operationName = (req.query.operationName as string | undefined) || null;
operationName = (req.query['operationName'] as string | undefined) || null;
} else {
query = req.body.query || null;
variables = req.body.variables || null;
@@ -58,10 +58,16 @@ export const parseGraphQL: RequestHandler = asyncHandler(async (req, res, next)
// Prevent caching responses when mutations are made
if (operationAST?.operation === 'mutation') {
res.locals.cache = false;
res.locals['cache'] = false;
}
res.locals.graphqlParams = { document, query, variables, operationName, contextValue: { req, res } } as GraphQLParams;
res.locals['graphqlParams'] = {
document,
query,
variables,
operationName,
contextValue: { req, res },
} as GraphQLParams;
return next();
});

View File

@@ -13,7 +13,7 @@ const RATE_LIMITER_GLOBAL_KEY = 'global-rate-limit';
let checkRateLimit: RequestHandler = (_req, _res, next) => next();
export let rateLimiterGlobal: RateLimiterRedis | RateLimiterMemcache | RateLimiterMemory;
if (env.RATE_LIMITER_GLOBAL_ENABLED === true) {
if (env['RATE_LIMITER_GLOBAL_ENABLED'] === true) {
validateEnv(['RATE_LIMITER_GLOBAL_STORE', 'RATE_LIMITER_GLOBAL_DURATION', 'RATE_LIMITER_GLOBAL_POINTS']);
validateConfiguration();
@@ -27,7 +27,7 @@ if (env.RATE_LIMITER_GLOBAL_ENABLED === true) {
res.set('Retry-After', String(Math.round(rateLimiterRes.msBeforeNext / 1000)));
throw new HitRateLimitException(`Too many requests, retry after ${ms(rateLimiterRes.msBeforeNext)}.`, {
limit: +env.RATE_LIMITER_GLOBAL_POINTS,
limit: +env['RATE_LIMITER_GLOBAL_POINTS'],
reset: new Date(Date.now() + rateLimiterRes.msBeforeNext),
});
}
@@ -39,13 +39,13 @@ if (env.RATE_LIMITER_GLOBAL_ENABLED === true) {
export default checkRateLimit;
function validateConfiguration() {
if (env.RATE_LIMITER_ENABLED !== true) {
if (env['RATE_LIMITER_ENABLED'] !== true) {
logger.error(`The IP based rate limiter needs to be enabled when using the global rate limiter.`);
process.exit(1);
}
const globalPointsPerSec =
Number(env.RATE_LIMITER_GLOBAL_POINTS) / Math.max(Number(env.RATE_LIMITER_GLOBAL_DURATION), 1);
const regularPointsPerSec = Number(env.RATE_LIMITER_POINTS) / Math.max(Number(env.RATE_LIMITER_DURATION), 1);
Number(env['RATE_LIMITER_GLOBAL_POINTS']) / Math.max(Number(env['RATE_LIMITER_GLOBAL_DURATION']), 1);
const regularPointsPerSec = Number(env['RATE_LIMITER_POINTS']) / Math.max(Number(env['RATE_LIMITER_DURATION']), 1);
if (globalPointsPerSec <= regularPointsPerSec) {
logger.error(`The global rate limiter needs to allow more requests per second than the IP based rate limiter.`);
process.exit(1);

View File

@@ -11,7 +11,7 @@ import { validateEnv } from '../utils/validate-env';
let checkRateLimit: RequestHandler = (_req, _res, next) => next();
export let rateLimiter: RateLimiterRedis | RateLimiterMemcache | RateLimiterMemory;
if (env.RATE_LIMITER_ENABLED === true) {
if (env['RATE_LIMITER_ENABLED'] === true) {
validateEnv(['RATE_LIMITER_STORE', 'RATE_LIMITER_DURATION', 'RATE_LIMITER_POINTS']);
rateLimiter = createRateLimiter('RATE_LIMITER');
@@ -24,7 +24,7 @@ if (env.RATE_LIMITER_ENABLED === true) {
res.set('Retry-After', String(Math.round(rateLimiterRes.msBeforeNext / 1000)));
throw new HitRateLimitException(`Too many requests, retry after ${ms(rateLimiterRes.msBeforeNext)}.`, {
limit: +env.RATE_LIMITER_POINTS,
limit: +env['RATE_LIMITER_POINTS'],
reset: new Date(Date.now() + rateLimiterRes.msBeforeNext),
});
}

View File

@@ -16,30 +16,30 @@ export const respond: RequestHandler = asyncHandler(async (req, res) => {
let exceedsMaxSize = false;
if (env.CACHE_VALUE_MAX_SIZE !== false) {
const valueSize = res.locals.payload ? stringByteSize(JSON.stringify(res.locals.payload)) : 0;
const maxSize = parseBytesConfiguration(env.CACHE_VALUE_MAX_SIZE);
if (env['CACHE_VALUE_MAX_SIZE'] !== false) {
const valueSize = res.locals['payload'] ? stringByteSize(JSON.stringify(res.locals['payload'])) : 0;
const maxSize = parseBytesConfiguration(env['CACHE_VALUE_MAX_SIZE']);
exceedsMaxSize = valueSize > maxSize;
}
if (
(req.method.toLowerCase() === 'get' || req.originalUrl?.startsWith('/graphql')) &&
env.CACHE_ENABLED === true &&
env['CACHE_ENABLED'] === true &&
cache &&
!req.sanitizedQuery.export &&
res.locals.cache !== false &&
res.locals['cache'] !== false &&
exceedsMaxSize === false
) {
const key = getCacheKey(req);
try {
await setCacheValue(cache, key, res.locals.payload, getMilliseconds(env.CACHE_TTL));
await setCacheValue(cache, `${key}__expires_at`, { exp: Date.now() + getMilliseconds(env.CACHE_TTL, 0) });
await setCacheValue(cache, key, res.locals['payload'], getMilliseconds(env['CACHE_TTL']));
await setCacheValue(cache, `${key}__expires_at`, { exp: Date.now() + getMilliseconds(env['CACHE_TTL'], 0) });
} catch (err: any) {
logger.warn(err, `[cache] Couldn't set key ${key}. ${err}`);
}
res.setHeader('Cache-Control', getCacheControlHeader(req, getMilliseconds(env.CACHE_TTL), true, true));
res.setHeader('Cache-Control', getCacheControlHeader(req, getMilliseconds(env['CACHE_TTL']), true, true));
res.setHeader('Vary', 'Origin, Cache-Control');
} else {
// Don't cache anything by default
@@ -63,32 +63,32 @@ export const respond: RequestHandler = asyncHandler(async (req, res) => {
if (req.sanitizedQuery.export === 'json') {
res.attachment(`${filename}.json`);
res.set('Content-Type', 'application/json');
return res.status(200).send(exportService.transform(res.locals.payload?.data, 'json'));
return res.status(200).send(exportService.transform(res.locals['payload']?.data, 'json'));
}
if (req.sanitizedQuery.export === 'xml') {
res.attachment(`${filename}.xml`);
res.set('Content-Type', 'text/xml');
return res.status(200).send(exportService.transform(res.locals.payload?.data, 'xml'));
return res.status(200).send(exportService.transform(res.locals['payload']?.data, 'xml'));
}
if (req.sanitizedQuery.export === 'csv') {
res.attachment(`${filename}.csv`);
res.set('Content-Type', 'text/csv');
return res.status(200).send(exportService.transform(res.locals.payload?.data, 'csv'));
return res.status(200).send(exportService.transform(res.locals['payload']?.data, 'csv'));
}
if (req.sanitizedQuery.export === 'yaml') {
res.attachment(`${filename}.yaml`);
res.set('Content-Type', 'text/yaml');
return res.status(200).send(exportService.transform(res.locals.payload?.data, 'yaml'));
return res.status(200).send(exportService.transform(res.locals['payload']?.data, 'yaml'));
}
}
if (Buffer.isBuffer(res.locals.payload)) {
return res.end(res.locals.payload);
} else if (res.locals.payload) {
return res.json(res.locals.payload);
if (Buffer.isBuffer(res.locals['payload'])) {
return res.end(res.locals['payload']);
} else if (res.locals['payload']) {
return res.json(res.locals['payload']);
} else {
return res.status(204).end();
}

View File

@@ -13,7 +13,7 @@ const sanitizeQueryMiddleware: RequestHandler = (req, _res, next) => {
req.sanitizedQuery = sanitizeQuery(
{
fields: req.query.fields || '*',
fields: req.query['fields'] || '*',
...req.query,
},
req.accountability || null

View File

@@ -9,10 +9,10 @@ type Options = {
export default defineOperationApi<Options>({
id: 'exec',
handler: async ({ code }, { data, env }) => {
const allowedModules = env.FLOWS_EXEC_ALLOWED_MODULES ? toArray(env.FLOWS_EXEC_ALLOWED_MODULES) : [];
const allowedModules = env['FLOWS_EXEC_ALLOWED_MODULES'] ? toArray(env['FLOWS_EXEC_ALLOWED_MODULES']) : [];
const allowedModulesBuiltIn: string[] = [];
const allowedModulesExternal: string[] = [];
const allowedEnv = data.$env ?? {};
const allowedEnv = data['$env'] ?? {};
const opts: NodeVMOptions = {
eval: false,

View File

@@ -16,7 +16,7 @@ export function createRateLimiter(
configPrefix = 'RATE_LIMITER',
configOverrides?: IRateLimiterOptionsOverrides
): RateLimiterAbstract {
switch (env.RATE_LIMITER_STORE) {
switch (env['RATE_LIMITER_STORE']) {
case 'redis':
return new RateLimiterRedis(getConfig('redis', configPrefix, configOverrides));
case 'memcache':

View File

@@ -4,11 +4,11 @@ import { getEnv } from '../env';
export const validateIP = async (ip: string, url: string) => {
const env = getEnv();
if (env.IMPORT_IP_DENY_LIST.includes(ip)) {
if (env['IMPORT_IP_DENY_LIST'].includes(ip)) {
throw new Error(`Requested URL "${url}" resolves to a denied IP address`);
}
if (env.IMPORT_IP_DENY_LIST.includes('0.0.0.0')) {
if (env['IMPORT_IP_DENY_LIST'].includes('0.0.0.0')) {
const networkInterfaces = os.networkInterfaces();
for (const networkInfo of Object.values(networkInterfaces)) {

View File

@@ -94,7 +94,7 @@ export async function createServer(): Promise<http.Server> {
return server;
async function beforeShutdown() {
if (env.NODE_ENV !== 'development') {
if (env['NODE_ENV'] !== 'development') {
logger.info('Shutting down...');
}
}
@@ -117,7 +117,7 @@ export async function createServer(): Promise<http.Server> {
}
);
if (env.NODE_ENV !== 'development') {
if (env['NODE_ENV'] !== 'development') {
logger.info('Directus shut down OK. Bye bye!');
}
}
@@ -126,8 +126,8 @@ export async function createServer(): Promise<http.Server> {
export async function startServer(): Promise<void> {
const server = await createServer();
const host = env.HOST;
const port = env.PORT;
const host = env['HOST'];
const port = env['PORT'];
server
.listen(port, host, () => {

View File

@@ -23,11 +23,11 @@ export class ActivityService extends ItemsService {
this.usersService = new UsersService({ schema: this.schema });
}
async createOne(data: Partial<Item>, opts?: MutationOptions): Promise<PrimaryKey> {
if (data.action === Action.COMMENT && typeof data.comment === 'string') {
override async createOne(data: Partial<Item>, opts?: MutationOptions): Promise<PrimaryKey> {
if (data['action'] === Action.COMMENT && typeof data['comment'] === 'string') {
const usersRegExp = new RegExp(/@[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}/gi);
const mentions = uniq(data.comment.match(usersRegExp) ?? []);
const mentions = uniq(data['comment'].match(usersRegExp) ?? []);
const sender = await this.usersService.readOne(this.accountability!.user!, {
fields: ['id', 'first_name', 'last_name', 'email'],
@@ -42,9 +42,9 @@ export class ActivityService extends ItemsService {
const accountability: Accountability = {
user: userID,
role: user.role?.id ?? null,
admin: user.role?.admin_access ?? null,
app: user.role?.app_access ?? null,
role: user['role']?.id ?? null,
admin: user['role']?.admin_access ?? null,
app: user['role']?.app_access ?? null,
};
accountability.permissions = await getPermissions(accountability, this.schema);
@@ -53,7 +53,7 @@ export class ActivityService extends ItemsService {
const usersService = new UsersService({ schema: this.schema, accountability });
try {
await authorizationService.checkAccess('read', data.collection, data.item);
await authorizationService.checkAccess('read', data['collection'], data['item']);
const templateData = await usersService.readByQuery({
fields: ['id', 'first_name', 'last_name', 'email'],
@@ -61,11 +61,11 @@ export class ActivityService extends ItemsService {
});
const userPreviews = templateData.reduce((acc, user) => {
acc[user.id] = `<em>${userName(user)}</em>`;
acc[user['id']] = `<em>${userName(user)}</em>`;
return acc;
}, {} as Record<string, string>);
let comment = data.comment;
let comment = data['comment'];
for (const mention of mentions) {
const uuid = mention.substring(1);
@@ -83,18 +83,18 @@ ${userName(sender)} has mentioned you in a comment:
${comment}
<a href="${new Url(env.PUBLIC_URL)
.addPath('admin', 'content', data.collection, data.item)
<a href="${new Url(env['PUBLIC_URL'])
.addPath('admin', 'content', data['collection'], data['item'])
.toString()}">Click here to view.</a>
`;
await this.notificationsService.createOne({
recipient: userID,
sender: sender.id,
subject: `You were mentioned in ${data.collection}`,
sender: sender['id'],
subject: `You were mentioned in ${data['collection']}`,
message,
collection: data.collection,
item: data.item,
collection: data['collection'],
item: data['item'],
});
} catch (err: any) {
if (err instanceof ForbiddenException) {

View File

@@ -145,8 +145,8 @@ export class AssetsService {
if (
!width ||
!height ||
width > env.ASSETS_TRANSFORM_IMAGE_MAX_DIMENSION ||
height > env.ASSETS_TRANSFORM_IMAGE_MAX_DIMENSION
width > env['ASSETS_TRANSFORM_IMAGE_MAX_DIMENSION'] ||
height > env['ASSETS_TRANSFORM_IMAGE_MAX_DIMENSION']
) {
throw new IllegalAssetTransformation(
`Image is too large to be transformed, or image size couldn't be determined.`
@@ -155,7 +155,7 @@ export class AssetsService {
const { queue, process } = sharp.counters();
if (queue + process > env.ASSETS_TRANSFORM_MAX_CONCURRENT) {
if (queue + process > env['ASSETS_TRANSFORM_MAX_CONCURRENT']) {
throw new ServiceUnavailableException('Server too busy', {
service: 'files',
});
@@ -164,13 +164,13 @@ export class AssetsService {
const readStream = await storage.location(file.storage).read(file.filename_disk, range);
const transformer = sharp({
limitInputPixels: Math.pow(env.ASSETS_TRANSFORM_IMAGE_MAX_DIMENSION, 2),
limitInputPixels: Math.pow(env['ASSETS_TRANSFORM_IMAGE_MAX_DIMENSION'], 2),
sequentialRead: true,
failOn: env.ASSETS_INVALID_IMAGE_SENSITIVITY_LEVEL,
failOn: env['ASSETS_INVALID_IMAGE_SENSITIVITY_LEVEL'],
});
transformer.timeout({
seconds: clamp(Math.round(getMilliseconds(env.ASSETS_TRANSFORM_TIMEOUT, 0) / 1000), 1, 3600),
seconds: clamp(Math.round(getMilliseconds(env['ASSETS_TRANSFORM_TIMEOUT'], 0) / 1000), 1, 3600),
});
if (transforms.find((transform) => transform[0] === 'rotate') === undefined) transformer.rotate();

View File

@@ -50,7 +50,7 @@ export class AuthenticationService {
): Promise<LoginResult> {
const { nanoid } = await import('nanoid');
const STALL_TIME = env.LOGIN_STALL_TIME;
const STALL_TIME = env['LOGIN_STALL_TIME'];
const timeStart = performance.now();
const provider = getAuthProvider(providerName);
@@ -203,13 +203,13 @@ export class AuthenticationService {
}
);
const accessToken = jwt.sign(customClaims, env.SECRET as string, {
expiresIn: env.ACCESS_TOKEN_TTL,
const accessToken = jwt.sign(customClaims, env['SECRET'] as string, {
expiresIn: env['ACCESS_TOKEN_TTL'],
issuer: 'directus',
});
const refreshToken = nanoid(64);
const refreshTokenExpiration = new Date(Date.now() + getMilliseconds(env.REFRESH_TOKEN_TTL, 0));
const refreshTokenExpiration = new Date(Date.now() + getMilliseconds(env['REFRESH_TOKEN_TTL'], 0));
await this.knex('directus_sessions').insert({
token: refreshToken,
@@ -247,7 +247,7 @@ export class AuthenticationService {
return {
accessToken,
refreshToken,
expires: getMilliseconds(env.ACCESS_TOKEN_TTL),
expires: getMilliseconds(env['ACCESS_TOKEN_TTL']),
id: user.id,
};
}
@@ -358,13 +358,13 @@ export class AuthenticationService {
}
);
const accessToken = jwt.sign(customClaims, env.SECRET as string, {
expiresIn: env.ACCESS_TOKEN_TTL,
const accessToken = jwt.sign(customClaims, env['SECRET'] as string, {
expiresIn: env['ACCESS_TOKEN_TTL'],
issuer: 'directus',
});
const newRefreshToken = nanoid(64);
const refreshTokenExpiration = new Date(Date.now() + getMilliseconds(env.REFRESH_TOKEN_TTL, 0));
const refreshTokenExpiration = new Date(Date.now() + getMilliseconds(env['REFRESH_TOKEN_TTL'], 0));
await this.knex('directus_sessions')
.update({
@@ -380,7 +380,7 @@ export class AuthenticationService {
return {
accessToken,
refreshToken: newRefreshToken,
expires: getMilliseconds(env.ACCESS_TOKEN_TTL),
expires: getMilliseconds(env['ACCESS_TOKEN_TTL']),
id: record.user_id,
};
}

View File

@@ -164,7 +164,7 @@ export class CollectionsService {
return payload.collection;
} finally {
if (this.cache && env.CACHE_AUTO_PURGE && opts?.autoPurgeCache !== false) {
if (this.cache && env['CACHE_AUTO_PURGE'] && opts?.autoPurgeCache !== false) {
await this.cache.clear();
}
@@ -213,7 +213,7 @@ export class CollectionsService {
return collections;
} finally {
if (this.cache && env.CACHE_AUTO_PURGE && opts?.autoPurgeCache !== false) {
if (this.cache && env['CACHE_AUTO_PURGE'] && opts?.autoPurgeCache !== false) {
await this.cache.clear();
}
@@ -306,8 +306,8 @@ export class CollectionsService {
}
}
if (env.DB_EXCLUDE_TABLES) {
return collections.filter((collection) => env.DB_EXCLUDE_TABLES.includes(collection.collection) === false);
if (env['DB_EXCLUDE_TABLES']) {
return collections.filter((collection) => env['DB_EXCLUDE_TABLES'].includes(collection.collection) === false);
}
return collections;
@@ -396,7 +396,7 @@ export class CollectionsService {
return collectionKey;
} finally {
if (this.cache && env.CACHE_AUTO_PURGE && opts?.autoPurgeCache !== false) {
if (this.cache && env['CACHE_AUTO_PURGE'] && opts?.autoPurgeCache !== false) {
await this.cache.clear();
}
@@ -452,7 +452,7 @@ export class CollectionsService {
}
});
} finally {
if (this.cache && env.CACHE_AUTO_PURGE && opts?.autoPurgeCache !== false) {
if (this.cache && env['CACHE_AUTO_PURGE'] && opts?.autoPurgeCache !== false) {
await this.cache.clear();
}
@@ -502,7 +502,7 @@ export class CollectionsService {
return collectionKeys;
} finally {
if (this.cache && env.CACHE_AUTO_PURGE && opts?.autoPurgeCache !== false) {
if (this.cache && env['CACHE_AUTO_PURGE'] && opts?.autoPurgeCache !== false) {
await this.cache.clear();
}
@@ -631,7 +631,7 @@ export class CollectionsService {
return collectionKey;
} finally {
if (this.cache && env.CACHE_AUTO_PURGE && opts?.autoPurgeCache !== false) {
if (this.cache && env['CACHE_AUTO_PURGE'] && opts?.autoPurgeCache !== false) {
await this.cache.clear();
}
@@ -679,7 +679,7 @@ export class CollectionsService {
return collectionKeys;
} finally {
if (this.cache && env.CACHE_AUTO_PURGE && opts?.autoPurgeCache !== false) {
if (this.cache && env['CACHE_AUTO_PURGE'] && opts?.autoPurgeCache !== false) {
await this.cache.clear();
}

View File

@@ -337,7 +337,7 @@ export class FieldsService {
await this.helpers.schema.postColumnChange();
}
if (this.cache && env.CACHE_AUTO_PURGE && opts?.autoPurgeCache !== false) {
if (this.cache && env['CACHE_AUTO_PURGE'] && opts?.autoPurgeCache !== false) {
await this.cache.clear();
}
@@ -456,7 +456,7 @@ export class FieldsService {
await this.helpers.schema.postColumnChange();
}
if (this.cache && env.CACHE_AUTO_PURGE && opts?.autoPurgeCache !== false) {
if (this.cache && env['CACHE_AUTO_PURGE'] && opts?.autoPurgeCache !== false) {
await this.cache.clear();
}
@@ -571,11 +571,11 @@ export class FieldsService {
const collectionMetaUpdates: Record<string, null> = {};
if (collectionMeta?.archive_field === field) {
collectionMetaUpdates.archive_field = null;
collectionMetaUpdates['archive_field'] = null;
}
if (collectionMeta?.sort_field === field) {
collectionMetaUpdates.sort_field = null;
collectionMetaUpdates['sort_field'] = null;
}
if (Object.keys(collectionMetaUpdates).length > 0) {
@@ -622,7 +622,7 @@ export class FieldsService {
await this.helpers.schema.postColumnChange();
}
if (this.cache && env.CACHE_AUTO_PURGE && opts?.autoPurgeCache !== false) {
if (this.cache && env['CACHE_AUTO_PURGE'] && opts?.autoPurgeCache !== false) {
await this.cache.clear();
}

View File

@@ -114,7 +114,7 @@ export class FilesService extends ItemsService {
await sudoService.updateOne(primaryKey, payload, { emitEvents: false });
if (this.cache && env.CACHE_AUTO_PURGE && opts?.autoPurgeCache !== false) {
if (this.cache && env['CACHE_AUTO_PURGE'] && opts?.autoPurgeCache !== false) {
await this.cache.clear();
}
@@ -140,7 +140,7 @@ export class FilesService extends ItemsService {
/**
* Extract metadata from a buffer's content
*/
async getMetadata(stream: Readable, allowList = env.FILE_METADATA_ALLOW_LIST): Promise<Metadata> {
async getMetadata(stream: Readable, allowList = env['FILE_METADATA_ALLOW_LIST']): Promise<Metadata> {
return new Promise((resolve, reject) => {
pipeline(
stream,
@@ -214,14 +214,14 @@ export class FilesService extends ItemsService {
}
}
if (fullMetadata?.iptc?.Caption && typeof fullMetadata.iptc.Caption === 'string') {
metadata.description = fullMetadata.iptc?.Caption;
if (fullMetadata?.iptc?.['Caption'] && typeof fullMetadata.iptc['Caption'] === 'string') {
metadata.description = fullMetadata.iptc?.['Caption'];
}
if (fullMetadata?.iptc?.Headline && typeof fullMetadata.iptc.Headline === 'string') {
metadata.title = fullMetadata.iptc.Headline;
if (fullMetadata?.iptc?.['Headline'] && typeof fullMetadata.iptc['Headline'] === 'string') {
metadata.title = fullMetadata.iptc['Headline'];
}
if (fullMetadata?.iptc?.Keywords) {
metadata.tags = fullMetadata.iptc.Keywords;
if (fullMetadata?.iptc?.['Keywords']) {
metadata.tags = fullMetadata.iptc['Keywords'];
}
if (allowList === '*' || allowList?.[0] === '*') {
@@ -277,7 +277,7 @@ export class FilesService extends ItemsService {
const payload = {
filename_download: filename,
storage: toArray(env.STORAGE_LOCATIONS)[0],
storage: toArray(env['STORAGE_LOCATIONS'])[0],
type: fileResponse.headers['content-type'],
title: formatTitle(filename),
...(body || {}),
@@ -290,7 +290,7 @@ export class FilesService extends ItemsService {
* Create a file (only applicable when it is not a multipart/data POST request)
* Useful for associating metadata with existing file in storage
*/
async createOne(data: Partial<File>, opts?: MutationOptions): Promise<PrimaryKey> {
override async createOne(data: Partial<File>, opts?: MutationOptions): Promise<PrimaryKey> {
if (!data.type) {
throw new InvalidPayloadException(`"type" is required`);
}
@@ -302,7 +302,7 @@ export class FilesService extends ItemsService {
/**
* Delete a file
*/
async deleteOne(key: PrimaryKey, opts?: MutationOptions): Promise<PrimaryKey> {
override async deleteOne(key: PrimaryKey, opts?: MutationOptions): Promise<PrimaryKey> {
await this.deleteMany([key], opts);
return key;
}
@@ -310,7 +310,7 @@ export class FilesService extends ItemsService {
/**
* Delete multiple files
*/
async deleteMany(keys: PrimaryKey[], opts?: MutationOptions): Promise<PrimaryKey[]> {
override async deleteMany(keys: PrimaryKey[], opts?: MutationOptions): Promise<PrimaryKey[]> {
const storage = await getStorage();
const files = await super.readMany(keys, { fields: ['id', 'storage'], limit: -1 });
@@ -321,15 +321,15 @@ export class FilesService extends ItemsService {
await super.deleteMany(keys);
for (const file of files) {
const disk = storage.location(file.storage);
const disk = storage.location(file['storage']);
// Delete file + thumbnails
for await (const filepath of disk.list(file.id)) {
for await (const filepath of disk.list(file['id'])) {
await disk.delete(filepath);
}
}
if (this.cache && env.CACHE_AUTO_PURGE && opts?.autoPurgeCache !== false) {
if (this.cache && env['CACHE_AUTO_PURGE'] && opts?.autoPurgeCache !== false) {
await this.cache.clear();
}

View File

@@ -8,7 +8,7 @@ export class FlowsService extends ItemsService<FlowRaw> {
super('directus_flows', options);
}
async createOne(data: Partial<Item>, opts?: MutationOptions): Promise<PrimaryKey> {
override async createOne(data: Partial<Item>, opts?: MutationOptions): Promise<PrimaryKey> {
const flowManager = getFlowManager();
const result = await super.createOne(data, opts);
@@ -17,7 +17,7 @@ export class FlowsService extends ItemsService<FlowRaw> {
return result;
}
async createMany(data: Partial<Item>[], opts?: MutationOptions): Promise<PrimaryKey[]> {
override async createMany(data: Partial<Item>[], opts?: MutationOptions): Promise<PrimaryKey[]> {
const flowManager = getFlowManager();
const result = await super.createMany(data, opts);
@@ -26,7 +26,7 @@ export class FlowsService extends ItemsService<FlowRaw> {
return result;
}
async updateBatch(data: Partial<Item>[], opts?: MutationOptions): Promise<PrimaryKey[]> {
override async updateBatch(data: Partial<Item>[], opts?: MutationOptions): Promise<PrimaryKey[]> {
const flowManager = getFlowManager();
const result = await super.updateBatch(data, opts);
@@ -35,7 +35,7 @@ export class FlowsService extends ItemsService<FlowRaw> {
return result;
}
async updateMany(keys: PrimaryKey[], data: Partial<Item>, opts?: MutationOptions): Promise<PrimaryKey[]> {
override async updateMany(keys: PrimaryKey[], data: Partial<Item>, opts?: MutationOptions): Promise<PrimaryKey[]> {
const flowManager = getFlowManager();
const result = await super.updateMany(keys, data, opts);
@@ -44,7 +44,7 @@ export class FlowsService extends ItemsService<FlowRaw> {
return result;
}
async deleteMany(keys: PrimaryKey[], opts?: MutationOptions): Promise<PrimaryKey[]> {
override async deleteMany(keys: PrimaryKey[], opts?: MutationOptions): Promise<PrimaryKey[]> {
const flowManager = getFlowManager();
// this is to prevent foreign key constraint error on directus_operations resolve/reject during cascade deletion

View File

@@ -93,7 +93,7 @@ import processError from './utils/process-error';
const validationRules = Array.from(specifiedRules);
if (env.GRAPHQL_INTROSPECTION === false) {
if (env['GRAPHQL_INTROSPECTION'] === false) {
validationRules.push(NoSchemaIntrospectionCustomRule);
}
@@ -158,9 +158,10 @@ export class GraphQLService {
const formattedResult: FormattedExecutionResult = {};
if (result.data) formattedResult.data = result.data;
if (result.errors) formattedResult.errors = result.errors.map((error) => processError(this.accountability, error));
if (result.extensions) formattedResult.extensions = result.extensions;
if (result['data']) formattedResult.data = result['data'];
if (result['errors'])
formattedResult.errors = result['errors'].map((error) => processError(this.accountability, error));
if (result['extensions']) formattedResult.extensions = result['extensions'];
return formattedResult;
}
@@ -313,11 +314,11 @@ export class GraphQLService {
.reduce((acc, collection) => {
const collectionName = this.scope === 'items' ? collection.collection : collection.collection.substring(9);
acc[`delete_${collectionName}_items`] = DeleteCollectionTypes.many.getResolver(
acc[`delete_${collectionName}_items`] = DeleteCollectionTypes['many'].getResolver(
`delete_${collection.collection}_items`
);
acc[`delete_${collectionName}_item`] = DeleteCollectionTypes.one.getResolver(
acc[`delete_${collectionName}_item`] = DeleteCollectionTypes['one'].getResolver(
`delete_${collection.collection}_item`
);
@@ -519,7 +520,7 @@ export class GraphQLService {
path = path.reverse().slice(0, -1);
let parent = context.data;
let parent = context['data'];
for (const pathPart of path) {
parent = parent[pathPart];
@@ -981,7 +982,7 @@ export class GraphQLService {
),
resolve: async ({ info, context }: { info: GraphQLResolveInfo; context: Record<string, any> }) => {
const result = await self.resolveQuery(info);
context.data = result;
context['data'] = result;
return result;
},
};
@@ -1035,7 +1036,7 @@ export class GraphQLService {
},
resolve: async ({ info, context }: { info: GraphQLResolveInfo; context: Record<string, any> }) => {
const result = await self.resolveQuery(info);
context.data = result;
context['data'] = result;
return result;
},
@@ -1050,7 +1051,7 @@ export class GraphQLService {
},
resolve: async ({ info, context }: { info: GraphQLResolveInfo; context: Record<string, any> }) => {
const result = await self.resolveQuery(info);
context.data = result;
context['data'] = result;
return result;
},
});
@@ -1264,14 +1265,14 @@ export class GraphQLService {
}
}
DeleteCollectionTypes.many = schemaComposer.createObjectTC({
DeleteCollectionTypes['many'] = schemaComposer.createObjectTC({
name: `delete_many`,
fields: {
ids: new GraphQLNonNull(new GraphQLList(GraphQLID)),
},
});
DeleteCollectionTypes.one = schemaComposer.createObjectTC({
DeleteCollectionTypes['one'] = schemaComposer.createObjectTC({
name: `delete_one`,
fields: {
id: new GraphQLNonNull(GraphQLID),
@@ -1279,9 +1280,9 @@ export class GraphQLService {
});
for (const collection of Object.values(schema.delete.collections)) {
DeleteCollectionTypes.many.addResolver({
DeleteCollectionTypes['many'].addResolver({
name: `delete_${collection.collection}_items`,
type: DeleteCollectionTypes.many,
type: DeleteCollectionTypes['many'],
args: {
ids: new GraphQLNonNull(new GraphQLList(GraphQLID)),
},
@@ -1289,9 +1290,9 @@ export class GraphQLService {
await self.resolveMutation(args, info),
});
DeleteCollectionTypes.one.addResolver({
DeleteCollectionTypes['one'].addResolver({
name: `delete_${collection.collection}_item`,
type: DeleteCollectionTypes.one,
type: DeleteCollectionTypes['one'],
args: {
id: new GraphQLNonNull(GraphQLID),
},
@@ -1330,13 +1331,13 @@ export class GraphQLService {
collection = collection.slice(0, -6);
}
}
if (args.id) {
if (args['id']) {
query.filter = {
_and: [
query.filter || {},
{
[this.schema.collections[collection].primary]: {
_eq: args.id,
_eq: args['id'],
},
},
],
@@ -1354,7 +1355,7 @@ export class GraphQLService {
const result = await this.read(collection, query);
if (args.id) {
if (args['id']) {
return result?.[0] || null;
}
@@ -1362,8 +1363,8 @@ export class GraphQLService {
// for every entry in result add a group field based on query.group;
const aggregateKeys = Object.keys(query.aggregate ?? {});
result.map((field: Item) => {
field.group = omit(field, aggregateKeys);
result['map']((field: Item) => {
field['group'] = omit(field, aggregateKeys);
});
}
@@ -1395,7 +1396,7 @@ export class GraphQLService {
if (collection.endsWith('_item')) collection = collection.slice(0, -5);
if (singleton && action === 'update') {
return await this.upsertSingleton(collection, args.data, query);
return await this.upsertSingleton(collection, args['data'], query);
}
const service = this.getService(collection);
@@ -1404,22 +1405,24 @@ export class GraphQLService {
try {
if (single) {
if (action === 'create') {
const key = await service.createOne(args.data);
const key = await service.createOne(args['data']);
return hasQuery ? await service.readOne(key, query) : true;
}
if (action === 'update') {
const key = await service.updateOne(args.id, args.data);
const key = await service.updateOne(args['id'], args['data']);
return hasQuery ? await service.readOne(key, query) : true;
}
if (action === 'delete') {
await service.deleteOne(args.id);
return { id: args.id };
await service.deleteOne(args['id']);
return { id: args['id'] };
}
return undefined;
} else {
if (action === 'create') {
const keys = await service.createMany(args.data);
const keys = await service.createMany(args['data']);
return hasQuery ? await service.readMany(keys, query) : true;
}
@@ -1427,18 +1430,20 @@ export class GraphQLService {
const keys: PrimaryKey[] = [];
if (batchUpdate) {
keys.push(...(await service.updateBatch(args.data)));
keys.push(...(await service.updateBatch(args['data'])));
} else {
keys.push(...(await service.updateMany(args.ids, args.data)));
keys.push(...(await service.updateMany(args['ids'], args['data'])));
}
return hasQuery ? await service.readMany(keys, query) : true;
}
if (action === 'delete') {
const keys = await service.deleteMany(args.ids);
const keys = await service.deleteMany(args['ids']);
return { ids: keys };
}
return undefined;
}
} catch (err: any) {
return this.formatError(err);
@@ -1490,7 +1495,7 @@ export class GraphQLService {
* of arguments
*/
parseArgs(args: readonly ArgumentNode[], variableValues: GraphQLResolveInfo['variableValues']): Record<string, any> {
if (!args || args.length === 0) return {};
if (!args || args['length'] === 0) return {};
const parse = (node: ValueNode): any => {
switch (node.kind) {
@@ -1515,7 +1520,7 @@ export class GraphQLService {
}
};
const argsObject = Object.fromEntries(args.map((arg) => [arg.name.value, parse(arg.value)]));
const argsObject = Object.fromEntries(args['map']((arg) => [arg.name.value, parse(arg.value)]));
return argsObject;
}
@@ -1712,10 +1717,10 @@ export class GraphQLService {
*/
formatError(error: BaseException | BaseException[]): GraphQLError {
if (Array.isArray(error)) {
error[0].extensions.code = error[0].code;
error[0].extensions['code'] = error[0].code;
return new GraphQLError(error[0].message, undefined, undefined, undefined, undefined, error[0]);
}
error.extensions.code = error.code;
error.extensions['code'] = error.code;
return new GraphQLError(error.message, undefined, undefined, undefined, undefined, error);
}
@@ -1938,7 +1943,7 @@ export class GraphQLService {
const service = new GraphQLService({
schema: this.schema,
accountability: this.accountability,
scope: args.scope ?? 'items',
scope: args['scope'] ?? 'items',
});
return service.getSchema('sdl');
},
@@ -2009,19 +2014,19 @@ export class GraphQLService {
schema: this.schema,
});
const result = await authenticationService.login(DEFAULT_AUTH_PROVIDER, args, args?.otp);
if (args.mode === 'cookie') {
res?.cookie(env.REFRESH_TOKEN_COOKIE_NAME, result.refreshToken, {
if (args['mode'] === 'cookie') {
res?.cookie(env['REFRESH_TOKEN_COOKIE_NAME'], result['refreshToken'], {
httpOnly: true,
domain: env.REFRESH_TOKEN_COOKIE_DOMAIN,
maxAge: getMilliseconds(env.REFRESH_TOKEN_TTL),
secure: env.REFRESH_TOKEN_COOKIE_SECURE ?? false,
sameSite: (env.REFRESH_TOKEN_COOKIE_SAME_SITE as 'lax' | 'strict' | 'none') || 'strict',
domain: env['REFRESH_TOKEN_COOKIE_DOMAIN'],
maxAge: getMilliseconds(env['REFRESH_TOKEN_TTL']),
secure: env['REFRESH_TOKEN_COOKIE_SECURE'] ?? false,
sameSite: (env['REFRESH_TOKEN_COOKIE_SAME_SITE'] as 'lax' | 'strict' | 'none') || 'strict',
});
}
return {
access_token: result.accessToken,
expires: result.expires,
refresh_token: result.refreshToken,
access_token: result['accessToken'],
expires: result['expires'],
refresh_token: result['refreshToken'],
};
},
},
@@ -2046,24 +2051,24 @@ export class GraphQLService {
accountability: accountability,
schema: this.schema,
});
const currentRefreshToken = args.refresh_token || req?.cookies[env.REFRESH_TOKEN_COOKIE_NAME];
const currentRefreshToken = args['refresh_token'] || req?.cookies[env['REFRESH_TOKEN_COOKIE_NAME']];
if (!currentRefreshToken) {
throw new InvalidPayloadException(`"refresh_token" is required in either the JSON payload or Cookie`);
}
const result = await authenticationService.refresh(currentRefreshToken);
if (args.mode === 'cookie') {
res?.cookie(env.REFRESH_TOKEN_COOKIE_NAME, result.refreshToken, {
if (args['mode'] === 'cookie') {
res?.cookie(env['REFRESH_TOKEN_COOKIE_NAME'], result['refreshToken'], {
httpOnly: true,
domain: env.REFRESH_TOKEN_COOKIE_DOMAIN,
maxAge: getMilliseconds(env.REFRESH_TOKEN_TTL),
secure: env.REFRESH_TOKEN_COOKIE_SECURE ?? false,
sameSite: (env.REFRESH_TOKEN_COOKIE_SAME_SITE as 'lax' | 'strict' | 'none') || 'strict',
domain: env['REFRESH_TOKEN_COOKIE_DOMAIN'],
maxAge: getMilliseconds(env['REFRESH_TOKEN_TTL']),
secure: env['REFRESH_TOKEN_COOKIE_SECURE'] ?? false,
sameSite: (env['REFRESH_TOKEN_COOKIE_SAME_SITE'] as 'lax' | 'strict' | 'none') || 'strict',
});
}
return {
access_token: result.accessToken,
expires: result.expires,
refresh_token: result.refreshToken,
access_token: result['accessToken'],
expires: result['expires'],
refresh_token: result['refreshToken'],
};
},
},
@@ -2087,7 +2092,7 @@ export class GraphQLService {
accountability: accountability,
schema: this.schema,
});
const currentRefreshToken = args.refresh_token || req?.cookies[env.REFRESH_TOKEN_COOKIE_NAME];
const currentRefreshToken = args['refresh_token'] || req?.cookies[env['REFRESH_TOKEN_COOKIE_NAME']];
if (!currentRefreshToken) {
throw new InvalidPayloadException(`"refresh_token" is required in either the JSON payload or Cookie`);
}
@@ -2114,7 +2119,7 @@ export class GraphQLService {
const service = new UsersService({ accountability, schema: this.schema });
try {
await service.requestPasswordReset(args.email, args.reset_url || null);
await service.requestPasswordReset(args['email'], args['reset_url'] || null);
} catch (err: any) {
if (err instanceof InvalidPayloadException) {
throw err;
@@ -2142,7 +2147,7 @@ export class GraphQLService {
if (origin) accountability.origin = origin;
const service = new UsersService({ accountability, schema: this.schema });
await service.resetPassword(args.token, args.password);
await service.resetPassword(args['token'], args['password']);
return true;
},
},
@@ -2167,7 +2172,7 @@ export class GraphQLService {
accountability: this.accountability,
schema: this.schema,
});
await authService.verifyPassword(this.accountability.user, args.password);
await authService.verifyPassword(this.accountability.user, args['password']);
const { url, secret } = await service.generateTFA(this.accountability.user);
return { secret, otpauth_url: url };
},
@@ -2185,7 +2190,7 @@ export class GraphQLService {
schema: this.schema,
});
await service.enableTFA(this.accountability.user, args.otp, args.secret);
await service.enableTFA(this.accountability.user, args['otp'], args['secret']);
return true;
},
},
@@ -2200,7 +2205,7 @@ export class GraphQLService {
accountability: this.accountability,
schema: this.schema,
});
const otpValid = await service.verifyOTP(this.accountability.user, args.otp);
const otpValid = await service.verifyOTP(this.accountability.user, args['otp']);
if (otpValid === false) {
throw new InvalidPayloadException(`"otp" is invalid`);
}
@@ -2214,7 +2219,7 @@ export class GraphQLService {
string: new GraphQLNonNull(GraphQLString),
},
resolve: async (_, args) => {
return await generateHash(args.string);
return await generateHash(args['string']);
},
},
utils_hash_verify: {
@@ -2224,7 +2229,7 @@ export class GraphQLService {
hash: new GraphQLNonNull(GraphQLString),
},
resolve: async (_, args) => {
return await argon2.verify(args.hash, args.string);
return await argon2.verify(args['hash'], args['string']);
},
},
utils_sort: {
@@ -2240,7 +2245,7 @@ export class GraphQLService {
schema: this.schema,
});
const { item, to } = args;
await service.sort(args.collection, { item, to });
await service.sort(args['collection'], { item, to });
return true;
},
},
@@ -2254,7 +2259,7 @@ export class GraphQLService {
accountability: this.accountability,
schema: this.schema,
});
await service.revert(args.revision);
await service.revert(args['revision']);
return true;
},
},
@@ -2284,7 +2289,7 @@ export class GraphQLService {
accountability: this.accountability,
schema: this.schema,
});
await service.acceptInvite(args.token, args.password);
await service.acceptInvite(args['token'], args['password']);
return true;
},
},
@@ -2339,7 +2344,7 @@ export class GraphQLService {
schema: this.schema,
});
return await collectionsService.readOne(args.name);
return await collectionsService.readOne(args['name']);
},
},
});
@@ -2406,7 +2411,7 @@ export class GraphQLService {
schema: this.schema,
});
return await service.readAll(args.collection);
return await service.readAll(args['collection']);
},
},
fields_by_name: {
@@ -2420,7 +2425,7 @@ export class GraphQLService {
accountability: this.accountability,
schema: this.schema,
});
return await service.readOne(args.collection, args.field);
return await service.readOne(args['collection'], args['field']);
},
},
});
@@ -2479,7 +2484,7 @@ export class GraphQLService {
schema: this.schema,
});
return await service.readAll(args.collection);
return await service.readAll(args['collection']);
},
},
relations_by_name: {
@@ -2493,7 +2498,7 @@ export class GraphQLService {
accountability: this.accountability,
schema: this.schema,
});
return await service.readOne(args.collection, args.field);
return await service.readOne(args['collection'], args['field']);
},
},
});
@@ -2517,7 +2522,7 @@ export class GraphQLService {
accountability: this.accountability,
schema: this.schema,
});
const collectionKey = await collectionsService.createOne(args.data);
const collectionKey = await collectionsService.createOne(args['data']);
return await collectionsService.readOne(collectionKey);
},
},
@@ -2534,7 +2539,7 @@ export class GraphQLService {
accountability: this.accountability,
schema: this.schema,
});
const collectionKey = await collectionsService.updateOne(args.collection, args.data);
const collectionKey = await collectionsService.updateOne(args['collection'], args['data']);
return await collectionsService.readOne(collectionKey);
},
},
@@ -2553,8 +2558,8 @@ export class GraphQLService {
accountability: this.accountability,
schema: this.schema,
});
await collectionsService.deleteOne(args.collection);
return { collection: args.collection };
await collectionsService.deleteOne(args['collection']);
return { collection: args['collection'] };
},
},
});
@@ -2571,8 +2576,8 @@ export class GraphQLService {
accountability: this.accountability,
schema: this.schema,
});
await service.createField(args.collection, args.data);
return await service.readOne(args.collection, args.data.field);
await service.createField(args['collection'], args['data']);
return await service.readOne(args['collection'], args['data'].field);
},
},
update_fields_item: {
@@ -2587,11 +2592,11 @@ export class GraphQLService {
accountability: this.accountability,
schema: this.schema,
});
await service.updateField(args.collection, {
...args.data,
field: args.field,
await service.updateField(args['collection'], {
...args['data'],
field: args['field'],
});
return await service.readOne(args.collection, args.data.field);
return await service.readOne(args['collection'], args['data'].field);
},
},
delete_fields_item: {
@@ -2611,7 +2616,7 @@ export class GraphQLService {
accountability: this.accountability,
schema: this.schema,
});
await service.deleteField(args.collection, args.field);
await service.deleteField(args['collection'], args['field']);
const { collection, field } = args;
return { collection, field };
},
@@ -2630,8 +2635,8 @@ export class GraphQLService {
schema: this.schema,
});
await relationsService.createOne(args.data);
return await relationsService.readOne(args.data.collection, args.data.field);
await relationsService.createOne(args['data']);
return await relationsService.readOne(args['data'].collection, args['data'].field);
},
},
update_relations_item: {
@@ -2647,8 +2652,8 @@ export class GraphQLService {
schema: this.schema,
});
await relationsService.updateOne(args.collection, args.field, args.data);
return await relationsService.readOne(args.data.collection, args.data.field);
await relationsService.updateOne(args['collection'], args['field'], args['data']);
return await relationsService.readOne(args['data'].collection, args['data'].field);
},
},
delete_relations_item: {
@@ -2668,8 +2673,8 @@ export class GraphQLService {
accountability: this.accountability,
schema: this.schema,
});
await relationsService.deleteOne(args.collection, args.field);
return { collection: args.collection, field: args.field };
await relationsService.deleteOne(args['collection'], args['field']);
return { collection: args['collection'], field: args['field'] };
},
},
});
@@ -2708,7 +2713,7 @@ export class GraphQLService {
accountability: this.accountability,
});
await service.updateOne(this.accountability.user, args.data);
await service.updateOne(this.accountability.user, args['data']);
if ('directus_users' in ReadCollectionTypes) {
const selections = this.replaceFragmentsInSelections(
@@ -2778,7 +2783,7 @@ export class GraphQLService {
accountability: this.accountability,
schema: this.schema,
});
const primaryKey = await service.updateOne(args.id, { comment: args.comment });
const primaryKey = await service.updateOne(args['id'], { comment: args['comment'] });
if ('directus_activity' in ReadCollectionTypes) {
const selections = this.replaceFragmentsInSelections(
@@ -2799,7 +2804,7 @@ export class GraphQLService {
if ('directus_activity' in schema.delete.collections) {
schemaComposer.Mutation.addFields({
delete_comment: {
type: DeleteCollectionTypes.one,
type: DeleteCollectionTypes['one'],
args: {
id: new GraphQLNonNull(GraphQLID),
},
@@ -2808,8 +2813,8 @@ export class GraphQLService {
accountability: this.accountability,
schema: this.schema,
});
await service.deleteOne(args.id);
return { id: args.id };
await service.deleteOne(args['id']);
return { id: args['id'] };
},
},
});
@@ -2828,7 +2833,7 @@ export class GraphQLService {
accountability: this.accountability,
schema: this.schema,
});
const primaryKey = await service.importOne(args.url, args.data);
const primaryKey = await service.importOne(args['url'], args['data']);
if ('directus_files' in ReadCollectionTypes) {
const selections = this.replaceFragmentsInSelections(
@@ -2859,7 +2864,7 @@ export class GraphQLService {
accountability: this.accountability,
schema: this.schema,
});
await service.inviteUser(args.email, args.role, args.invite_url || null);
await service.inviteUser(args['email'], args['role'], args['invite_url'] || null);
return true;
},
},

View File

@@ -224,26 +224,26 @@ export class ExportService {
count: ['*'],
},
})
.then((result) => Number(result?.[0]?.count ?? 0));
.then((result) => Number(result?.[0]?.['count'] ?? 0));
const count = query.limit ? Math.min(totalCount, query.limit) : totalCount;
const requestedLimit = query.limit ?? -1;
const batchesRequired = Math.ceil(count / env.EXPORT_BATCH_SIZE);
const batchesRequired = Math.ceil(count / env['EXPORT_BATCH_SIZE']);
let readCount = 0;
for (let batch = 0; batch < batchesRequired; batch++) {
let limit = env.EXPORT_BATCH_SIZE;
let limit = env['EXPORT_BATCH_SIZE'];
if (requestedLimit > 0 && env.EXPORT_BATCH_SIZE > requestedLimit - readCount) {
if (requestedLimit > 0 && env['EXPORT_BATCH_SIZE'] > requestedLimit - readCount) {
limit = requestedLimit - readCount;
}
const result = await service.readByQuery({
...query,
limit,
offset: batch * env.EXPORT_BATCH_SIZE,
offset: batch * env['EXPORT_BATCH_SIZE'],
});
readCount += result.length;
@@ -265,7 +265,7 @@ export class ExportService {
schema: this.schema,
});
const storage: string = toArray(env.STORAGE_LOCATIONS)[0];
const storage: string = toArray(env['STORAGE_LOCATIONS'])[0];
const title = `export-${collection}-${getDateFormatted()}`;
const filename = `${title}.${format}`;

View File

@@ -90,12 +90,12 @@ describe('Integration Tests', () => {
it(`the returned UUID primary key for MS SQL should be uppercase`, async () => {
vi.mocked(getDatabaseClient).mockReturnValue('mssql');
const table = schemas.system.tables[0];
const table = schemas['system'].tables[0];
const itemsService = new ItemsService(table, {
knex: db,
accountability: { role: 'admin', admin: true },
schema: schemas.system.schema,
schema: schemas['system'].schema,
});
tracker.on.insert(table).responseOnce(item);

View File

@@ -265,7 +265,7 @@ export class ItemsService<Item extends AnyItem = AnyItem> implements AbstractSer
}
}
if (this.cache && env.CACHE_AUTO_PURGE && opts?.autoPurgeCache !== false) {
if (this.cache && env['CACHE_AUTO_PURGE'] && opts?.autoPurgeCache !== false) {
await this.cache.clear();
}
@@ -308,7 +308,7 @@ export class ItemsService<Item extends AnyItem = AnyItem> implements AbstractSer
}
}
if (this.cache && env.CACHE_AUTO_PURGE && opts?.autoPurgeCache !== false) {
if (this.cache && env['CACHE_AUTO_PURGE'] && opts?.autoPurgeCache !== false) {
await this.cache.clear();
}
@@ -491,7 +491,7 @@ export class ItemsService<Item extends AnyItem = AnyItem> implements AbstractSer
}
});
} finally {
if (this.cache && env.CACHE_AUTO_PURGE && opts?.autoPurgeCache !== false) {
if (this.cache && env['CACHE_AUTO_PURGE'] && opts?.autoPurgeCache !== false) {
await this.cache.clear();
}
}
@@ -668,7 +668,7 @@ export class ItemsService<Item extends AnyItem = AnyItem> implements AbstractSer
}
});
if (this.cache && env.CACHE_AUTO_PURGE && opts?.autoPurgeCache !== false) {
if (this.cache && env['CACHE_AUTO_PURGE'] && opts?.autoPurgeCache !== false) {
await this.cache.clear();
}
@@ -755,7 +755,7 @@ export class ItemsService<Item extends AnyItem = AnyItem> implements AbstractSer
return primaryKeys;
});
if (this.cache && env.CACHE_AUTO_PURGE && opts?.autoPurgeCache !== false) {
if (this.cache && env['CACHE_AUTO_PURGE'] && opts?.autoPurgeCache !== false) {
await this.cache.clear();
}
@@ -844,7 +844,7 @@ export class ItemsService<Item extends AnyItem = AnyItem> implements AbstractSer
}
});
if (this.cache && env.CACHE_AUTO_PURGE && opts?.autoPurgeCache !== false) {
if (this.cache && env['CACHE_AUTO_PURGE'] && opts?.autoPurgeCache !== false) {
await this.cache.clear();
}

View File

@@ -13,7 +13,7 @@ import type { AbstractServiceOptions } from '../../types';
import { Url } from '../../utils/url';
const liquidEngine = new Liquid({
root: [path.resolve(env.EXTENSIONS_PATH, 'templates'), path.resolve(__dirname, 'templates')],
root: [path.resolve(env['EXTENSIONS_PATH'], 'templates'), path.resolve(__dirname, 'templates')],
extname: '.liquid',
});
@@ -36,7 +36,7 @@ export class MailService {
this.knex = opts?.knex || getDatabase();
this.mailer = getMailer();
if (env.EMAIL_VERIFY_SETUP) {
if (env['EMAIL_VERIFY_SETUP']) {
this.mailer.verify((error) => {
if (error) {
logger.warn(`Email connection failed:`);
@@ -52,7 +52,7 @@ export class MailService {
const defaultTemplateData = await this.getDefaultTemplateData();
const from = `${defaultTemplateData.projectName} <${options.from || (env.EMAIL_FROM as string)}>`;
const from = `${defaultTemplateData.projectName} <${options.from || (env['EMAIL_FROM'] as string)}>`;
if (template) {
let templateData = template.data;
@@ -78,7 +78,7 @@ export class MailService {
}
private async renderTemplate(template: string, variables: Record<string, any>) {
const customTemplatePath = path.resolve(env.EXTENSIONS_PATH, 'templates', template + '.liquid');
const customTemplatePath = path.resolve(env['EXTENSIONS_PATH'], 'templates', template + '.liquid');
const systemTemplatePath = path.join(__dirname, 'templates', template + '.liquid');
const templatePath = (await fse.pathExists(customTemplatePath)) ? customTemplatePath : systemTemplatePath;
@@ -107,7 +107,7 @@ export class MailService {
};
function getProjectLogoURL(logoID?: string) {
const projectLogoUrl = new Url(env.PUBLIC_URL);
const projectLogoUrl = new Url(env['PUBLIC_URL']);
if (logoID) {
projectLogoUrl.addPath('assets', logoID);

View File

@@ -24,6 +24,7 @@ export class MetaService {
query.meta.map((metaVal: string) => {
if (metaVal === 'total_count') return this.totalCount(collection);
if (metaVal === 'filter_count') return this.filterCount(collection, query);
return undefined;
})
);

View File

@@ -18,7 +18,7 @@ export class NotificationsService extends ItemsService {
this.mailService = new MailService({ schema: this.schema, accountability: this.accountability });
}
async createOne(data: Partial<Notification>, opts?: MutationOptions): Promise<PrimaryKey> {
override async createOne(data: Partial<Notification>, opts?: MutationOptions): Promise<PrimaryKey> {
const response = await super.createOne(data, opts);
await this.sendEmail(data);
@@ -26,7 +26,7 @@ export class NotificationsService extends ItemsService {
return response;
}
async createMany(data: Partial<Notification>[], opts?: MutationOptions): Promise<PrimaryKey[]> {
override async createMany(data: Partial<Notification>[], opts?: MutationOptions): Promise<PrimaryKey[]> {
const response = await super.createMany(data, opts);
for (const notification of data) {
@@ -41,18 +41,18 @@ export class NotificationsService extends ItemsService {
const user = await this.usersService.readOne(data.recipient, {
fields: ['id', 'email', 'email_notifications', 'role.app_access'],
});
const manageUserAccountUrl = new Url(env.PUBLIC_URL).addPath('admin', 'users', user.id).toString();
const manageUserAccountUrl = new Url(env['PUBLIC_URL']).addPath('admin', 'users', user['id']).toString();
const html = data.message ? md(data.message) : '';
if (user.email && user.email_notifications === true) {
if (user['email'] && user['email_notifications'] === true) {
try {
await this.mailService.send({
template: {
name: 'base',
data: user.role?.app_access ? { url: manageUserAccountUrl, html } : { html },
data: user['role']?.app_access ? { url: manageUserAccountUrl, html } : { html },
},
to: user.email,
to: user['email'],
subject: data.subject,
});
} catch (error: any) {

View File

@@ -8,7 +8,7 @@ export class OperationsService extends ItemsService<OperationRaw> {
super('directus_operations', options);
}
async createOne(data: Partial<Item>, opts?: MutationOptions): Promise<PrimaryKey> {
override async createOne(data: Partial<Item>, opts?: MutationOptions): Promise<PrimaryKey> {
const flowManager = getFlowManager();
const result = await super.createOne(data, opts);
@@ -17,7 +17,7 @@ export class OperationsService extends ItemsService<OperationRaw> {
return result;
}
async createMany(data: Partial<Item>[], opts?: MutationOptions): Promise<PrimaryKey[]> {
override async createMany(data: Partial<Item>[], opts?: MutationOptions): Promise<PrimaryKey[]> {
const flowManager = getFlowManager();
const result = await super.createMany(data, opts);
@@ -26,7 +26,7 @@ export class OperationsService extends ItemsService<OperationRaw> {
return result;
}
async updateBatch(data: Partial<Item>[], opts?: MutationOptions): Promise<PrimaryKey[]> {
override async updateBatch(data: Partial<Item>[], opts?: MutationOptions): Promise<PrimaryKey[]> {
const flowManager = getFlowManager();
const result = await super.updateBatch(data, opts);
@@ -35,7 +35,7 @@ export class OperationsService extends ItemsService<OperationRaw> {
return result;
}
async updateMany(keys: PrimaryKey[], data: Partial<Item>, opts?: MutationOptions): Promise<PrimaryKey[]> {
override async updateMany(keys: PrimaryKey[], data: Partial<Item>, opts?: MutationOptions): Promise<PrimaryKey[]> {
const flowManager = getFlowManager();
const result = await super.updateMany(keys, data, opts);
@@ -44,7 +44,7 @@ export class OperationsService extends ItemsService<OperationRaw> {
return result;
}
async deleteMany(keys: PrimaryKey[], opts?: MutationOptions): Promise<PrimaryKey[]> {
override async deleteMany(keys: PrimaryKey[], opts?: MutationOptions): Promise<PrimaryKey[]> {
const flowManager = getFlowManager();
const result = await super.deleteMany(keys, opts);

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