Syntax fixes (#5367)

* Declare return types on functions

And a very few other type related minor fixes

* Minor syntax fixes

* Remove unnecessary escape chars in regexes
* Remove unnecessary awaits
* Replace deprecated req.connection with req.socket
* Replace deprecated upload with uploadOne
* Remove unnecessary eslint-disable-next-line comments
* Comment empty functions / catch or finally clauses
* Fix irregular whitespaces
* Add missing returns (null)
* Remove unreachable code
* A few logical fixes
* Remove / Handle non-null assertions which are certainly unnecessary (e.g. in
tests)
This commit is contained in:
Pascal Jufer
2021-04-29 18:11:43 +02:00
committed by GitHub
parent 40eba791fa
commit acd41eb0be
231 changed files with 646 additions and 527 deletions

View File

@@ -51,7 +51,7 @@ import { emitAsyncSafe } from './emitter';
import fse from 'fs-extra';
export default async function createApp() {
export default async function createApp(): Promise<express.Application> {
validateEnv(['KEY', 'SECRET']);
await validateDBConnection();

View File

@@ -5,7 +5,7 @@ import runMigrations from '../../../database/migrations/run';
import { getSchema } from '../../../utils/get-schema';
import { nanoid } from 'nanoid';
export default async function bootstrap() {
export default async function bootstrap(): Promise<void> {
logger.info('Initializing bootstrap...');
if ((await isDatabaseAvailable()) === false) {

View File

@@ -1,4 +1,4 @@
export default async function count(collection: string) {
export default async function count(collection: string): Promise<void> {
const database = require('../../../database/index').default;
if (!collection) {

View File

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

View File

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

View File

@@ -15,7 +15,7 @@ import runMigrations from '../../../database/migrations/run';
import createDBConnection, { Credentials } from '../../utils/create-db-connection';
import { Knex } from 'knex';
export default async function init(options: Record<string, any>) {
export default async function init(): Promise<void> {
const rootPath = process.cwd();
let { client } = await inquirer.prompt([
@@ -39,7 +39,7 @@ export default async function init(options: Record<string, any>) {
async function trySeed(): Promise<{ credentials: Credentials; db: Knex }> {
const credentials: Credentials = await inquirer.prompt(
(databaseQuestions[dbClient] as any[]).map((question: Function) =>
(databaseQuestions[dbClient] as any[]).map((question: ({ client, filepath }: any) => any) =>
question({ client: dbClient, filepath: rootPath })
)
);

View File

@@ -1,20 +1,20 @@
import path from 'path';
const filename = ({ filepath }: { filepath: string }) => ({
const filename = ({ filepath }: { filepath: string }): Record<string, string> => ({
type: 'input',
name: 'filename',
message: 'Database File Path:',
default: path.join(filepath, 'data.db'),
});
const host = () => ({
const host = (): Record<string, string> => ({
type: 'input',
name: 'host',
message: 'Database Host:',
default: '127.0.0.1',
});
const port = ({ client }: { client: string }) => ({
const port = ({ client }: { client: string }): Record<string, any> => ({
type: 'input',
name: 'port',
message: 'Port:',
@@ -30,27 +30,27 @@ const port = ({ client }: { client: string }) => ({
},
});
const database = () => ({
const database = (): Record<string, string> => ({
type: 'input',
name: 'database',
message: 'Database Name:',
default: 'directus',
});
const user = () => ({
const user = (): Record<string, string> => ({
type: 'input',
name: 'user',
message: 'Database User:',
});
const password = () => ({
const password = (): Record<string, string> => ({
type: 'password',
name: 'password',
message: 'Database Password:',
mask: '*',
});
const ssl = () => ({
const ssl = (): Record<string, string | boolean> => ({
type: 'confirm',
name: 'ssl',
message: 'Enable SSL:',

View File

@@ -1,6 +1,6 @@
import { getSchema } from '../../../utils/get-schema';
export default async function rolesCreate({ name, admin }: any) {
export default async function rolesCreate({ name, admin }: any): Promise<void> {
const { default: database } = require('../../../database/index');
const { RolesService } = require('../../../services/roles');

View File

@@ -1,6 +1,6 @@
import { getSchema } from '../../../utils/get-schema';
export default async function usersCreate({ email, password, role }: any) {
export default async function usersCreate({ email, password, role }: any): Promise<void> {
const { default: database, schemaInspector } = require('../../../database/index');
const { UsersService } = require('../../../services/users');

View File

@@ -1,7 +1,7 @@
import argon2 from 'argon2';
import { getSchema } from '../../../utils/get-schema';
export default async function usersPasswd({ email, password }: any) {
export default async function usersPasswd({ email, password }: any): Promise<void> {
const { default: database } = require('../../../database/index');
const { UsersService } = require('../../../services/users');

View File

@@ -13,7 +13,7 @@ export type Credentials = {
export default function createDBConnection(
client: 'sqlite3' | 'mysql' | 'pg' | 'oracledb' | 'mssql',
credentials: Credentials
) {
): Knex<any, unknown[]> {
let connection: Knex.Config['connection'] = {};
if (client === 'sqlite3') {

View File

@@ -23,7 +23,11 @@ const defaults = {
},
};
export default async function createEnv(client: keyof typeof drivers, credentials: Credentials, directory: string) {
export default async function createEnv(
client: keyof typeof drivers,
credentials: Credentials,
directory: string
): Promise<void> {
const config: Record<string, any> = {
...defaults,
database: {

View File

@@ -311,7 +311,9 @@ router.get(
try {
const email = getEmailFromProfile(req.params.provider, req.session.grant.response?.profile);
req.session?.destroy(() => {});
req.session?.destroy(() => {
// Do nothing
});
authResponse = await authenticationService.authenticate({
email,

View File

@@ -55,7 +55,7 @@ database
logger.trace(`[${delta.toFixed(3)}ms] ${queryInfo.sql} [${queryInfo.bindings.join(', ')}]`);
});
export async function hasDatabaseConnection() {
export async function hasDatabaseConnection(): Promise<boolean> {
try {
if (env.DB_CLIENT === 'oracledb') {
await database.raw('select 1 from DUAL');
@@ -68,7 +68,7 @@ export async function hasDatabaseConnection() {
}
}
export async function validateDBConnection() {
export async function validateDBConnection(): Promise<void> {
try {
await hasDatabaseConnection();
} catch (error) {
@@ -80,7 +80,7 @@ export async function validateDBConnection() {
export const schemaInspector = SchemaInspector(database);
export async function isInstalled() {
export async function isInstalled(): Promise<boolean> {
// The existence of a directus_collections table alone isn't a "proper" check to see if everything
// is installed correctly of course, but it's safe enough to assume that this collection only
// exists when using the installer CLI.

View File

@@ -1,6 +1,6 @@
import { Knex } from 'knex';
export async function up(knex: Knex) {
export async function up(knex: Knex): Promise<void> {
await knex.schema.alterTable('directus_fields', (table) => {
table.dropForeign(['collection']);
});
@@ -27,7 +27,7 @@ export async function up(knex: Knex) {
});
}
export async function down(knex: Knex) {
export async function down(knex: Knex): Promise<void> {
await knex.schema.alterTable('directus_fields', (table) => {
table.foreign('collection').references('directus_collections.collection');
});

View File

@@ -1,14 +1,14 @@
import { Knex } from 'knex';
import { merge } from 'lodash';
export async function up(knex: Knex) {
export async function up(knex: Knex): Promise<void> {
await knex('directus_relations')
.delete()
.where('many_collection', 'like', 'directus_%')
.andWhere('one_collection', 'like', 'directus_%');
}
export async function down(knex: Knex) {
export async function down(knex: Knex): Promise<void> {
const defaults = {
many_collection: 'directus_users',
many_field: null,

View File

@@ -1,11 +1,11 @@
import { Knex } from 'knex';
import { merge } from 'lodash';
export async function up(knex: Knex) {
export async function up(knex: Knex): Promise<void> {
await knex('directus_collections').delete().where('collection', 'like', 'directus_%');
}
export async function down(knex: Knex) {
export async function down(knex: Knex): Promise<void> {
const defaults = {
collection: null,
hidden: false,

View File

@@ -1639,12 +1639,12 @@ const systemFields = [
return merge({}, defaults, row);
});
export async function up(knex: Knex) {
export async function up(knex: Knex): Promise<void> {
const fieldKeys = uniq(systemFields.map((field: any) => field.field));
await knex('directus_fields').delete().where('collection', 'like', 'directus_%').whereIn('field', fieldKeys);
}
export async function down(knex: Knex) {
export async function down(knex: Knex): Promise<void> {
await knex.insert(systemFields).into('directus_fields');
}

View File

@@ -114,7 +114,7 @@ const updates = [
* Postgres behaves erratic on those triggers, not sure if MySQL / Maria plays nice either.
*/
export async function up(knex: Knex) {
export async function up(knex: Knex): Promise<void> {
for (const update of updates) {
await knex.schema.alterTable(update.table, (table) => {
for (const constraint of update.constraints) {
@@ -125,7 +125,7 @@ export async function up(knex: Knex) {
}
}
export async function down(knex: Knex) {
export async function down(knex: Knex): Promise<void> {
for (const update of updates) {
await knex.schema.alterTable(update.table, (table) => {
for (const constraint of update.constraints) {

View File

@@ -1,12 +1,12 @@
import { Knex } from 'knex';
export async function up(knex: Knex) {
export async function up(knex: Knex): Promise<void> {
await knex.schema.alterTable('directus_webhooks', (table) => {
table.text('url').alter();
});
}
export async function down(knex: Knex) {
export async function down(knex: Knex): Promise<void> {
await knex.schema.alterTable('directus_webhooks', (table) => {
table.string('url').alter();
});

View File

@@ -1,6 +1,6 @@
import { Knex } from 'knex';
export async function up(knex: Knex) {
export async function up(knex: Knex): Promise<void> {
await knex.schema.alterTable('directus_relations', (table) => {
table.string('sort_field');
});
@@ -26,7 +26,7 @@ export async function up(knex: Knex) {
}
}
export async function down(knex: Knex) {
export async function down(knex: Knex): Promise<void> {
await knex.schema.alterTable('directus_relations', (table) => {
table.dropColumn('sort_field');
});

View File

@@ -1,12 +1,12 @@
import { Knex } from 'knex';
export async function up(knex: Knex) {
export async function up(knex: Knex): Promise<void> {
await knex.schema.alterTable('directus_fields', (table) => {
table.dropColumn('locked');
});
}
export async function down(knex: Knex) {
export async function down(knex: Knex): Promise<void> {
await knex.schema.alterTable('directus_fields', (table) => {
table.boolean('locked').defaultTo(false).notNullable();
});

View File

@@ -1,12 +1,12 @@
import { Knex } from 'knex';
export async function up(knex: Knex) {
export async function up(knex: Knex): Promise<void> {
await knex.schema.alterTable('directus_webhooks', (table) => {
table.text('collections').alter();
});
}
export async function down(knex: Knex) {
export async function down(knex: Knex): Promise<void> {
await knex.schema.alterTable('directus_webhooks', (table) => {
table.string('collections').alter();
});

View File

@@ -1,12 +1,12 @@
import { Knex } from 'knex';
export async function up(knex: Knex) {
export async function up(knex: Knex): Promise<void> {
await knex.schema.alterTable('directus_presets', (table) => {
table.integer('refresh_interval');
});
}
export async function down(knex: Knex) {
export async function down(knex: Knex): Promise<void> {
await knex.schema.alterTable('directus_presets', (table) => {
table.dropColumn('refresh_interval');
});

View File

@@ -1,12 +1,12 @@
import { Knex } from 'knex';
export async function up(knex: Knex) {
export async function up(knex: Knex): Promise<void> {
await knex.schema.alterTable('directus_files', (table) => {
table.integer('filesize').nullable().defaultTo(null).alter();
});
}
export async function down(knex: Knex) {
export async function down(knex: Knex): Promise<void> {
await knex.schema.alterTable('directus_files', (table) => {
table.integer('filesize').notNullable().defaultTo(0).alter();
});

View File

@@ -1,6 +1,6 @@
import { Knex } from 'knex';
export async function up(knex: Knex) {
export async function up(knex: Knex): Promise<void> {
await knex.schema.alterTable('directus_collections', (table) => {
table.string('accountability').defaultTo('all');
});
@@ -8,7 +8,7 @@ export async function up(knex: Knex) {
await knex('directus_collections').update({ accountability: 'all' });
}
export async function down(knex: Knex) {
export async function down(knex: Knex): Promise<void> {
await knex.schema.alterTable('directus_collections', (table) => {
table.dropColumn('accountability');
});

View File

@@ -1,7 +1,9 @@
import { Knex } from 'knex';
export async function up(knex: Knex) {
export async function up(knex: Knex): Promise<void> {
await knex('directus_fields').update({ interface: 'many-to-many' }).where({ interface: 'files' });
}
export async function down(knex: Knex) {}
export async function down(_knex: Knex): Promise<void> {
// Do nothing
}

View File

@@ -10,7 +10,7 @@ type Migration = {
timestamp: Date;
};
export default async function run(database: Knex, direction: 'up' | 'down' | 'latest') {
export default async function run(database: Knex, direction: 'up' | 'down' | 'latest'): Promise<void> {
let migrationFiles = await fse.readdir(__dirname);
const customMigrationsPath = path.resolve(env.EXTENSIONS_PATH, 'migrations');

View File

@@ -25,7 +25,7 @@ type TableSeed = {
};
};
export default async function runSeed(database: Knex) {
export default async function runSeed(database: Knex): Promise<void> {
const exists = await database.schema.hasTable('directus_collections');
if (exists) {

View File

@@ -15,7 +15,7 @@ const emitter = new EventEmitter2({
* @param name
* @param args
*/
export async function emitAsyncSafe(name: string, ...args: any[]) {
export async function emitAsyncSafe(name: string, ...args: any[]): Promise<any> {
try {
return await emitter.emitAsync(name, ...args);
} catch (err) {

View File

@@ -5,17 +5,7 @@ import { ValueTooLongException } from '../value-too-long';
import { ValueOutOfRangeException } from '../value-out-of-range';
import database from '../../../database';
type MSSQLError = {
message: string;
code: 'EREQUEST';
number: number;
state: number;
class: number;
serverName: string;
procName: string;
lineNumber: number;
};
import { MSSQLError } from './types';
enum MSSQLErrorCodes {
FOREIGN_KEY_VIOLATION = 547,
@@ -25,7 +15,7 @@ enum MSSQLErrorCodes {
VALUE_LIMIT_VIOLATION = 2628,
}
export async function extractError(error: MSSQLError) {
export async function extractError(error: MSSQLError): Promise<MSSQLError | Error> {
switch (error.number) {
case MSSQLErrorCodes.UNIQUE_VIOLATION:
case 2627:
@@ -56,8 +46,8 @@ async function uniqueViolation(error: MSSQLError) {
* information_schema when this happens
*/
const betweenQuotes = /\'([^\']+)\'/;
const betweenParens = /\(([^\)]+)\)/g;
const betweenQuotes = /'([^']+)'/;
const betweenParens = /\(([^)]+)\)/g;
const quoteMatches = error.message.match(betweenQuotes);
const parenMatches = error.message.match(betweenParens);
@@ -117,7 +107,7 @@ function numericValueOutOfRange(error: MSSQLError) {
function valueLimitViolation(error: MSSQLError) {
const betweenBrackets = /\[([^\]]+)\]/g;
const betweenQuotes = /\'([^\']+)\'/g;
const betweenQuotes = /'([^']+)'/g;
const bracketMatches = error.message.match(betweenBrackets);
const quoteMatches = error.message.match(betweenQuotes);
@@ -135,7 +125,7 @@ function valueLimitViolation(error: MSSQLError) {
function notNullViolation(error: MSSQLError) {
const betweenBrackets = /\[([^\]]+)\]/g;
const betweenQuotes = /\'([^\']+)\'/g;
const betweenQuotes = /'([^']+)'/g;
const bracketMatches = error.message.match(betweenBrackets);
const quoteMatches = error.message.match(betweenQuotes);
@@ -152,8 +142,8 @@ function notNullViolation(error: MSSQLError) {
}
function foreignKeyViolation(error: MSSQLError) {
const betweenUnderscores = /\_\_(.+)\_\_/g;
const betweenParens = /\(([^\)]+)\)/g;
const betweenUnderscores = /__(.+)__/g;
const betweenParens = /\(([^)]+)\)/g;
// NOTE:
// Seeing that MS SQL doesn't return the offending column name, we have to extract it from the

View File

@@ -3,16 +3,7 @@ import { NotNullViolationException } from '../not-null-violation';
import { RecordNotUniqueException } from '../record-not-unique';
import { ValueTooLongException } from '../value-too-long';
import { ValueOutOfRangeException } from '../value-out-of-range';
type MySQLError = {
message: string;
code: string;
errno: number;
sqlMessage: string;
sqlState: string;
index: number;
sql: string;
};
import { MySQLError } from './types';
enum MySQLErrorCodes {
UNIQUE_VIOLATION = 'ER_DUP_ENTRY',
@@ -22,7 +13,7 @@ enum MySQLErrorCodes {
FOREIGN_KEY_VIOLATION = 'ER_NO_REFERENCED_ROW_2',
}
export function extractError(error: MySQLError) {
export function extractError(error: MySQLError): MySQLError | Error {
switch (error.code) {
case MySQLErrorCodes.UNIQUE_VIOLATION:
return uniqueViolation(error);
@@ -39,7 +30,7 @@ export function extractError(error: MySQLError) {
}
function uniqueViolation(error: MySQLError) {
const betweenQuotes = /\'([^\']+)\'/g;
const betweenQuotes = /'([^']+)'/g;
const matches = error.sqlMessage.match(betweenQuotes);
if (!matches) return error;
@@ -69,8 +60,8 @@ function uniqueViolation(error: MySQLError) {
}
function numericValueOutOfRange(error: MySQLError) {
const betweenTicks = /\`([^\`]+)\`/g;
const betweenQuotes = /\'([^\']+)\'/g;
const betweenTicks = /`([^`]+)`/g;
const betweenQuotes = /'([^']+)'/g;
const tickMatches = error.sql.match(betweenTicks);
const quoteMatches = error.sqlMessage.match(betweenQuotes);
@@ -87,8 +78,8 @@ function numericValueOutOfRange(error: MySQLError) {
}
function valueLimitViolation(error: MySQLError) {
const betweenTicks = /\`([^\`]+)\`/g;
const betweenQuotes = /\'([^\']+)\'/g;
const betweenTicks = /`([^`]+)`/g;
const betweenQuotes = /'([^']+)'/g;
const tickMatches = error.sql.match(betweenTicks);
const quoteMatches = error.sqlMessage.match(betweenQuotes);
@@ -105,8 +96,8 @@ function valueLimitViolation(error: MySQLError) {
}
function notNullViolation(error: MySQLError) {
const betweenTicks = /\`([^\`]+)\`/g;
const betweenQuotes = /\'([^\']+)\'/g;
const betweenTicks = /`([^`]+)`/g;
const betweenQuotes = /'([^']+)'/g;
const tickMatches = error.sql.match(betweenTicks);
const quoteMatches = error.sqlMessage.match(betweenQuotes);
@@ -123,8 +114,8 @@ function notNullViolation(error: MySQLError) {
}
function foreignKeyViolation(error: MySQLError) {
const betweenTicks = /\`([^\`]+)\`/g;
const betweenParens = /\(([^\)]+)\)/g;
const betweenTicks = /`([^`]+)`/g;
const betweenParens = /\(([^)]+)\)/g;
const tickMatches = error.sqlMessage.match(betweenTicks);
const parenMatches = error.sql.match(betweenParens);

View File

@@ -1,3 +1,3 @@
export function extractError(error: any) {
export function extractError(error: Error): Error {
return error;
}

View File

@@ -3,18 +3,7 @@ import { NotNullViolationException } from '../not-null-violation';
import { RecordNotUniqueException } from '../record-not-unique';
import { ValueTooLongException } from '../value-too-long';
import { ValueOutOfRangeException } from '../value-out-of-range';
type PostgresError = {
message: string;
length: number;
code: string;
detail: string;
schema: string;
table: string;
column?: string;
dataType?: string;
constraint?: string;
};
import { PostgresError } from './types';
enum PostgresErrorCodes {
FOREIGN_KEY_VIOLATION = '23503',
@@ -24,7 +13,7 @@ enum PostgresErrorCodes {
VALUE_LIMIT_VIOLATION = '22001',
}
export function extractError(error: PostgresError) {
export function extractError(error: PostgresError): PostgresError | Error {
switch (error.code) {
case PostgresErrorCodes.UNIQUE_VIOLATION:
return uniqueViolation(error);
@@ -44,7 +33,7 @@ export function extractError(error: PostgresError) {
function uniqueViolation(error: PostgresError) {
const { table, detail } = error;
const betweenParens = /\(([^\)]+)\)/g;
const betweenParens = /\(([^)]+)\)/g;
const matches = detail.match(betweenParens);
if (!matches) return error;
@@ -111,7 +100,7 @@ function notNullViolation(error: PostgresError) {
function foreignKeyViolation(error: PostgresError) {
const { table, detail } = error;
const betweenParens = /\(([^\)]+)\)/g;
const betweenParens = /\(([^)]+)\)/g;
const matches = detail.match(betweenParens);
if (!matches) return error;

View File

@@ -1,18 +1,13 @@
import { InvalidForeignKeyException } from '../invalid-foreign-key';
import { RecordNotUniqueException } from '../record-not-unique';
import { NotNullViolationException } from '../not-null-violation';
type SQLiteError = {
message: string;
errno: number;
code: string;
};
import { SQLiteError } from './types';
// NOTE:
// - Sqlite doesn't have varchar with length support, so no ValueTooLongException
// - Sqlite doesn't have a max range for numbers, so no ValueOutOfRangeException
export function extractError(error: SQLiteError) {
export function extractError(error: SQLiteError): SQLiteError | Error {
if (error.message.includes('SQLITE_CONSTRAINT: NOT NULL')) {
return notNullConstraint(error);
}

View File

@@ -0,0 +1,40 @@
export type MSSQLError = {
message: string;
code: 'EREQUEST';
number: number;
state: number;
class: number;
serverName: string;
procName: string;
lineNumber: number;
};
export type MySQLError = {
message: string;
code: string;
errno: number;
sqlMessage: string;
sqlState: string;
index: number;
sql: string;
};
export type PostgresError = {
message: string;
length: number;
code: string;
detail: string;
schema: string;
table: string;
column?: string;
dataType?: string;
constraint?: string;
};
export type SQLiteError = {
message: string;
errno: number;
code: string;
};
export type SQLError = MSSQLError & MySQLError & PostgresError & SQLiteError & Error;

View File

@@ -4,6 +4,7 @@ import { extractError as mysql } from './dialects/mysql';
import { extractError as mssql } from './dialects/mssql';
import { extractError as sqlite } from './dialects/sqlite';
import { extractError as oracle } from './dialects/oracle';
import { SQLError } from './dialects/types';
/**
* Translates an error thrown by any of the databases into a pre-defined Exception. Currently
@@ -14,17 +15,17 @@ import { extractError as oracle } from './dialects/oracle';
* - Value Out of Range
* - Value Too Long
*/
export async function translateDatabaseError(error: any) {
export async function translateDatabaseError(error: SQLError): Promise<any> {
switch (database.client.constructor.name) {
case 'Client_MySQL':
return await mysql(error);
return mysql(error);
case 'Client_PG':
return await postgres(error);
return postgres(error);
case 'Client_SQLite3':
return await sqlite(error);
return sqlite(error);
case 'Client_Oracledb':
case 'Client_Oracle':
return await oracle(error);
return oracle(error);
case 'Client_MSSQL':
return await mssql(error);

View File

@@ -13,7 +13,7 @@ import * as exceptions from './exceptions';
import * as services from './services';
import database from './database';
export async function ensureFoldersExist() {
export async function ensureFoldersExist(): Promise<void> {
const folders = ['endpoints', 'hooks', 'interfaces', 'modules', 'layouts', 'displays'];
for (const folder of folders) {
@@ -26,11 +26,11 @@ export async function ensureFoldersExist() {
}
}
export async function initializeExtensions() {
export async function initializeExtensions(): Promise<void> {
await ensureFoldersExist();
}
export async function listExtensions(type: string) {
export async function listExtensions(type: string): Promise<string[]> {
const extensionsPath = env.EXTENSIONS_PATH as string;
const location = path.join(extensionsPath, type);
@@ -46,12 +46,12 @@ export async function listExtensions(type: string) {
}
}
export async function registerExtensions(router: Router) {
export async function registerExtensions(router: Router): Promise<void> {
await registerExtensionHooks();
await registerExtensionEndpoints(router);
}
export async function registerExtensionEndpoints(router: Router) {
export async function registerExtensionEndpoints(router: Router): Promise<void> {
let endpoints: string[] = [];
try {
endpoints = await listExtensions('endpoints');
@@ -61,7 +61,7 @@ export async function registerExtensionEndpoints(router: Router) {
}
}
export async function registerExtensionHooks() {
export async function registerExtensionHooks(): Promise<void> {
let hooks: string[] = [];
try {
hooks = await listExtensions('hooks');

View File

@@ -2,9 +2,10 @@
* Set req.collection for use in other middleware. Used as an alternative on validate-collection for
* system collections
*/
import { RequestHandler } from 'express';
import asyncHandler from '../utils/async-handler';
const useCollection = (collection: string) =>
const useCollection = (collection: string): RequestHandler =>
asyncHandler(async (req, res, next) => {
req.collection = collection;
next();

View File

@@ -4,6 +4,7 @@ import {
RateLimiterMemcache,
IRateLimiterOptions,
IRateLimiterStoreOptions,
RateLimiterAbstract,
} from 'rate-limiter-flexible';
import { merge } from 'lodash';
@@ -13,7 +14,7 @@ import { getConfigFromEnv } from './utils/get-config-from-env';
type IRateLimiterOptionsOverrides = Partial<IRateLimiterOptions> | Partial<IRateLimiterStoreOptions>;
export function createRateLimiter(configOverrides?: IRateLimiterOptionsOverrides) {
export function createRateLimiter(configOverrides?: IRateLimiterOptionsOverrides): RateLimiterAbstract {
switch (env.RATE_LIMITER_STORE) {
case 'redis':
return new RateLimiterRedis(getConfig('redis', configOverrides));

View File

@@ -10,7 +10,7 @@ import database from './database';
import createApp from './app';
import { once } from 'lodash';
export default async function createServer() {
export default async function createServer(): Promise<http.Server> {
const server = http.createServer(await createApp());
server.on('request', function (req: http.IncomingMessage & Request, res: http.ServerResponse) {
@@ -21,22 +21,24 @@ export default async function createServer() {
const elapsedNanoseconds = elapsedTime[0] * 1e9 + elapsedTime[1];
const elapsedMilliseconds = elapsedNanoseconds / 1e6;
const previousIn = (req.connection as any)._metrics?.in || 0;
const previousOut = (req.connection as any)._metrics?.out || 0;
const previousIn = (req.socket as any)._metrics?.in || 0;
const previousOut = (req.socket as any)._metrics?.out || 0;
const metrics = {
in: req.connection.bytesRead - previousIn,
out: req.connection.bytesWritten - previousOut,
in: req.socket.bytesRead - previousIn,
out: req.socket.bytesWritten - previousOut,
};
(req.connection as any)._metrics = {
in: req.connection.bytesRead,
out: req.connection.bytesWritten,
(req.socket as any)._metrics = {
in: req.socket.bytesRead,
out: req.socket.bytesWritten,
};
// Compatibility when supporting serving with certificates
const protocol = server instanceof https.Server ? 'https' : 'http';
// Rely on url.parse for path extraction
// Doesn't break on illegal URLs
const urlInfo = url.parse(req.originalUrl || req.url);
const info = {
@@ -58,7 +60,7 @@ export default async function createServer() {
size: metrics.out,
headers: res.getHeaders(),
},
ip: req.headers['x-forwarded-for'] || req.connection?.remoteAddress || req.socket?.remoteAddress,
ip: req.headers['x-forwarded-for'] || req.socket?.remoteAddress,
duration: elapsedMilliseconds.toFixed(),
};

View File

@@ -5,7 +5,7 @@ import path from 'path';
import { Knex } from 'knex';
import { Accountability, AbstractServiceOptions, Transformation } from '../types';
import { AuthorizationService } from './authorization';
import { Range } from '@directus/drive';
import { Range, StatResponse } from '@directus/drive';
import { RangeNotSatisfiableException } from '../exceptions';
export class AssetsService {
@@ -19,7 +19,11 @@ export class AssetsService {
this.authorizationService = new AuthorizationService(options);
}
async getAsset(id: string, transformation: Transformation, range?: Range) {
async getAsset(
id: string,
transformation: Transformation,
range?: Range
): Promise<{ stream: NodeJS.ReadableStream; file: any; stat: StatResponse }> {
const publicSettings = await this.knex
.select('project_logo', 'public_background', 'public_foreground')
.from('directus_settings')

View File

@@ -50,7 +50,9 @@ export class AuthenticationService {
* Password is optional to allow usage of this function within the SSO flow and extensions. Make sure
* to handle password existence checks elsewhere
*/
async authenticate(options: AuthenticateOptions) {
async authenticate(
options: AuthenticateOptions
): Promise<{ accessToken: any; refreshToken: any; expires: any; id?: any }> {
const settingsService = new SettingsService({
knex: this.knex,
schema: this.schema,
@@ -196,7 +198,7 @@ export class AuthenticationService {
};
}
async refresh(refreshToken: string) {
async refresh(refreshToken: string): Promise<Record<string, any>> {
if (!refreshToken) {
throw new InvalidCredentialsException();
}
@@ -235,16 +237,16 @@ export class AuthenticationService {
};
}
async logout(refreshToken: string) {
async logout(refreshToken: string): Promise<void> {
await this.knex.delete().from('directus_sessions').where({ token: refreshToken });
}
generateTFASecret() {
generateTFASecret(): string {
const secret = authenticator.generateSecret();
return secret;
}
async generateOTPAuthURL(pk: string, secret: string) {
async generateOTPAuthURL(pk: string, secret: string): Promise<string> {
const user = await this.knex.select('first_name', 'last_name').from('directus_users').where({ id: pk }).first();
const name = `${user.first_name} ${user.last_name}`;
return authenticator.keyuri(name, 'Directus', secret);
@@ -261,7 +263,7 @@ export class AuthenticationService {
return authenticator.check(otp, secret);
}
async verifyPassword(pk: string, password: string) {
async verifyPassword(pk: string, password: string): Promise<boolean> {
const userRecord = await this.knex.select('password').from('directus_users').where({ id: pk }).first();
if (!userRecord || !userRecord.password) {

View File

@@ -304,7 +304,7 @@ export class AuthorizationService {
return errors;
}
async checkAccess(action: PermissionsAction, collection: string, pk: PrimaryKey | PrimaryKey[]) {
async checkAccess(action: PermissionsAction, collection: string, pk: PrimaryKey | PrimaryKey[]): Promise<void> {
if (this.accountability?.admin === true) return;
const itemsService = new ItemsService(collection, {

View File

@@ -114,7 +114,10 @@ export class CollectionsService {
/**
* Create multiple new collections
*/
async createMany(payloads: Partial<Collection> & { collection: string }[], opts?: MutationOptions) {
async createMany(
payloads: Partial<Collection> & { collection: string }[],
opts?: MutationOptions
): Promise<string[]> {
const collections = await this.knex.transaction(async (trx) => {
const service = new CollectionsService({
schema: this.schema,

View File

@@ -157,7 +157,7 @@ export class FieldsService {
return result;
}
async readOne(collection: string, field: string) {
async readOne(collection: string, field: string): Promise<Record<string, any>> {
if (this.accountability && this.accountability.admin !== true) {
if (this.hasReadAccess === false) {
throw new ForbiddenException();
@@ -188,7 +188,9 @@ export class FieldsService {
try {
column = await this.schemaInspector.columnInfo(collection, field);
column.default_value = getDefaultValue(column);
} catch {}
} finally {
// Do nothing
}
const data = {
collection,
@@ -205,7 +207,7 @@ export class FieldsService {
collection: string,
field: Partial<Field> & { field: string; type: typeof types[number] },
table?: Knex.CreateTableBuilder // allows collection creation to
) {
): Promise<void> {
if (this.accountability && this.accountability.admin !== true) {
throw new ForbiddenException('Only admins can perform this action.');
}
@@ -246,7 +248,7 @@ export class FieldsService {
}
}
async updateField(collection: string, field: RawField) {
async updateField(collection: string, field: RawField): Promise<string> {
if (this.accountability && this.accountability.admin !== true) {
throw new ForbiddenException('Only admins can perform this action');
}
@@ -289,7 +291,7 @@ export class FieldsService {
}
/** @todo save accountability */
async deleteField(collection: string, field: string) {
async deleteField(collection: string, field: string): Promise<void> {
if (this.accountability && this.accountability.admin !== true) {
throw new ForbiddenException('Only admins can perform this action.');
}
@@ -375,7 +377,7 @@ export class FieldsService {
});
}
public addColumnToTable(table: Knex.CreateTableBuilder, field: RawField | Field, alter: Column | null = null) {
public addColumnToTable(table: Knex.CreateTableBuilder, field: RawField | Field, alter: Column | null = null): void {
let column: Knex.ColumnBuilder;
if (field.schema?.has_auto_increment) {

View File

@@ -30,7 +30,7 @@ export class FilesService extends ItemsService {
stream: NodeJS.ReadableStream,
data: Partial<File> & { filename_download: string; storage: string },
primaryKey?: PrimaryKey
) {
): Promise<PrimaryKey> {
const payload = clone(data);
if (primaryKey !== undefined) {
@@ -140,7 +140,7 @@ export class FilesService extends ItemsService {
/**
* Import a single file from an external URL
*/
async importOne(importURL: string, body: Partial<File>) {
async importOne(importURL: string, body: Partial<File>): Promise<PrimaryKey> {
const fileCreatePermissions = this.schema.permissions.find(
(permission) => permission.collection === 'directus_files' && permission.action === 'create'
);
@@ -174,7 +174,7 @@ export class FilesService extends ItemsService {
...(body || {}),
};
return await this.upload(fileResponse.data, payload);
return await this.uploadOne(fileResponse.data, payload);
}
/**
@@ -220,7 +220,7 @@ export class FilesService extends ItemsService {
stream: NodeJS.ReadableStream,
data: Partial<File> & { filename_download: string; storage: string },
primaryKey?: PrimaryKey
) {
): Promise<PrimaryKey> {
logger.warn('FilesService.upload is deprecated and will be removed before v9.0.0. Use uploadOne instead.');
return await this.uploadOne(stream, data, primaryKey);
@@ -229,7 +229,7 @@ export class FilesService extends ItemsService {
/**
* @deprecated Use `importOne` instead
*/
async import(importURL: string, body: Partial<File>) {
async import(importURL: string, body: Partial<File>): Promise<PrimaryKey> {
return await this.importOne(importURL, body);
}

View File

@@ -1,6 +1,6 @@
import { Knex } from 'knex';
import database from '../database';
import { AbstractServiceOptions, Accountability, Query, SchemaOverview, GraphQLParams, Action } from '../types';
import { AbstractServiceOptions, Accountability, Query, SchemaOverview, GraphQLParams, Action, Item } from '../types';
import argon2 from 'argon2';
import {
GraphQLString,
@@ -122,7 +122,12 @@ export class GraphQLService {
/**
* Execute a GraphQL structure
*/
async execute({ document, variables, operationName, contextValue }: GraphQLParams) {
async execute({
document,
variables,
operationName,
contextValue,
}: GraphQLParams): Promise<FormattedExecutionResult> {
const schema = this.getSchema();
const validationErrors = validate(schema, document, specifiedRules);
@@ -158,8 +163,9 @@ export class GraphQLService {
*/
getSchema(): GraphQLSchema;
getSchema(type: 'schema'): GraphQLSchema;
getSchema(type: 'sdl'): string;
getSchema(type: 'schema' | 'sdl' = 'schema') {
getSchema(type: 'sdl'): GraphQLSchema | string;
getSchema(type: 'schema' | 'sdl' = 'schema'): GraphQLSchema | string {
// eslint-disable-next-line @typescript-eslint/no-this-alias
const self = this;
const schemaComposer = new SchemaComposer<GraphQLParams['contextValue']>();
@@ -798,7 +804,7 @@ export class GraphQLService {
* Generic resolver that's used for every "regular" items/system query. Converts the incoming GraphQL AST / fragments into
* Directus' query structure which is then executed by the services.
*/
async resolveQuery(info: GraphQLResolveInfo) {
async resolveQuery(info: GraphQLResolveInfo): Promise<Partial<Item> | null> {
let collection = info.fieldName;
if (this.scope === 'system') collection = `directus_${collection}`;
@@ -841,7 +847,10 @@ export class GraphQLService {
* Generic mutation resolver that converts the incoming GraphQL mutation AST into a Directus query and executes the
* appropriate C-UD operation
*/
async resolveMutation(args: Record<string, any>, info: GraphQLResolveInfo) {
async resolveMutation(
args: Record<string, any>,
info: GraphQLResolveInfo
): Promise<Partial<Item> | boolean | undefined> {
const action = info.fieldName.split('_')[0] as 'create' | 'update' | 'delete';
let collection = info.fieldName.substring(action.length + 1);
if (this.scope === 'system') collection = `directus_${collection}`;
@@ -906,7 +915,7 @@ export class GraphQLService {
/**
* Execute the read action on the correct service. Checks for singleton as well.
*/
async read(collection: string, query: Query) {
async read(collection: string, query: Query): Promise<Partial<Item>> {
const service = this.getService(collection);
const result = this.schema.collections[collection].singleton
@@ -919,7 +928,11 @@ export class GraphQLService {
/**
* Upsert and read singleton item
*/
async upsertSingleton(collection: string, body: Record<string, any> | Record<string, any>[], query: Query) {
async upsertSingleton(
collection: string,
body: Record<string, any> | Record<string, any>[],
query: Query
): Promise<Partial<Item> | boolean> {
const service = this.getService(collection);
try {
@@ -988,7 +1001,7 @@ export class GraphQLService {
rawQuery: Query,
selections: readonly SelectionNode[],
variableValues: GraphQLResolveInfo['variableValues']
) {
): Query {
const query: Query = sanitizeQuery(rawQuery, this.accountability);
const parseFields = (selections: readonly SelectionNode[], parent?: string): string[] => {
@@ -1052,7 +1065,7 @@ export class GraphQLService {
/**
* Convert Directus-Exception into a GraphQL format, so it can be returned by GraphQL properly.
*/
formatError(error: BaseException | BaseException[]) {
formatError(error: BaseException | BaseException[]): GraphQLError {
if (Array.isArray(error)) {
return new GraphQLError(error[0].message, undefined, undefined, undefined, undefined, error[0]);
}
@@ -1064,7 +1077,7 @@ export class GraphQLService {
* Select the correct service for the given collection. This allows the individual services to run
* their custom checks (f.e. it allows UsersService to prevent updating TFA secret from outside)
*/
getService(collection: string) {
getService(collection: string): RolesService {
const opts = {
knex: this.knex,
accountability: this.accountability,
@@ -1150,7 +1163,7 @@ export class GraphQLService {
update: SchemaOverview;
delete: SchemaOverview;
}
) {
): SchemaComposer<any> {
const AuthTokens = schemaComposer.createObjectTC({
name: 'auth_tokens',
fields: {

View File

@@ -527,7 +527,7 @@ export class ItemsService<Item extends AnyItem = AnyItem> implements AbstractSer
/**
* Upsert a single item
*/
async upsertOne(payload: Partial<Item>, opts?: MutationOptions) {
async upsertOne(payload: Partial<Item>, opts?: MutationOptions): Promise<PrimaryKey> {
const primaryKeyField = this.schema.collections[this.collection].primary;
const primaryKey: PrimaryKey | undefined = payload[primaryKeyField];
@@ -549,7 +549,7 @@ export class ItemsService<Item extends AnyItem = AnyItem> implements AbstractSer
/**
* Upsert many items
*/
async upsertMany(payloads: Partial<Item>[], opts?: MutationOptions) {
async upsertMany(payloads: Partial<Item>[], opts?: MutationOptions): Promise<PrimaryKey[]> {
const primaryKeys = await this.knex.transaction(async (trx) => {
const service = new ItemsService(this.collection, {
accountability: this.accountability,
@@ -702,7 +702,7 @@ export class ItemsService<Item extends AnyItem = AnyItem> implements AbstractSer
/**
* Upsert/treat collection as singleton
*/
async upsertSingleton(data: Partial<Item>, opts?: MutationOptions) {
async upsertSingleton(data: Partial<Item>, opts?: MutationOptions): Promise<PrimaryKey> {
const primaryKeyField = this.schema.collections[this.collection].primary;
const record = await this.knex.select(primaryKeyField).from(this.collection).limit(1).first();

View File

@@ -17,7 +17,7 @@ export class MetaService {
this.schema = options.schema;
}
async getMetaForQuery(collection: string, query: Query) {
async getMetaForQuery(collection: string, query: Query): Promise<Record<string, any> | undefined> {
if (!query || !query.meta) return;
const results = await Promise.all(
@@ -35,7 +35,7 @@ export class MetaService {
}, {});
}
async totalCount(collection: string) {
async totalCount(collection: string): Promise<number> {
const dbQuery = this.knex(collection).count('*', { as: 'count' }).first();
if (this.accountability?.admin !== true) {
@@ -55,7 +55,7 @@ export class MetaService {
return Number(result?.count ?? 0);
}
async filterCount(collection: string, query: Query) {
async filterCount(collection: string, query: Query): Promise<number> {
const dbQuery = this.knex(collection).count('*', { as: 'count' });
let filter = query.filter || {};

View File

@@ -191,7 +191,7 @@ export class PayloadService {
payload: Partial<Item>,
action: Action,
accountability: Accountability | null
) {
): Promise<any> {
if (!field.special) return payload[field.field];
const fieldSpecials = field.special ? toArray(field.special) : [];
@@ -215,7 +215,10 @@ export class PayloadService {
* Knex returns `datetime` and `date` columns as Date.. This is wrong for date / datetime, as those
* shouldn't return with time / timezone info respectively
*/
async processDates(payloads: Partial<Record<string, any>>[], action: Action) {
async processDates(
payloads: Partial<Record<string, any>>[],
action: Action
): Promise<Partial<Record<string, any>>[]> {
const fieldsInCollection = Object.entries(this.schema.collections[this.collection].fields);
const dateColumns = fieldsInCollection.filter(([name, field]) =>

View File

@@ -10,7 +10,7 @@ export class PermissionsService extends ItemsService {
super('directus_permissions', options);
}
getAllowedFields(action: PermissionsAction, collection?: string) {
getAllowedFields(action: PermissionsAction, collection?: string): Record<string, string[]> {
const results = this.schema.permissions.filter((permission) => {
let matchesCollection = true;

View File

@@ -7,7 +7,7 @@ export class RevisionsService extends ItemsService {
super('directus_revisions', options);
}
async revert(pk: PrimaryKey) {
async revert(pk: PrimaryKey): Promise<void> {
const revision = await super.readOne(pk);
if (!revision) throw new ForbiddenException();

View File

@@ -30,7 +30,7 @@ export class ServerService {
this.settingsService = new SettingsService({ knex: this.knex, schema: this.schema });
}
async serverInfo() {
async serverInfo(): Promise<Record<string, any>> {
const info: Record<string, any> = {};
const projectInfo = await this.settingsService.readSingleton({
@@ -70,7 +70,7 @@ export class ServerService {
return info;
}
async health() {
async health(): Promise<Record<string, any>> {
const checkID = nanoid(5);
// Based on https://tools.ietf.org/id/draft-inadarei-api-health-check-05.html#name-componenttype

View File

@@ -217,7 +217,7 @@ export class UsersService extends ItemsService {
return keys;
}
async inviteUser(email: string | string[], role: string, url: string | null) {
async inviteUser(email: string | string[], role: string, url: string | null): Promise<void> {
const emails = toArray(email);
const urlWhitelist = toArray(env.USER_INVITE_URL_ALLOW_LIST);
@@ -263,7 +263,7 @@ export class UsersService extends ItemsService {
});
}
async acceptInvite(token: string, password: string) {
async acceptInvite(token: string, password: string): Promise<void> {
const { email, scope } = jwt.verify(token, env.SECRET as string) as {
email: string;
scope: string;
@@ -286,7 +286,7 @@ export class UsersService extends ItemsService {
}
}
async requestPasswordReset(email: string, url: string | null) {
async requestPasswordReset(email: string, url: string | null): Promise<void> {
const user = await this.knex.select('id').from('directus_users').where({ email }).first();
if (!user) throw new ForbiddenException();
@@ -321,7 +321,7 @@ export class UsersService extends ItemsService {
});
}
async resetPassword(token: string, password: string) {
async resetPassword(token: string, password: string): Promise<void> {
const { email, scope } = jwt.verify(token, env.SECRET as string) as {
email: string;
scope: string;
@@ -344,7 +344,7 @@ export class UsersService extends ItemsService {
}
}
async enableTFA(pk: string) {
async enableTFA(pk: string): Promise<Record<string, string>> {
const user = await this.knex.select('tfa_secret').from('directus_users').where({ id: pk }).first();
if (user?.tfa_secret !== null) {
@@ -366,7 +366,7 @@ export class UsersService extends ItemsService {
};
}
async disableTFA(pk: string) {
async disableTFA(pk: string): Promise<void> {
await this.knex('directus_users').update({ tfa_secret: null }).where({ id: pk });
}

View File

@@ -15,7 +15,7 @@ export class UtilsService {
this.schema = options.schema;
}
async sort(collection: string, { item, to }: { item: PrimaryKey; to: PrimaryKey }) {
async sort(collection: string, { item, to }: { item: PrimaryKey; to: PrimaryKey }): Promise<void> {
const sortFieldResponse =
(await this.knex.select('sort_field').from('directus_collections').where({ collection }).first()) ||
systemCollectionRows;

View File

@@ -7,7 +7,7 @@ if (require.main === module) {
start();
}
export default async function start() {
export default async function start(): Promise<void> {
const createServer = require('./server').default;
const server = await createServer();

View File

@@ -1,3 +1,6 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable @typescript-eslint/ban-types */
type Primitive = string | number | boolean | bigint | symbol | undefined | null;
type Builtin = Primitive | Function | Date | Error | RegExp;
type IsTuple<T> = T extends [infer A]

View File

@@ -1,4 +1,6 @@
import * as expressSession from 'express-session';
import { SessionData } from 'express-session';
export = SessionData;
declare module 'express-session' {
interface SessionData {

View File

@@ -13,7 +13,7 @@ export default function applyQuery(
query: Query,
schema: SchemaOverview,
subQuery: boolean = false
) {
): void {
if (query.sort) {
dbQuery.orderBy(
query.sort.map((sort) => ({
@@ -50,7 +50,7 @@ export function applyFilter(
rootFilter: Filter,
collection: string,
subQuery: boolean = false
) {
): void {
const relations: Relation[] = [...schema.relations, ...systemRelationRows];
const aliasMap: Record<string, string> = {};
@@ -332,7 +332,7 @@ export async function applySearch(
dbQuery: Knex.QueryBuilder,
searchQuery: string,
collection: string
) {
): Promise<void> {
const fields = Object.entries(schema.collections[collection].fields);
dbQuery.andWhere(function () {

View File

@@ -1,6 +1,7 @@
export function deepMap(
object: Record<string, any>,
iterator: (value: any, key: string | number) => any,
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
context?: any
): any {
if (Array.isArray(object)) {

View File

@@ -6,7 +6,7 @@ import generateJoi from './generate-joi';
existing array of items has to be filtered using the same filter syntax as used in the ast-to-sql flow
*/
export function filterItems(items: Record<string, any>[], filter: Query['filter']) {
export function filterItems(items: Record<string, any>[], filter: Query['filter']): Record<string, any>[] {
if (!filter) return items;
return items.filter((item) => {

View File

@@ -1,7 +1,7 @@
import { Request } from 'express';
import url from 'url';
export function getCacheKey(req: Request) {
export function getCacheKey(req: Request): string {
const path = url.parse(req.originalUrl).pathname;
let key: string;

View File

@@ -2,7 +2,7 @@ import camelcase from 'camelcase';
import env from '../env';
import { set } from 'lodash';
export function getConfigFromEnv(prefix: string, omitPrefix?: string | string[]) {
export function getConfigFromEnv(prefix: string, omitPrefix?: string | string[]): any {
const config: any = {};
for (const [key, value] of Object.entries(env)) {

View File

@@ -2,7 +2,9 @@ import getLocalType from './get-local-type';
import { Column } from 'knex-schema-inspector/dist/types/column';
import { SchemaOverview } from '@directus/schema/dist/types/overview';
export default function getDefaultValue(column: SchemaOverview[string]['columns'][string] | Column) {
export default function getDefaultValue(
column: SchemaOverview[string]['columns'][string] | Column
): string | boolean | null {
const type = getLocalType(column);
let defaultValue = column.default_value ?? null;

View File

@@ -15,7 +15,7 @@ const profileMap: Record<string, string> = {};
*
* This is used in the SSO flow to extract the users
*/
export default function getEmailFromProfile(provider: string, profile: Record<string, any>) {
export default function getEmailFromProfile(provider: string, profile: Record<string, any>): string {
const path = profileMap[provider] || env[`OAUTH_${provider.toUpperCase()}_PROFILE_EMAIL`] || 'email';
const email = get(profile, path);

View File

@@ -1,9 +1,9 @@
import { GraphQLBoolean, GraphQLFloat, GraphQLInt, GraphQLString } from 'graphql';
import { GraphQLBoolean, GraphQLFloat, GraphQLInt, GraphQLScalarType, GraphQLString } from 'graphql';
import { GraphQLJSON } from 'graphql-compose';
import { GraphQLDate } from '../services/graphql';
import { types } from '../types';
export function getGraphQLType(localType: typeof types[number] | 'alias' | 'unknown') {
export function getGraphQLType(localType: typeof types[number] | 'alias' | 'unknown'): GraphQLScalarType {
switch (localType) {
case 'boolean':
return GraphQLBoolean;

View File

@@ -4,7 +4,7 @@ import logger from '../logger';
/**
* Check if a given string conforms to the structure of a JWT.
*/
export default function isJWT(string: string) {
export default function isJWT(string: string): boolean {
const parts = string.split('.');
// JWTs have the structure header.payload.signature

View File

@@ -5,7 +5,7 @@ import { promisify } from 'util';
const readdir = promisify(fs.readdir);
const stat = promisify(fs.stat);
export default async function listFolders(location: string) {
export default async function listFolders(location: string): Promise<string[]> {
const fullPath = path.resolve(location);
const files = await readdir(fullPath);

View File

@@ -2,7 +2,7 @@ import { Filter, Accountability } from '../types';
import { deepMap } from './deep-map';
import { toArray } from '../utils/to-array';
export function parseFilter(filter: Filter, accountability: Accountability | null) {
export function parseFilter(filter: Filter, accountability: Accountability | null): any {
return deepMap(filter, (val, key) => {
if (val === 'true') return true;
if (val === 'false') return false;

View File

@@ -13,7 +13,7 @@ const IPTC_ENTRY_TYPES = new Map([
const IPTC_ENTRY_MARKER = Buffer.from([0x1c, 0x02]);
export default function parseIPTC(buffer: Buffer) {
export default function parseIPTC(buffer: Buffer): Record<string, any> {
if (!Buffer.isBuffer(buffer)) return {};
let iptc: Record<string, any> = {};

View File

@@ -11,7 +11,7 @@ import { uniq } from 'lodash';
export function reduceSchema(
schema: SchemaOverview,
actions: PermissionsAction[] = ['create', 'read', 'update', 'delete']
) {
): SchemaOverview {
const reduced: SchemaOverview = {
collections: {},
relations: [],

View File

@@ -1,7 +1,7 @@
import fse from 'fs-extra';
import yaml from 'js-yaml';
export function requireYAML(filepath: string) {
export function requireYAML(filepath: string): Record<string, any> {
const yamlRaw = fse.readFileSync(filepath, 'utf8');
return yaml.load(yamlRaw) as Record<string, any>;

View File

@@ -3,7 +3,7 @@ import logger from '../logger';
import { parseFilter } from '../utils/parse-filter';
import { flatten, set, merge, get } from 'lodash';
export function sanitizeQuery(rawQuery: Record<string, any>, accountability?: Accountability | null) {
export function sanitizeQuery(rawQuery: Record<string, any>, accountability?: Accountability | null): Query {
const query: Query = {};
if (rawQuery.limit !== undefined) {

View File

@@ -9,7 +9,7 @@ import env from '../env';
// @ts-ignore
import { version } from '../../package.json';
export async function track(event: string) {
export async function track(event: string): Promise<void> {
if (env.TELEMETRY !== false) {
const info = await getEnvInfo(event);

View File

@@ -1,7 +1,7 @@
import logger from '../logger';
import env from '../env';
export function validateEnv(requiredKeys: string[]) {
export function validateEnv(requiredKeys: string[]): void {
if (env.DB_CLIENT && env.DB_CLIENT === 'sqlite3') {
requiredKeys.push('DB_FILENAME');
} else if (env.DB_CLIENT && env.DB_CLIENT === 'oracledb') {

View File

@@ -22,7 +22,7 @@ const querySchema = Joi.object({
deep: Joi.object(),
}).id('query');
export function validateQuery(query: Query) {
export function validateQuery(query: Query): Query {
const { error } = querySchema.validate(query);
if (query.filter && Object.keys(query.filter).length > 0) {

View File

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

View File

@@ -24,7 +24,7 @@ export interface RequestError extends AxiosError {
response: Response;
}
export const onRequest = (config: AxiosRequestConfig) => {
export const onRequest = (config: AxiosRequestConfig): RequestConfig => {
const requestsStore = useRequestsStore();
const id = requestsStore.startRequest();
@@ -36,14 +36,14 @@ export const onRequest = (config: AxiosRequestConfig) => {
return requestConfig;
};
export const onResponse = (response: AxiosResponse | Response) => {
export const onResponse = (response: AxiosResponse | Response): AxiosResponse | Response => {
const requestsStore = useRequestsStore();
const id = (response.config as RequestConfig).id;
requestsStore.endRequest(id);
return response;
};
export const onError = async (error: RequestError) => {
export const onError = async (error: RequestError): Promise<RequestError> => {
const requestsStore = useRequestsStore();
const id = (error.response.config as RequestConfig).id;
requestsStore.endRequest(id);
@@ -64,7 +64,7 @@ export const onError = async (error: RequestError) => {
error.request.responseURL.includes('login') === false &&
error.request.responseURL.includes('tfa') === false
) {
let newToken: string;
let newToken: string | undefined;
try {
newToken = await refresh();
@@ -95,7 +95,7 @@ function getToken() {
return api.defaults.headers?.['Authorization']?.split(' ')[1] || null;
}
export function addTokenToURL(url: string, token?: string) {
export function addTokenToURL(url: string, token?: string): string {
token = token || getToken();
if (!token) return url;

View File

@@ -9,7 +9,7 @@ export type LoginCredentials = {
password: string;
};
export async function login(credentials: LoginCredentials) {
export async function login(credentials: LoginCredentials): Promise<void> {
const appStore = useAppStore();
const response = await api.post(`/auth/login`, {
@@ -38,7 +38,7 @@ export async function login(credentials: LoginCredentials) {
let refreshTimeout: any;
export async function refresh({ navigate }: LogoutOptions = { navigate: true }) {
export async function refresh({ navigate }: LogoutOptions = { navigate: true }): Promise<string | undefined> {
const appStore = useAppStore();
try {
@@ -79,7 +79,7 @@ export type LogoutOptions = {
/**
* Everything that should happen when someone logs out, or is logged out through an external factor
*/
export async function logout(optionsRaw: LogoutOptions = {}) {
export async function logout(optionsRaw: LogoutOptions = {}): Promise<void> {
const appStore = useAppStore();
const defaultOptions: Required<LogoutOptions> = {

View File

@@ -11,7 +11,7 @@ interface HTMLExpandElement extends HTMLElement {
};
}
export default function (expandedParentClass = '', xAxis = false) {
export default function (expandedParentClass = '', xAxis = false): Record<string, any> {
const sizeProperty = xAxis ? 'width' : ('height' as 'width' | 'height');
const offsetProperty = `offset${capitalizeFirst(sizeProperty)}` as 'offsetHeight' | 'offsetWidth';

View File

@@ -38,6 +38,7 @@ import { defineComponent, PropType, computed, ref } from '@vue/composition-api';
import { Field } from '@/types';
import { getInterfaces } from '@/interfaces';
import { getDefaultInterfaceForType } from '@/utils/get-default-interface-for-type';
import { InterfaceConfig } from '@/interfaces/types';
export default defineComponent({
props: {
@@ -74,7 +75,9 @@ export default defineComponent({
const { interfaces } = getInterfaces();
const interfaceExists = computed(() => {
return !!interfaces.value.find((inter) => inter.id === props.field?.meta?.interface || 'text-input');
return !!interfaces.value.find(
(inter: InterfaceConfig) => inter.id === props.field?.meta?.interface || 'text-input'
);
});
return { interfaceExists, getDefaultInterfaceForType };

View File

@@ -39,7 +39,7 @@
<script lang="ts">
import { defineComponent, PropType, computed, ref, provide } from '@vue/composition-api';
import { useFieldsStore } from '@/stores/';
import { Field } from '@/types';
import { Field, FieldRaw } from '@/types';
import { useElementSize } from '@/composables/use-element-size';
import { clone, cloneDeep } from 'lodash';
import marked from 'marked';
@@ -113,7 +113,7 @@ export default defineComponent({
* admin can be made aware
*/
const unknownValidationErrors = computed(() => {
const fieldKeys = formFields.value.map((field) => field.field);
const fieldKeys = formFields.value.map((field: FieldRaw) => field.field);
return props.validationErrors.filter((error) => fieldKeys.includes(error.field) === false);
});

View File

@@ -14,7 +14,7 @@ export function usePopper(
reference: Ref<HTMLElement | null>,
popper: Ref<HTMLElement | null>,
options: Readonly<Ref<Readonly<{ placement: Placement; attached: boolean; arrow: boolean }>>>
) {
): Record<string, any> {
const popperInstance = ref<Instance | null>(null);
const styles = ref({});
const arrowStyles = ref({});

View File

@@ -75,7 +75,7 @@ export default defineComponent({
if (props.value === null) return { '--_v-slider-percentage': 50 };
let percentage = ((props.value - props.min) / (props.max - props.min)) * 100;
if (percentage === NaN) percentage = 0;
if (isNaN(percentage)) percentage = 0;
return { '--_v-slider-percentage': percentage };
});

View File

@@ -265,7 +265,7 @@ export default defineComponent({
if (_sort.value.desc === true) return itemsSorted.reverse();
return itemsSorted;
},
set: (value: object[]) => {
set: (value: Record<string, any>) => {
emit('update:items', value);
},
});

View File

@@ -19,19 +19,22 @@ type GroupableOptions = {
watch?: boolean;
};
export function useGroupable(options?: GroupableOptions) {
export function useGroupable(options?: GroupableOptions): Record<string, any> {
// Injects the registration / toggle functions from the parent scope
const parentFunctions = inject(options?.group || 'item-group', null);
if (isEmpty(parentFunctions)) {
return {
active: ref(false),
// eslint-disable-next-line @typescript-eslint/no-empty-function
toggle: () => {},
// eslint-disable-next-line @typescript-eslint/no-empty-function
activate: () => {},
// eslint-disable-next-line @typescript-eslint/no-empty-function
deactivate: () => {},
toggle: () => {
// Do nothing
},
activate: () => {
// Do nothing
},
deactivate: () => {
// Do nothing
},
};
}
@@ -104,7 +107,7 @@ export function useGroupableParent(
state: GroupableParentState = {},
options: GroupableParentOptions = {},
group = 'item-group'
) {
): Record<string, any> {
// References to the active state and value of the individual child items
const items = ref<GroupableInstance[]>([]);

View File

@@ -1,4 +1,4 @@
import { computed } from '@vue/composition-api';
import { computed, ComputedRef } from '@vue/composition-api';
export const sizeProps = {
xSmall: {
@@ -26,7 +26,7 @@ interface RequiredProps {
xLarge: boolean;
}
export default function useSizeClass<T>(props: T & RequiredProps) {
export default function useSizeClass<T>(props: T & RequiredProps): ComputedRef<string | null> {
const sizeClass = computed<string | null>(() => {
if (props.xSmall) return 'x-small';
if (props.small) return 'small';

View File

@@ -1,6 +1,6 @@
import { onBeforeMount, onBeforeUnmount, Ref } from '@vue/composition-api';
export default function unsavedChanges(isSavable: Ref<boolean>) {
export default function unsavedChanges(isSavable: Ref<boolean>): void {
onBeforeMount(() => {
window.addEventListener('beforeunload', beforeUnload);
});

View File

@@ -1,8 +1,8 @@
import { computed, Ref, ref } from '@vue/composition-api';
import { computed, ComputedRef, Ref, ref } from '@vue/composition-api';
import { useCollectionsStore, useFieldsStore } from '@/stores/';
import { Field } from '@/types';
export function useCollection(collectionKey: string | Ref<string>) {
export function useCollection(collectionKey: string | Ref<string>): Record<string, ComputedRef> {
const collectionsStore = useCollectionsStore();
const fieldsStore = useFieldsStore();
@@ -32,8 +32,7 @@ export function useCollection(collectionKey: string | Ref<string>) {
const primaryKeyField = computed(() => {
// Every collection has a primary key; rules of the land
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return fields.value?.find(
return fields.value.find(
(field) => field.collection === collection.value && field.schema?.is_primary_key === true
)!;
});

View File

@@ -1,9 +1,13 @@
import { Ref, ref, computed, watch } from '@vue/composition-api';
import { Ref, ref, computed, watch, ComputedRef } from '@vue/composition-api';
import { nanoid } from 'nanoid';
type EmitFunction = (event: string, ...args: any[]) => void;
export function useCustomSelection(currentValue: Ref<string>, items: Ref<any[]>, emit: EmitFunction) {
export function useCustomSelection(
currentValue: Ref<string>,
items: Ref<any[]>,
emit: EmitFunction
): Record<string, ComputedRef> {
const localOtherValue = ref('');
const otherValue = computed({
@@ -34,7 +38,11 @@ export function useCustomSelection(currentValue: Ref<string>, items: Ref<any[]>,
return { otherValue, usesOtherValue };
}
export function useCustomSelectionMultiple(currentValues: Ref<string[]>, items: Ref<any[]>, emit: EmitFunction) {
export function useCustomSelectionMultiple(
currentValues: Ref<string[]>,
items: Ref<any[]>,
emit: EmitFunction
): Record<string, any> {
type OtherValue = {
key: string;
value: string;

View File

@@ -8,7 +8,9 @@ declare global {
}
}
export default function useElementSize<T extends Element>(target: T | Ref<T> | Ref<undefined>) {
export default function useElementSize<T extends Element>(
target: T | Ref<T> | Ref<undefined>
): Record<string, Ref<number>> {
const width = ref(0);
const height = ref(0);

View File

@@ -5,7 +5,7 @@ export default function useEventListener<T extends EventTarget, E extends Event>
type: string,
handler: (this: T, evt: E) => void,
options?: AddEventListenerOptions
) {
): void {
onMounted(() => {
const t = isRef(target) ? target.value : target;
t.addEventListener(type, handler as (evt: Event) => void, options);

View File

@@ -1,4 +1,4 @@
import { Ref, computed } from '@vue/composition-api';
import { Ref, computed, ComputedRef } from '@vue/composition-api';
import { useFieldsStore, useRelationsStore } from '@/stores/';
import { Field, Relation } from '@/types';
import { cloneDeep } from 'lodash';
@@ -12,7 +12,7 @@ export default function useFieldTree(
strict: boolean = false,
inject?: Ref<{ fields: Field[]; relations: Relation[] } | null>,
filter: (field: Field) => boolean = () => true
) {
): Record<string, ComputedRef> {
const fieldsStore = useFieldsStore();
const relationsStore = useRelationsStore();

View File

@@ -1,13 +1,14 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import { computed, Ref } from '@vue/composition-api';
import { computed, ComputedRef, Ref } from '@vue/composition-api';
import getDefaultInterfaceForType from '@/utils/get-default-interface-for-type';
import { getInterfaces } from '@/interfaces';
import { FormField } from '@/components/v-form/types';
import { Field } from '@/types';
import { clone } from 'lodash';
import { InterfaceConfig } from '@/interfaces/types';
export default function useFormFields(fields: Ref<Field[]>) {
export default function useFormFields(fields: Ref<Field[]>): { formFields: ComputedRef } {
const { interfaces } = getInterfaces();
const formFields = computed(() => {
@@ -16,14 +17,14 @@ export default function useFormFields(fields: Ref<Field[]>) {
formFields = formFields.map((field, index) => {
if (!field.meta) return field;
let interfaceUsed = interfaces.value.find((int) => int.id === field.meta?.interface);
let interfaceUsed = interfaces.value.find((int: InterfaceConfig) => int.id === field.meta?.interface);
const interfaceExists = interfaceUsed !== undefined;
if (interfaceExists === false) {
field.meta.interface = getDefaultInterfaceForType(field.type);
}
interfaceUsed = interfaces.value.find((int) => int.id === field.meta?.interface);
interfaceUsed = interfaces.value.find((int: InterfaceConfig) => int.id === field.meta?.interface);
if (interfaceUsed?.hideLabel === true) {
(field as FormField).hideLabel = true;

View File

@@ -8,7 +8,7 @@ import { notify } from '@/utils/notify';
import { unexpectedError } from '@/utils/unexpected-error';
import { VALIDATION_TYPES } from '@/constants';
export function useItem(collection: Ref<string>, primaryKey: Ref<string | number | null>) {
export function useItem(collection: Ref<string>, primaryKey: Ref<string | number | null>): Record<string, any> {
const { info: collectionInfo, primaryKeyField } = useCollection(collection);
const item = ref<Record<string, any> | null>(null);

View File

@@ -17,7 +17,7 @@ type Query = {
searchQuery: Ref<string | null>;
};
export function useItems(collection: Ref<string>, query: Query) {
export function useItems(collection: Ref<string>, query: Query): Record<string, any> {
const { primaryKeyField, sortField } = useCollection(collection);
let loadingTimeout: any = null;

View File

@@ -1,11 +1,15 @@
import { computed, Ref } from '@vue/composition-api';
import { computed, ComputedRef, Ref } from '@vue/composition-api';
import { isAllowed } from '../utils/is-allowed';
import { useCollection } from './use-collection';
import { useUserStore, usePermissionsStore } from '@/stores';
import { cloneDeep } from 'lodash';
import { Field } from '@/types';
export function usePermissions(collection: Ref<string>, item: Ref<any>, isNew: Ref<boolean>) {
export function usePermissions(
collection: Ref<string>,
item: Ref<any>,
isNew: Ref<boolean>
): Record<string, ComputedRef> {
const userStore = useUserStore();
const permissionsStore = usePermissionsStore();

View File

@@ -5,7 +5,11 @@ import { useCollection } from '@/composables/use-collection';
import { Filter, Preset } from '@/types/';
export function usePreset(collection: Ref<string>, bookmark: Ref<number | null> = ref(null), temporary = false) {
export function usePreset(
collection: Ref<string>,
bookmark: Ref<number | null> = ref(null),
temporary = false
): Record<string, any> {
const presetsStore = usePresetsStore();
const userStore = useUserStore();
@@ -269,7 +273,6 @@ export function usePreset(collection: Ref<string>, bookmark: Ref<number | null>
if (data.id) delete data.id;
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
data.user = userStore.state.currentUser!.id;
return await savePreset(data);

View File

@@ -1,7 +1,7 @@
import { ref, onMounted, onUnmounted, Ref, isRef, computed } from '@vue/composition-api';
import { throttle } from 'lodash';
export default function useScrollDistance<T extends Element>(t: T | Ref<T | null | Vue>) {
export default function useScrollDistance<T extends Element>(t: T | Ref<T | null | Vue>): Record<string, Ref> {
const top = ref<number>();
const left = ref<number>();

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