Merge branch 'main' into aggregation

This commit is contained in:
rijkvanzanten
2021-09-13 12:50:19 -04:00
476 changed files with 28319 additions and 34337 deletions

View File

@@ -14,6 +14,7 @@ export function getGeometryHelper(): KnexSpatial {
mariadb: KnexSpatial_MySQL,
sqlite3: KnexSpatial,
pg: KnexSpatial_PG,
postgres: KnexSpatial_PG,
redshift: KnexSpatial_Redshift,
mssql: KnexSpatial_MSSQL,
oracledb: KnexSpatial_Oracle,

View File

@@ -8,6 +8,7 @@ import { validateEnv } from '../utils/validate-env';
import fse from 'fs-extra';
import path from 'path';
import { merge } from 'lodash';
import { promisify } from 'util';
let database: Knex | null = null;
let inspector: ReturnType<typeof SchemaInspector> | null = null;
@@ -22,6 +23,7 @@ export default function getDatabase(): Knex {
'DB_SEARCH_PATH',
'DB_CONNECTION_STRING',
'DB_POOL',
'DB_EXCLUDE_TABLES',
]);
const poolConfig = getConfigFromEnv('DB_POOL');
@@ -54,7 +56,12 @@ export default function getDatabase(): Knex {
connection: env.DB_CONNECTION_STRING || connectionConfig,
log: {
warn: (msg) => {
// Ignore warnings about returning not being supported in some DBs
if (msg.startsWith('.returning()')) return;
// Ignore warning about MySQL not supporting TRX for DDL
if (msg.startsWith('Transaction was implicitly committed, do not mix transactions and DDL with MySQL')) return;
return logger.warn(msg);
},
error: (msg) => logger.error(msg),
@@ -66,8 +73,14 @@ export default function getDatabase(): Knex {
if (env.DB_CLIENT === 'sqlite3') {
knexConfig.useNullAsDefault = true;
poolConfig.afterCreate = (conn: any, cb: any) => {
conn.run('PRAGMA foreign_keys = ON', cb);
poolConfig.afterCreate = async (conn: any, callback: any) => {
logger.trace('Enabling SQLite Foreign Keys support...');
const run = promisify(conn.run.bind(conn));
await run('PRAGMA foreign_keys = ON');
callback(null, conn);
};
}
@@ -111,7 +124,7 @@ export async function hasDatabaseConnection(database?: Knex): Promise<boolean> {
database = database ?? getDatabase();
try {
if (env.DB_CLIENT === 'oracledb') {
if (getDatabaseClient(database) === 'oracle') {
await database.raw('select 1 from DUAL');
} else {
await database.raw('SELECT 1');
@@ -123,28 +136,48 @@ export async function hasDatabaseConnection(database?: Knex): Promise<boolean> {
}
}
export async function validateDBConnection(database?: Knex): Promise<void> {
export async function validateDatabaseConnection(database?: Knex): Promise<void> {
database = database ?? getDatabase();
try {
if (env.DB_CLIENT === 'oracledb') {
if (getDatabaseClient(database) === 'oracle') {
await database.raw('select 1 from DUAL');
} else {
await database.raw('SELECT 1');
}
} catch (error) {
} catch (error: any) {
logger.error(`Can't connect to the database.`);
logger.error(error);
process.exit(1);
}
}
export function getDatabaseClient(database?: Knex): 'mysql' | 'postgres' | 'sqlite' | 'oracle' | 'mssql' {
database = database ?? getDatabase();
switch (database.client.constructor.name) {
case 'Client_MySQL':
return 'mysql';
case 'Client_PG':
return 'postgres';
case 'Client_SQLite3':
return 'sqlite';
case 'Client_Oracledb':
case 'Client_Oracle':
return 'oracle';
case 'Client_MSSQL':
return 'mssql';
}
throw new Error(`Couldn't extract database client`);
}
export async function isInstalled(): Promise<boolean> {
const inspector = getSchemaInspector();
// The existence of a directus_collections table alone isn't a "proper" check to see if everything
// is installed correctly of course, but it's safe enough to assume that this collection only
// exists when using the installer CLI.
// exists when Directus is properly installed.
return await inspector.hasTable('directus_collections');
}
@@ -173,9 +206,45 @@ export async function validateMigrations(): Promise<boolean> {
);
return requiredVersions.every((version) => completedVersions.includes(version));
} catch (error) {
} catch (error: any) {
logger.error(`Database migrations cannot be found`);
logger.error(error);
throw process.exit(1);
}
}
/**
* These database extensions should be optional, so we don't throw or return any problem states when they don't
*/
export async function validateDatabaseExtensions(): Promise<void> {
const database = getDatabase();
const databaseClient = getDatabaseClient(database);
if (databaseClient === 'postgres') {
let available = false;
let installed = false;
const exists = await database.raw(`SELECT name FROM pg_available_extensions WHERE name = 'postgis';`);
if (exists.rows.length > 0) {
available = true;
}
if (available) {
try {
await database.raw(`SELECT PostGIS_version();`);
installed = true;
} catch {
installed = false;
}
}
if (available === false) {
logger.warn(`PostGIS isn't installed. Geometry type support will be limited.`);
} else if (available === true && installed === false) {
logger.warn(
`PostGIS is installed, but hasn't been activated on this database. Geometry type support will be limited.`
);
}
}
}

View File

@@ -68,7 +68,7 @@ export async function up(knex: Knex): Promise<void> {
await knex(constraint.many_collection)
.update({ [constraint.many_field]: null })
.whereIn(currentPrimaryKeyField, ids);
} catch (err) {
} catch (err: any) {
logger.error(
`${constraint.many_collection}.${constraint.many_field} contains illegal foreign keys which couldn't be set to NULL. Please fix these references and rerun this migration to complete the upgrade.`
);
@@ -111,7 +111,7 @@ export async function up(knex: Knex): Promise<void> {
builder.onDelete('SET NULL');
}
});
} catch (err) {
} catch (err: any) {
logger.warn(
`Couldn't add foreign key constraint for ${constraint.many_collection}.${constraint.many_field}<->${constraint.one_collection}`
);
@@ -140,7 +140,7 @@ export async function down(knex: Knex): Promise<void> {
await knex.schema.alterTable(relation.many_collection, (table) => {
table.dropForeign([relation.many_field]);
});
} catch (err) {
} catch (err: any) {
logger.warn(
`Couldn't drop foreign key constraint for ${relation.many_collection}.${relation.many_field}<->${relation.one_collection}`
);

View File

@@ -99,7 +99,7 @@ export async function up(knex: Knex): Promise<void> {
await knex.schema.alterTable(update.table, (table) => {
table.dropForeign([constraint.column], existingForeignKey?.constraint_name || undefined);
});
} catch (err) {
} catch (err: any) {
logger.warn(`Couldn't drop foreign key ${update.table}.${constraint.column}->${constraint.references}`);
logger.warn(err);
}
@@ -114,7 +114,7 @@ export async function up(knex: Knex): Promise<void> {
// Knex uses a default convention for index names: `table_column_type`
table.dropIndex([constraint.column], `${update.table}_${constraint.column}_foreign`);
});
} catch (err) {
} catch (err: any) {
logger.warn(
`Couldn't clean up index for foreign key ${update.table}.${constraint.column}->${constraint.references}`
);
@@ -126,7 +126,7 @@ export async function up(knex: Knex): Promise<void> {
await knex.schema.alterTable(update.table, (table) => {
table.foreign(constraint.column).references(constraint.references).onDelete(constraint.on_delete);
});
} catch (err) {
} catch (err: any) {
logger.warn(`Couldn't add foreign key to ${update.table}.${constraint.column}->${constraint.references}`);
logger.warn(err);
}
@@ -141,7 +141,7 @@ export async function down(knex: Knex): Promise<void> {
await knex.schema.alterTable(update.table, (table) => {
table.dropForeign([constraint.column]);
});
} catch (err) {
} catch (err: any) {
logger.warn(`Couldn't drop foreign key ${update.table}.${constraint.column}->${constraint.references}`);
logger.warn(err);
}
@@ -156,7 +156,7 @@ export async function down(knex: Knex): Promise<void> {
// Knex uses a default convention for index names: `table_column_type`
table.dropIndex([constraint.column], `${update.table}_${constraint.column}_foreign`);
});
} catch (err) {
} catch (err: any) {
logger.warn(
`Couldn't clean up index for foreign key ${update.table}.${constraint.column}->${constraint.references}`
);
@@ -168,7 +168,7 @@ export async function down(knex: Knex): Promise<void> {
await knex.schema.alterTable(update.table, (table) => {
table.foreign(constraint.column).references(constraint.references);
});
} catch (err) {
} catch (err: any) {
logger.warn(`Couldn't add foreign key to ${update.table}.${constraint.column}->${constraint.references}`);
logger.warn(err);
}

View File

@@ -14,7 +14,7 @@ export async function up(knex: Knex): Promise<void> {
if (options.icon) newOptions.headerIcon = options.icon;
if (options.color) newOptions.headerColor = options.color;
} catch (err) {
} catch (err: any) {
logger.warn(`Couldn't convert previous options from field ${dividerGroup.collection}.${dividerGroup.field}`);
logger.warn(err);
}
@@ -27,7 +27,7 @@ export async function up(knex: Knex): Promise<void> {
options: JSON.stringify(newOptions),
})
.where('id', '=', dividerGroup.id);
} catch (err) {
} catch (err: any) {
logger.warn(`Couldn't update ${dividerGroup.collection}.${dividerGroup.field} to new group interface`);
logger.warn(err);
}

View File

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

View File

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

View File

@@ -1,10 +1,9 @@
/* eslint-disable no-console */
import formatTitle from '@directus/format-title';
import fse from 'fs-extra';
import { Knex } from 'knex';
import path from 'path';
import env from '../../env';
import logger from '../../logger';
import { Migration } from '../../types';
export default async function run(database: Knex, direction: 'up' | 'down' | 'latest'): Promise<void> {
@@ -62,7 +61,7 @@ export default async function run(database: Knex, direction: 'up' | 'down' | 'la
const { up } = require(nextVersion.file);
console.log(`Applying ${nextVersion.name}...`);
logger.info(`Applying ${nextVersion.name}...`);
await up(database);
await database.insert({ version: nextVersion.version, name: nextVersion.name }).into('directus_migrations');
@@ -83,7 +82,7 @@ export default async function run(database: Knex, direction: 'up' | 'down' | 'la
const { down } = require(migration.file);
console.log(`Undoing ${migration.name}...`);
logger.info(`Undoing ${migration.name}...`);
await down(database);
await database('directus_migrations').delete().where({ version: migration.version });
@@ -94,7 +93,7 @@ export default async function run(database: Knex, direction: 'up' | 'down' | 'la
if (migration.completed === false) {
const { up } = require(migration.file);
console.log(`Applying ${migration.name}...`);
logger.info(`Applying ${migration.name}...`);
await up(database);
await database.insert({ version: migration.version, name: migration.name }).into('directus_migrations');

View File

@@ -8,7 +8,6 @@ const defaults: Partial<Permission> = {
validation: null,
presets: null,
fields: ['*'],
limit: null,
system: true,
};

View File

@@ -12,48 +12,48 @@ defaults:
data:
- collection: directus_activity
note: Accountability logs for all events
note: $t:directus_collection.directus_activity
- collection: directus_collections
icon: list_alt
note: Additional collection configuration and metadata
note: $t:directus_collection.directus_collections
- collection: directus_fields
icon: input
note: Additional field configuration and metadata
note: $t:directus_collection.directus_fields
- collection: directus_files
icon: folder
note: Metadata for all managed file assets
note: $t:directus_collection.directus_files
display_template: '{{ $thumbnail }} {{ title }}'
- collection: directus_folders
note: Provides virtual directories for files
note: $t:directus_collection.directus_folders
display_template: '{{ name }}'
- collection: directus_migrations
note: What version of the database you're using
note: $t:directus_collection.directus_migrations
- collection: directus_permissions
icon: admin_panel_settings
note: Access permissions for each role
note: $t:directus_collection.directus_permissions
- collection: directus_presets
icon: bookmark_border
note: Presets for collection defaults and bookmarks
note: $t:directus_collection.directus_presets
accountability: null
- collection: directus_relations
icon: merge_type
note: Relationship configuration and metadata
note: $t:directus_collection.directus_relations
- collection: directus_revisions
note: Data snapshots for all activity
note: $t:directus_collection.directus_revisions
- collection: directus_roles
icon: supervised_user_circle
note: Permission groups for system users
note: $t:directus_collection.directus_roles
- collection: directus_sessions
note: User session information
note: $t:directus_collection.directus_sessions
- collection: directus_settings
singleton: true
note: Project configuration options
note: $t:directus_collection.directus_settings
- collection: directus_users
archive_field: status
archive_value: archived
unarchive_value: draft
icon: people_alt
note: System users for the platform
note: $t:directus_collection.directus_users
display_template: '{{ first_name }} {{ last_name }}'
- collection: directus_webhooks
note: Configuration for event-based HTTP requests
note: $t:directus_collection.directus_webhooks

View File

@@ -13,19 +13,19 @@ fields:
defaultForeground: 'var(--foreground-normal)'
defaultBackground: 'var(--background-normal-alt)'
choices:
- text: Create
- text: $t:field_options.directus_activity.create
value: create
foreground: 'var(--primary)'
background: 'var(--primary-25)'
- text: Update
- text: $t:field_options.directus_activity.update
value: update
foreground: 'var(--blue)'
background: 'var(--blue-25)'
- text: Delete
- text: $t:field_options.directus_activity.delete
value: delete
foreground: 'var(--danger)'
background: 'var(--danger-25)'
- text: Login
- text: $t:field_options.directus_activity.login
value: authenticate
foreground: 'var(--purple)'
background: 'var(--purple-25)'

View File

@@ -8,7 +8,7 @@ fields:
interface: presentation-divider
options:
icon: box
title: Collection Setup
title: $t:field_options.directus_collections.collection_setup
width: full
- field: collection
@@ -32,7 +32,7 @@ fields:
- field: color
interface: select-color
options:
placeholder: Choose a color...
placeholder: $t:field_options.directus_collections.note_placeholder
width: half
- field: display_template
@@ -45,7 +45,7 @@ fields:
special: boolean
interface: boolean
options:
label: Hide within the App
label: $t:field_options.directus_collections.hidden_label
width: half
- field: singleton
@@ -102,7 +102,7 @@ fields:
interface: presentation-divider
options:
icon: archive
title: Archive
title: $t:field_options.directus_collections.archive_divider
width: full
- field: archive_field
@@ -110,14 +110,14 @@ fields:
options:
collectionField: collection
allowNone: true
placeholder: Choose a field...
placeholder: $t:field_options.directus_collections.archive_field
width: half
- field: archive_app_filter
interface: boolean
special: boolean
options:
label: Enable App Archive Filter
label: $t:field_options.directus_collections.archive_app_filter
width: half
- field: archive_value
@@ -125,7 +125,7 @@ fields:
options:
font: monospace
iconRight: archive
placeholder: Value set when archiving...
placeholder: $t:field_options.directus_collections.archive_value
width: half
- field: unarchive_value
@@ -133,7 +133,7 @@ fields:
options:
font: monospace
iconRight: unarchive
placeholder: Value set when unarchiving...
placeholder: $t:field_options.directus_collections.unarchive_value
width: half
- field: sort_divider
@@ -143,14 +143,14 @@ fields:
interface: presentation-divider
options:
icon: sort
title: Sort
title: $t:field_options.directus_collections.divider
width: full
- field: sort_field
interface: system-field
options:
collectionField: collection
placeholder: Choose a field...
placeholder: $t:field_options.directus_collections.sort_field
typeAllowList:
- float
- decimal
@@ -165,7 +165,7 @@ fields:
interface: presentation-divider
options:
icon: admin_panel_settings
title: Accountability
title: $t:field_options.directus_collections.accountability_divider
width: full
- field: accountability

View File

@@ -10,14 +10,14 @@ fields:
interface: input
options:
iconRight: title
placeholder: A unique title...
placeholder: $t:field_options.directus_files.title
width: full
- field: description
interface: input-multiline
width: full
options:
placeholder: An optional description...
placeholder: $t:field_options.directus_files.description
- field: tags
interface: tags
@@ -35,7 +35,7 @@ fields:
interface: input
options:
iconRight: place
placeholder: An optional location...
placeholder: $t:field_options.directus_files.location
width: half
- field: storage
@@ -49,7 +49,7 @@ fields:
interface: presentation-divider
options:
icon: insert_drive_file
title: File Naming
title: $t:field_options.directus_files.storage_divider
special:
- alias
- no-data
@@ -59,7 +59,7 @@ fields:
interface: input
options:
iconRight: publish
placeholder: Name on disk storage...
placeholder: $t:field_options.directus_files.filename_disk
readonly: true
width: half
@@ -67,7 +67,7 @@ fields:
interface: input
options:
iconRight: get_app
placeholder: Name when downloading...
placeholder: $t:field_options.directus_files.filename_download
width: half
- field: metadata
@@ -106,6 +106,7 @@ fields:
display: user
width: half
hidden: true
special: user-created
- field: uploaded_on
display: datetime

View File

@@ -15,9 +15,6 @@ fields:
- field: role
width: half
- field: limit
width: half
- field: collection
width: half

View File

@@ -9,7 +9,7 @@ fields:
- field: name
interface: input
options:
placeholder: The unique name for this role...
placeholder: $t:field_options.directus_roles.name
width: half
- field: icon
@@ -20,7 +20,7 @@ fields:
- field: description
interface: input
options:
placeholder: A description of this role...
placeholder: $t:field_options.directus_roles.description
width: full
- field: app_access
@@ -36,7 +36,7 @@ fields:
- field: ip_access
interface: tags
options:
placeholder: Add allowed IP addresses, leave empty to allow all...
placeholder: $t:field_options.directus_roles.ip_access
special: csv
width: full
@@ -60,13 +60,13 @@ fields:
template: '{{ name }}'
addLabel: Add New Module...
fields:
- name: Icon
- name: $t:field_options.directus_roles.fields.icon_name
field: icon
type: string
meta:
interface: select-icon
width: half
- name: Name
- name: $t:field_options.directus_roles.fields.name_name
field: name
type: string
meta:
@@ -74,8 +74,8 @@ fields:
width: half
options:
iconRight: title
placeholder: Enter a title...
- name: Link
placeholder:
- name: $t:field_options.directus_roles.fields.link_name
field: link
type: string
meta:
@@ -83,7 +83,7 @@ fields:
width: full
options:
iconRight: link
placeholder: Relative or absolute URL...
placeholder: $t:field_options.directus_roles.fields.link_placeholder
special: json
width: full
@@ -91,9 +91,9 @@ fields:
interface: list
options:
template: '{{ group_name }}'
addLabel: Add New Group...
addLabel: $t:field_options.directus_roles.collection_list.group_name_addLabel
fields:
- name: Group Name
- name: $t:field_options.directus_roles.collection_list.fields.group_name
field: group_name
type: string
meta:
@@ -101,10 +101,10 @@ fields:
interface: input
options:
iconRight: title
placeholder: Label this group...
placeholder: $t:field_options.directus_roles.collection_list.fields.group_placeholder
schema:
is_nullable: false
- name: Type
- name: $t:field_options.directus_roles.collection_list.fields.type_name
field: accordion
type: string
schema:
@@ -115,21 +115,21 @@ fields:
options:
choices:
- value: always_open
text: Always Open
text: $t:field_options.directus_roles.collection_list.fields.choices_always
- value: start_open
text: Start Open
text: $t:field_options.directus_roles.collection_list.fields.choices_start_open
- value: start_collapsed
text: Start Collapsed
- name: Collections
text: $t:field_options.directus_roles.collection_list.fields.choices_start_collapsed
- name: $t:field_options.directus_roles.collections_name
field: collections
type: JSON
meta:
interface: list
options:
addLabel: Add New Collection...
addLabel: $t:field_options.directus_roles.collections_addLabel
template: '{{ collection }}'
fields:
- name: Collection
- name: $t:field_options.directus_roles.collections_name
field: collection
type: string
meta:

View File

@@ -8,7 +8,7 @@ fields:
interface: input
options:
iconRight: title
placeholder: My project...
placeholder: $t:field_options.directus_settings.project_name_placeholder
translations:
language: en-US
translations: Name
@@ -26,7 +26,7 @@ fields:
- field: project_color
interface: select-color
note: Login & Logo Background
note: $t:field_options.directus_settings.project_logo_note
translations:
language: en-US
translations: Brand Color
@@ -67,7 +67,7 @@ fields:
- field: public_note
interface: input-multiline
options:
placeholder: A short, public message that supports markdown formatting...
placeholder: $t:field_options.directus_settings.public_note_placeholder
width: full
- field: security_divider
@@ -85,11 +85,11 @@ fields:
options:
choices:
- value: null
text: None  Not Recommended
text: $t:field_options.directus_settings.auth_password_policy.none_text
- value: '/^.{8,}$/'
text: Weak Minimum 8 Characters
text: $t:field_options.directus_settings.auth_password_policy.weak_text
- value: "/(?=^.{8,}$)(?=.*\\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[!@#$%^&*()_+}{';'?>.<,])(?!.*\\s).*$/"
text: Strong Upper / Lowercase / Numbers / Special
text: $t:field_options.directus_settings.auth_password_policy.strong_text
allowOther: true
width: half
@@ -135,13 +135,13 @@ fields:
options:
choices:
- value: contain
text: Contain (preserve aspect ratio)
text: $t:field_options.directus_settings.storage_asset_presets.fit.contain_text
- value: cover
text: Cover (forces exact size)
text: $t:field_options.directus_settings.storage_asset_presets.fit.cover_text
- value: inside
text: Fit inside
text: $t:field_options.directus_settings.storage_asset_presets.fit.fit_text
- value: outside
text: Fit outside
text: $t:field_options.directus_settings.storage_asset_presets.fit.outside_text
width: half
- field: width
name: $t:width
@@ -181,7 +181,7 @@ fields:
interface: boolean
width: half
options:
label: Don't upscale images
label: $t:no_upscale
- field: format
name: Format
type: string
@@ -203,15 +203,14 @@ fields:
text: Tiff
width: half
- field: transforms
name: Additional Transformations
name: $t:field_options.directus_settings.additional_transforms
type: json
schema:
is_nullable: false
default_value: []
meta:
note:
The Sharp method name and its arguments. See https://sharp.pixelplumbing.com/api-constructor for more
information.
note: $t:field_options.directus_settings.transforms_note
interface: json
options:
template: >
@@ -279,8 +278,8 @@ fields:
interface: input
options:
icon: key
title: Mapbox Access Token
placeholder: pk.eyJ1Ijo.....
title: $t:field_options.directus_settings.mapbox_key
placeholder: $t:field_options.directus_settings.mapbox_placeholder
iconLeft: vpn_key
font: monospace
width: half
@@ -306,11 +305,11 @@ fields:
options:
choices:
- value: raster
text: Raster
text: $t:field_options.directus_settings.basemaps_raster
- value: tile
text: Raster TileJSON
text: $t:field_options.directus_settings.basemaps_tile
- value: style
text: Mapbox Style
text: $t:field_options.directus_settings.basemaps_style
- field: url
name: $t:url
schema:
@@ -319,3 +318,17 @@ fields:
interface: text-input
options:
placeholder: http://{a-c}.tile.openstreetmap.org/{z}/{x}/{y}.png
- field: tileSize
name: $t:tile_size
schema:
is_nullable: true
meta:
interface: input
options:
placeholder: '512'
conditions:
- name: typeNeqRaster
rule:
type:
_neq: 'raster'
hidden: true