mirror of
https://github.com/directus/directus.git
synced 2026-04-25 03:00:53 -04:00
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:
@@ -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();
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 })
|
||||
)
|
||||
);
|
||||
|
||||
@@ -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:',
|
||||
|
||||
@@ -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');
|
||||
|
||||
|
||||
@@ -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');
|
||||
|
||||
|
||||
@@ -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');
|
||||
|
||||
|
||||
@@ -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') {
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
export function extractError(error: any) {
|
||||
export function extractError(error: Error): Error {
|
||||
return error;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
40
api/src/exceptions/database/dialects/types.ts
Normal file
40
api/src/exceptions/database/dialects/types.ts
Normal 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;
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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(),
|
||||
};
|
||||
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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, {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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 || {};
|
||||
|
||||
@@ -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]) =>
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
3
api/src/types/deep-partial.d.ts
vendored
3
api/src/types/deep-partial.d.ts
vendored
@@ -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]
|
||||
|
||||
4
api/src/types/express-session.d.ts
vendored
4
api/src/types/express-session.d.ts
vendored
@@ -1,4 +1,6 @@
|
||||
import * as expressSession from 'express-session';
|
||||
import { SessionData } from 'express-session';
|
||||
|
||||
export = SessionData;
|
||||
|
||||
declare module 'express-session' {
|
||||
interface SessionData {
|
||||
|
||||
@@ -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 () {
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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> = {};
|
||||
|
||||
@@ -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: [],
|
||||
|
||||
@@ -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>;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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') {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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> = {
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
|
||||
@@ -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({});
|
||||
|
||||
@@ -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 };
|
||||
});
|
||||
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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[]>([]);
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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
|
||||
)!;
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user