mirror of
https://github.com/directus/directus.git
synced 2026-04-25 03:00:53 -04:00
Merge branch 'main' into assets
This commit is contained in:
@@ -46,15 +46,13 @@ router.post(
|
||||
const ip = req.ip;
|
||||
const userAgent = req.get('user-agent');
|
||||
|
||||
const { accessToken, refreshToken, expires, id } = await authenticationService.authenticate(
|
||||
{
|
||||
ip,
|
||||
userAgent,
|
||||
email,
|
||||
password,
|
||||
otp,
|
||||
}
|
||||
);
|
||||
const { accessToken, refreshToken, expires } = await authenticationService.authenticate({
|
||||
ip,
|
||||
userAgent,
|
||||
email,
|
||||
password,
|
||||
otp,
|
||||
});
|
||||
|
||||
const payload = {
|
||||
data: { access_token: accessToken, expires },
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
import Knex from 'knex';
|
||||
|
||||
export async function up(knex: Knex) {
|
||||
await knex.schema.alterTable('directus_fields', (table) => {
|
||||
table.dropForeign(['collection']);
|
||||
});
|
||||
|
||||
await knex.schema.alterTable('directus_activity', (table) => {
|
||||
table.dropForeign(['collection']);
|
||||
});
|
||||
|
||||
await knex.schema.alterTable('directus_permissions', (table) => {
|
||||
table.dropForeign(['collection']);
|
||||
});
|
||||
|
||||
await knex.schema.alterTable('directus_presets', (table) => {
|
||||
table.dropForeign(['collection']);
|
||||
});
|
||||
|
||||
await knex.schema.alterTable('directus_relations', (table) => {
|
||||
table.dropForeign(['one_collection']);
|
||||
table.dropForeign(['many_collection']);
|
||||
});
|
||||
|
||||
await knex.schema.alterTable('directus_revisions', (table) => {
|
||||
table.dropForeign(['collection']);
|
||||
});
|
||||
}
|
||||
|
||||
export async function down(knex: Knex) {
|
||||
await knex.schema.alterTable('directus_fields', (table) => {
|
||||
table.foreign('collection').references('directus_collections.collection');
|
||||
});
|
||||
|
||||
await knex.schema.alterTable('directus_activity', (table) => {
|
||||
table.foreign('collection').references('directus_collections.collection');
|
||||
});
|
||||
|
||||
await knex.schema.alterTable('directus_permissions', (table) => {
|
||||
table.foreign('collection').references('directus_collections.collection');
|
||||
});
|
||||
|
||||
await knex.schema.alterTable('directus_presets', (table) => {
|
||||
table.foreign('collection').references('directus_collections.collection');
|
||||
});
|
||||
|
||||
await knex.schema.alterTable('directus_relations', (table) => {
|
||||
table.foreign('one_collection').references('directus_collections.collection');
|
||||
table.foreign('many_collection').references('directus_collections.collection');
|
||||
});
|
||||
|
||||
await knex.schema.alterTable('directus_revisions', (table) => {
|
||||
table.foreign('collection').references('directus_collections.collection');
|
||||
});
|
||||
}
|
||||
128
api/src/database/migrations/20201029A-remove-system-relations.ts
Normal file
128
api/src/database/migrations/20201029A-remove-system-relations.ts
Normal file
@@ -0,0 +1,128 @@
|
||||
import Knex from 'knex';
|
||||
import { merge } from 'lodash';
|
||||
|
||||
export async function up(knex: Knex) {
|
||||
await knex('directus_relations')
|
||||
.delete()
|
||||
.where('many_collection', 'like', 'directus_%')
|
||||
.andWhere('one_collection', 'like', 'directus_%');
|
||||
}
|
||||
|
||||
export async function down(knex: Knex) {
|
||||
const defaults = {
|
||||
many_collection: 'directus_users',
|
||||
many_field: null,
|
||||
many_primary: null,
|
||||
one_collection: null,
|
||||
one_field: null,
|
||||
one_primary: null,
|
||||
junction_field: null,
|
||||
};
|
||||
|
||||
const systemRelations = [
|
||||
{
|
||||
many_collection: 'directus_users',
|
||||
many_field: 'role',
|
||||
many_primary: 'id',
|
||||
one_collection: 'directus_roles',
|
||||
one_field: 'users',
|
||||
one_primary: 'id',
|
||||
},
|
||||
{
|
||||
many_collection: 'directus_users',
|
||||
many_field: 'avatar',
|
||||
many_primary: 'id',
|
||||
one_collection: 'directus_files',
|
||||
one_primary: 'id',
|
||||
},
|
||||
{
|
||||
many_collection: 'directus_revisions',
|
||||
many_field: 'activity',
|
||||
many_primary: 'id',
|
||||
one_collection: 'directus_activity',
|
||||
one_field: 'revisions',
|
||||
one_primary: 'id',
|
||||
},
|
||||
{
|
||||
many_collection: 'directus_presets',
|
||||
many_field: 'user',
|
||||
many_primary: 'id',
|
||||
one_collection: 'directus_users',
|
||||
one_primary: 'id',
|
||||
},
|
||||
{
|
||||
many_collection: 'directus_presets',
|
||||
many_field: 'role',
|
||||
many_primary: 'id',
|
||||
one_collection: 'directus_roles',
|
||||
one_primary: 'id',
|
||||
},
|
||||
{
|
||||
many_collection: 'directus_folders',
|
||||
many_field: 'parent',
|
||||
many_primary: 'id',
|
||||
one_collection: 'directus_folders',
|
||||
one_primary: 'id',
|
||||
},
|
||||
{
|
||||
many_collection: 'directus_files',
|
||||
many_field: 'folder',
|
||||
many_primary: 'id',
|
||||
one_collection: 'directus_folders',
|
||||
one_primary: 'id',
|
||||
},
|
||||
{
|
||||
many_collection: 'directus_files',
|
||||
many_field: 'uploaded_by',
|
||||
many_primary: 'id',
|
||||
one_collection: 'directus_users',
|
||||
one_primary: 'id',
|
||||
},
|
||||
{
|
||||
many_collection: 'directus_fields',
|
||||
many_field: 'collection',
|
||||
many_primary: 'id',
|
||||
one_collection: 'directus_collections',
|
||||
one_field: 'fields',
|
||||
one_primary: 'collection',
|
||||
},
|
||||
{
|
||||
many_collection: 'directus_activity',
|
||||
many_field: 'user',
|
||||
many_primary: 'id',
|
||||
one_collection: 'directus_users',
|
||||
one_primary: 'id',
|
||||
},
|
||||
{
|
||||
many_collection: 'directus_settings',
|
||||
many_field: 'project_logo',
|
||||
many_primary: 'id',
|
||||
one_collection: 'directus_files',
|
||||
one_primary: 'id',
|
||||
},
|
||||
{
|
||||
many_collection: 'directus_settings',
|
||||
many_field: 'public_foreground',
|
||||
many_primary: 'id',
|
||||
one_collection: 'directus_files',
|
||||
one_primary: 'id',
|
||||
},
|
||||
{
|
||||
many_collection: 'directus_settings',
|
||||
many_field: 'public_background',
|
||||
many_primary: 'id',
|
||||
one_collection: 'directus_files',
|
||||
one_primary: 'id',
|
||||
},
|
||||
].map((row) => {
|
||||
for (const [key, value] of Object.entries(row)) {
|
||||
if (value !== null && (typeof value === 'object' || Array.isArray(value))) {
|
||||
(row as any)[key] = JSON.stringify(value);
|
||||
}
|
||||
}
|
||||
|
||||
return merge({}, defaults, row);
|
||||
});
|
||||
|
||||
await knex.insert(systemRelations).into('directus_relations');
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
import Knex from 'knex';
|
||||
import { merge } from 'lodash';
|
||||
|
||||
export async function up(knex: Knex) {
|
||||
await knex('directus_collections').delete().where('collection', 'like', 'directus_%');
|
||||
}
|
||||
|
||||
export async function down(knex: Knex) {
|
||||
const defaults = {
|
||||
collection: null,
|
||||
hidden: false,
|
||||
singleton: false,
|
||||
icon: null,
|
||||
note: null,
|
||||
translations: null,
|
||||
display_template: null,
|
||||
};
|
||||
|
||||
const systemCollections = [
|
||||
{
|
||||
collection: 'directus_activity',
|
||||
note: 'Accountability logs for all events',
|
||||
},
|
||||
{
|
||||
collection: 'directus_collections',
|
||||
icon: 'list_alt',
|
||||
note: 'Additional collection configuration and metadata',
|
||||
},
|
||||
{
|
||||
collection: 'directus_fields',
|
||||
icon: 'input',
|
||||
note: 'Additional field configuration and metadata',
|
||||
},
|
||||
{
|
||||
collection: 'directus_files',
|
||||
icon: 'folder',
|
||||
note: 'Metadata for all managed file assets',
|
||||
},
|
||||
{
|
||||
collection: 'directus_folders',
|
||||
note: 'Provides virtual directories for files',
|
||||
},
|
||||
{
|
||||
collection: 'directus_permissions',
|
||||
icon: 'admin_panel_settings',
|
||||
note: 'Access permissions for each role',
|
||||
},
|
||||
{
|
||||
collection: 'directus_presets',
|
||||
icon: 'bookmark_border',
|
||||
note: 'Presets for collection defaults and bookmarks',
|
||||
},
|
||||
{
|
||||
collection: 'directus_relations',
|
||||
icon: 'merge_type',
|
||||
note: 'Relationship configuration and metadata',
|
||||
},
|
||||
{
|
||||
collection: 'directus_revisions',
|
||||
note: 'Data snapshots for all activity',
|
||||
},
|
||||
{
|
||||
collection: 'directus_roles',
|
||||
icon: 'supervised_user_circle',
|
||||
note: 'Permission groups for system users',
|
||||
},
|
||||
{
|
||||
collection: 'directus_sessions',
|
||||
note: 'User session information',
|
||||
},
|
||||
{
|
||||
collection: 'directus_settings',
|
||||
singleton: true,
|
||||
note: 'Project configuration options',
|
||||
},
|
||||
{
|
||||
collection: 'directus_users',
|
||||
archive_field: 'status',
|
||||
archive_value: 'archived',
|
||||
unarchive_value: 'draft',
|
||||
icon: 'people_alt',
|
||||
note: 'System users for the platform',
|
||||
},
|
||||
{
|
||||
collection: 'directus_webhooks',
|
||||
note: 'Configuration for event-based HTTP requests',
|
||||
},
|
||||
].map((row) => {
|
||||
for (const [key, value] of Object.entries(row)) {
|
||||
if (value !== null && (typeof value === 'object' || Array.isArray(value))) {
|
||||
(row as any)[key] = JSON.stringify(value);
|
||||
}
|
||||
}
|
||||
|
||||
return merge({}, defaults, row);
|
||||
});
|
||||
|
||||
await knex.insert(systemCollections).into('directus_collections');
|
||||
}
|
||||
1654
api/src/database/migrations/20201029C-remove-system-fields.ts
Normal file
1654
api/src/database/migrations/20201029C-remove-system-fields.ts
Normal file
File diff suppressed because it is too large
Load Diff
@@ -164,7 +164,7 @@ async function getDBQuery(
|
||||
|
||||
query.sort = query.sort || [{ column: primaryKeyField, order: 'asc' }];
|
||||
|
||||
await applyQuery(table, dbQuery, queryCopy);
|
||||
await applyQuery(knex, table, dbQuery, queryCopy);
|
||||
|
||||
return dbQuery;
|
||||
}
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
table: directus_presets
|
||||
|
||||
defaults:
|
||||
bookmark: null
|
||||
user: null
|
||||
role: null
|
||||
collection: null
|
||||
search: null
|
||||
filters: '[]'
|
||||
layout: tabular
|
||||
layout_query: null
|
||||
layout_options: null
|
||||
|
||||
data:
|
||||
- collection: directus_files
|
||||
layout: cards
|
||||
layout_query:
|
||||
cards:
|
||||
sort: -uploaded_on
|
||||
layout_options:
|
||||
cards:
|
||||
icon: insert_drive_file
|
||||
title: '{{ title }}'
|
||||
subtitle: '{{ type }} • {{ filesize }}'
|
||||
size: 4
|
||||
imageFit: crop
|
||||
|
||||
- collection: directus_users
|
||||
layout: cards
|
||||
layout_options:
|
||||
cards:
|
||||
icon: account_circle
|
||||
title: '{{ first_name }} {{ last_name }}'
|
||||
subtitle: '{{ email }}'
|
||||
size: 4
|
||||
|
||||
- collection: directus_activity
|
||||
layout: tabular
|
||||
layout_query:
|
||||
tabular:
|
||||
sort: -timestamp
|
||||
fields:
|
||||
- action
|
||||
- collection
|
||||
- timestamp
|
||||
- user
|
||||
layout_options:
|
||||
tabular:
|
||||
widths:
|
||||
action: 100
|
||||
collection: 210
|
||||
timestamp: 240
|
||||
user: 240
|
||||
|
||||
- collection: directus_webhooks
|
||||
layout: tabular
|
||||
layout_query:
|
||||
tabular:
|
||||
fields:
|
||||
- status
|
||||
- name
|
||||
- method
|
||||
- url
|
||||
layout_options:
|
||||
tabular:
|
||||
widths:
|
||||
status: 36
|
||||
name: 300
|
||||
|
||||
- collection: directus_roles
|
||||
layout: tabular
|
||||
layout_query:
|
||||
tabular:
|
||||
fields:
|
||||
- icon
|
||||
- name
|
||||
- description
|
||||
layout_options:
|
||||
tabular:
|
||||
widths:
|
||||
icon: 36
|
||||
name: 248
|
||||
description: 500
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
# directus_fields isn't surfaced in the app
|
||||
table: directus_fields
|
||||
|
||||
fields:
|
||||
- collection: directus_fields
|
||||
field: options
|
||||
hidden: true
|
||||
locked: true
|
||||
special: json
|
||||
- collection: directus_fields
|
||||
field: display_options
|
||||
hidden: true
|
||||
locked: true
|
||||
special: json
|
||||
- collection: directus_fields
|
||||
field: locked
|
||||
hidden: true
|
||||
locked: true
|
||||
special: boolean
|
||||
- collection: directus_fields
|
||||
field: readonly
|
||||
hidden: true
|
||||
locked: true
|
||||
special: boolean
|
||||
- collection: directus_fields
|
||||
field: hidden
|
||||
hidden: true
|
||||
locked: true
|
||||
special: boolean
|
||||
- collection: directus_fields
|
||||
field: special
|
||||
hidden: true
|
||||
locked: true
|
||||
special: csv
|
||||
- collection: directus_fields
|
||||
field: translations
|
||||
hidden: true
|
||||
locked: true
|
||||
special: json
|
||||
@@ -1,8 +0,0 @@
|
||||
table: directus_folders
|
||||
|
||||
fields:
|
||||
- collection: directus_folders
|
||||
field: id
|
||||
interface: text-input
|
||||
locked: true
|
||||
special: uuid
|
||||
@@ -1,14 +0,0 @@
|
||||
# directus_permissions isn't surfaced in the app
|
||||
table: directus_permissions
|
||||
|
||||
fields:
|
||||
- collection: directus_permissions
|
||||
field: permissions
|
||||
hidden: true
|
||||
locked: true
|
||||
special: json
|
||||
- collection: directus_permissions
|
||||
field: presets
|
||||
hidden: true
|
||||
locked: true
|
||||
special: json
|
||||
@@ -1,19 +0,0 @@
|
||||
table: directus_presets
|
||||
|
||||
fields:
|
||||
# directus_presets isn't surfaced in the app
|
||||
- collection: directus_presets
|
||||
field: filters
|
||||
hidden: true
|
||||
locked: true
|
||||
special: json
|
||||
- collection: directus_presets
|
||||
field: layout_query
|
||||
hidden: true
|
||||
locked: true
|
||||
special: json
|
||||
- collection: directus_presets
|
||||
field: layout_options
|
||||
hidden: true
|
||||
locked: true
|
||||
special: json
|
||||
@@ -1,14 +0,0 @@
|
||||
table: directus_revisions
|
||||
|
||||
fields:
|
||||
# directus_revisions isn't surfaced in the app
|
||||
- collection: directus_revisions
|
||||
field: data
|
||||
hidden: true
|
||||
locked: true
|
||||
special: json
|
||||
- collection: directus_revisions
|
||||
field: delta
|
||||
hidden: true
|
||||
locked: true
|
||||
special: json
|
||||
@@ -1,7 +0,0 @@
|
||||
table: directus_relations
|
||||
|
||||
fields:
|
||||
- collection: directus_relations
|
||||
field: one_allowed_collections
|
||||
locked: true
|
||||
special: csv
|
||||
@@ -7,9 +7,6 @@ columns:
|
||||
type: string
|
||||
length: 64
|
||||
nullable: false
|
||||
references:
|
||||
table: directus_collections
|
||||
column: collection
|
||||
field:
|
||||
type: string
|
||||
length: 64
|
||||
@@ -25,9 +25,6 @@ columns:
|
||||
type: string
|
||||
length: 64
|
||||
nullable: false
|
||||
references:
|
||||
table: directus_collections
|
||||
column: collection
|
||||
item:
|
||||
type: string
|
||||
length: 255
|
||||
@@ -12,9 +12,6 @@ columns:
|
||||
type: string
|
||||
length: 64
|
||||
nullable: false
|
||||
references:
|
||||
table: directus_collections
|
||||
column: collection
|
||||
action:
|
||||
type: string
|
||||
length: 10
|
||||
@@ -19,9 +19,6 @@ columns:
|
||||
collection:
|
||||
type: string
|
||||
length: 64
|
||||
references:
|
||||
table: directus_collections
|
||||
column: collection
|
||||
search:
|
||||
type: string
|
||||
length: 100
|
||||
@@ -7,9 +7,6 @@ columns:
|
||||
type: string
|
||||
length: 64
|
||||
nullable: false
|
||||
references:
|
||||
table: directus_collections
|
||||
column: collection
|
||||
many_field:
|
||||
type: string
|
||||
length: 64
|
||||
@@ -21,9 +18,6 @@ columns:
|
||||
one_collection:
|
||||
type: string
|
||||
length: 64
|
||||
references:
|
||||
table: directus_collections
|
||||
column: collection
|
||||
one_field:
|
||||
type: string
|
||||
length: 64
|
||||
@@ -25,33 +25,6 @@ type TableSeed = {
|
||||
};
|
||||
};
|
||||
|
||||
type RowSeed = {
|
||||
table: string;
|
||||
defaults: Record<string, any>;
|
||||
data: Record<string, any>[];
|
||||
};
|
||||
|
||||
type FieldSeed = {
|
||||
table: string;
|
||||
fields: {
|
||||
collection: string;
|
||||
field: string;
|
||||
special: string | null;
|
||||
interface: string | null;
|
||||
options: Record<string, any> | null;
|
||||
display: string | null;
|
||||
display_options: Record<string, any> | null;
|
||||
locked: boolean;
|
||||
readonly: boolean;
|
||||
hidden: boolean;
|
||||
sort: number | null;
|
||||
width: string | null;
|
||||
group: number | null;
|
||||
translations: Record<string, any> | null;
|
||||
note: string | null;
|
||||
}[];
|
||||
};
|
||||
|
||||
export default async function runSeed(database: Knex) {
|
||||
const exists = await database.schema.hasTable('directus_collections');
|
||||
|
||||
@@ -59,19 +32,13 @@ export default async function runSeed(database: Knex) {
|
||||
throw new Error('Database is already installed');
|
||||
}
|
||||
|
||||
await createTables(database);
|
||||
await insertRows(database);
|
||||
await insertFields(database);
|
||||
}
|
||||
|
||||
async function createTables(database: Knex) {
|
||||
const tableSeeds = await fse.readdir(path.resolve(__dirname, './01-tables/'));
|
||||
const tableSeeds = await fse.readdir(path.resolve(__dirname));
|
||||
|
||||
for (const tableSeedFile of tableSeeds) {
|
||||
const yamlRaw = await fse.readFile(
|
||||
path.resolve(__dirname, './01-tables', tableSeedFile),
|
||||
'utf8'
|
||||
);
|
||||
if (tableSeedFile === 'run.ts') continue;
|
||||
|
||||
const yamlRaw = await fse.readFile(path.resolve(__dirname, tableSeedFile), 'utf8');
|
||||
|
||||
const seedData = yaml.safeLoad(yamlRaw) as TableSeed;
|
||||
|
||||
await database.schema.createTable(seedData.table, (tableBuilder) => {
|
||||
@@ -128,61 +95,3 @@ async function createTables(database: Knex) {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function insertRows(database: Knex) {
|
||||
const rowSeeds = await fse.readdir(path.resolve(__dirname, './02-rows/'));
|
||||
|
||||
for (const rowSeedFile of rowSeeds) {
|
||||
const yamlRaw = await fse.readFile(
|
||||
path.resolve(__dirname, './02-rows', rowSeedFile),
|
||||
'utf8'
|
||||
);
|
||||
const seedData = yaml.safeLoad(yamlRaw) as RowSeed;
|
||||
|
||||
const dataWithDefaults = seedData.data.map((row) => {
|
||||
for (const [key, value] of Object.entries(row)) {
|
||||
if (value !== null && (typeof value === 'object' || Array.isArray(value))) {
|
||||
row[key] = JSON.stringify(value);
|
||||
}
|
||||
}
|
||||
|
||||
return merge({}, seedData.defaults, row);
|
||||
});
|
||||
|
||||
await database.batchInsert(seedData.table, dataWithDefaults);
|
||||
}
|
||||
}
|
||||
|
||||
async function insertFields(database: Knex) {
|
||||
const fieldSeeds = await fse.readdir(path.resolve(__dirname, './03-fields/'));
|
||||
|
||||
const defaultsYaml = await fse.readFile(
|
||||
path.resolve(__dirname, './03-fields/_defaults.yaml'),
|
||||
'utf8'
|
||||
);
|
||||
const defaults = yaml.safeLoad(defaultsYaml) as FieldSeed;
|
||||
|
||||
for (const fieldSeedFile of fieldSeeds) {
|
||||
const yamlRaw = await fse.readFile(
|
||||
path.resolve(__dirname, './03-fields', fieldSeedFile),
|
||||
'utf8'
|
||||
);
|
||||
const seedData = yaml.safeLoad(yamlRaw) as FieldSeed;
|
||||
|
||||
if (fieldSeedFile === '_defaults.yaml') {
|
||||
continue;
|
||||
}
|
||||
|
||||
const dataWithDefaults = seedData.fields.map((row) => {
|
||||
for (const [key, value] of Object.entries(row)) {
|
||||
if (value !== null && (typeof value === 'object' || Array.isArray(value))) {
|
||||
(row as any)[key] = JSON.stringify(value);
|
||||
}
|
||||
}
|
||||
|
||||
return merge({}, defaults, row);
|
||||
});
|
||||
|
||||
await database.batchInsert('directus_fields', dataWithDefaults);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,8 @@ data:
|
||||
note: Metadata for all managed file assets
|
||||
- collection: directus_folders
|
||||
note: Provides virtual directories for files
|
||||
- collection: directus_migrations
|
||||
note: What version of the database you're using
|
||||
- collection: directus_permissions
|
||||
icon: admin_panel_settings
|
||||
note: Access permissions for each role
|
||||
11
api/src/database/system-data/collections/index.ts
Normal file
11
api/src/database/system-data/collections/index.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { requireYAML } from '../../../utils/require-yaml';
|
||||
import { merge } from 'lodash';
|
||||
import { CollectionMeta } from '../../../types';
|
||||
|
||||
const systemData = requireYAML(require.resolve('./collections.yaml'));
|
||||
|
||||
export const systemCollectionRows: CollectionMeta[] = systemData.data.map(
|
||||
(row: Record<string, any>) => {
|
||||
return merge({ system: true }, systemData.defaults, row);
|
||||
}
|
||||
);
|
||||
@@ -1,8 +1,13 @@
|
||||
table: directus_activity
|
||||
|
||||
fields:
|
||||
- collection: directus_activity
|
||||
field: action
|
||||
- field: id
|
||||
width: half
|
||||
|
||||
- field: item
|
||||
width: half
|
||||
|
||||
- field: action
|
||||
display: labels
|
||||
display_options:
|
||||
defaultForeground: '#263238'
|
||||
@@ -24,41 +29,47 @@ fields:
|
||||
value: authenticate
|
||||
foreground: '#9b51e0'
|
||||
background: '#e6d3f7'
|
||||
- collection: directus_activity
|
||||
field: collection
|
||||
width: half
|
||||
|
||||
- field: collection
|
||||
display: collection
|
||||
display_options:
|
||||
icon: true
|
||||
- collection: directus_activity
|
||||
field: timestamp
|
||||
width: half
|
||||
|
||||
- field: timestamp
|
||||
display: datetime
|
||||
options:
|
||||
relative: true
|
||||
- collection: directus_activity
|
||||
field: user
|
||||
width: half
|
||||
|
||||
- field: user
|
||||
display: user
|
||||
- collection: directus_activity
|
||||
field: comment
|
||||
width: half
|
||||
|
||||
- field: comment
|
||||
display: formatted-text
|
||||
display_options:
|
||||
subdued: true
|
||||
- collection: directus_activity
|
||||
field: user_agent
|
||||
width: half
|
||||
|
||||
- field: user_agent
|
||||
display: formatted-text
|
||||
display_options:
|
||||
font: monospace
|
||||
- collection: directus_activity
|
||||
field: ip
|
||||
width: half
|
||||
|
||||
- field: ip
|
||||
display: formatted-text
|
||||
display_options:
|
||||
font: monospace
|
||||
- collection: directus_activity
|
||||
field: revisions
|
||||
width: half
|
||||
|
||||
- field: revisions
|
||||
interface: one-to-many
|
||||
locked: true
|
||||
special: o2m
|
||||
options:
|
||||
fields:
|
||||
- collection
|
||||
- item
|
||||
width: full
|
||||
width: half
|
||||
@@ -1,69 +1,54 @@
|
||||
table: directus_collections
|
||||
|
||||
fields:
|
||||
- collection: directus_collections
|
||||
field: collection_divider
|
||||
- field: collection_divider
|
||||
special: alias
|
||||
interface: divider
|
||||
options:
|
||||
icon: box
|
||||
title: Collection Setup
|
||||
color: '#2F80ED'
|
||||
locked: true
|
||||
sort: 1
|
||||
width: full
|
||||
- collection: directus_collections
|
||||
field: collection
|
||||
|
||||
- field: collection
|
||||
interface: text-input
|
||||
options:
|
||||
font: monospace
|
||||
locked: true
|
||||
readonly: true
|
||||
sort: 2
|
||||
width: half
|
||||
- collection: directus_collections
|
||||
field: icon
|
||||
|
||||
- field: icon
|
||||
interface: icon
|
||||
options:
|
||||
locked: true
|
||||
sort: 3
|
||||
width: half
|
||||
- collection: directus_collections
|
||||
field: note
|
||||
|
||||
- field: note
|
||||
interface: text-input
|
||||
options:
|
||||
placeholder: A description of this collection...
|
||||
locked: true
|
||||
sort: 4
|
||||
width: full
|
||||
- collection: directus_collections
|
||||
field: display_template
|
||||
|
||||
- field: display_template
|
||||
interface: display-template
|
||||
options:
|
||||
collectionField: collection
|
||||
locked: true
|
||||
sort: 5
|
||||
width: full
|
||||
- collection: directus_collections
|
||||
field: hidden
|
||||
|
||||
- field: hidden
|
||||
special: boolean
|
||||
interface: toggle
|
||||
options:
|
||||
label: Hide within the App
|
||||
locked: true
|
||||
sort: 6
|
||||
width: half
|
||||
- collection: directus_collections
|
||||
field: singleton
|
||||
|
||||
- field: singleton
|
||||
special: boolean
|
||||
interface: toggle
|
||||
options:
|
||||
label: Treat as single object
|
||||
locked: true
|
||||
sort: 7
|
||||
width: half
|
||||
- collection: directus_collections
|
||||
field: translations
|
||||
|
||||
- field: translations
|
||||
special: json
|
||||
interface: repeater
|
||||
options:
|
||||
@@ -85,72 +70,58 @@ fields:
|
||||
width: half
|
||||
options:
|
||||
placeholder: Enter a translation...
|
||||
locked: true
|
||||
sort: 8
|
||||
width: full
|
||||
- collection: directus_collections
|
||||
field: archive_divider
|
||||
|
||||
- field: archive_divider
|
||||
special: alias
|
||||
interface: divider
|
||||
options:
|
||||
icon: archive
|
||||
title: Archive
|
||||
color: '#2F80ED'
|
||||
locked: true
|
||||
sort: 9
|
||||
width: full
|
||||
- collection: directus_collections
|
||||
field: archive_field
|
||||
|
||||
- field: archive_field
|
||||
interface: field
|
||||
options:
|
||||
collectionField: collection
|
||||
allowNone: true
|
||||
placeholder: Choose a field...
|
||||
locked: true
|
||||
sort: 10
|
||||
width: half
|
||||
- collection: directus_collections
|
||||
field: archive_app_filter
|
||||
|
||||
- field: archive_app_filter
|
||||
interface: toggle
|
||||
special: boolean
|
||||
options:
|
||||
label: Enable App Archive Filter
|
||||
locked: true
|
||||
sort: 11
|
||||
width: half
|
||||
- collection: directus_collections
|
||||
field: archive_value
|
||||
|
||||
- field: archive_value
|
||||
interface: text-input
|
||||
options:
|
||||
font: monospace
|
||||
iconRight: archive
|
||||
placeholder: Value set when archiving...
|
||||
locked: true
|
||||
sort: 12
|
||||
width: half
|
||||
- collection: directus_collections
|
||||
field: unarchive_value
|
||||
|
||||
- field: unarchive_value
|
||||
interface: text-input
|
||||
options:
|
||||
font: monospace
|
||||
iconRight: unarchive
|
||||
placeholder: Value set when unarchiving...
|
||||
locked: true
|
||||
sort: 13
|
||||
width: half
|
||||
- collection: directus_collections
|
||||
field: sort_divider
|
||||
|
||||
- field: sort_divider
|
||||
special: alias
|
||||
interface: divider
|
||||
options:
|
||||
icon: sort
|
||||
title: Sort
|
||||
color: '#2F80ED'
|
||||
locked: true
|
||||
sort: 14
|
||||
width: full
|
||||
- collection: directus_collections
|
||||
field: sort_field
|
||||
|
||||
- field: sort_field
|
||||
interface: field
|
||||
options:
|
||||
collectionField: collection
|
||||
@@ -160,6 +131,4 @@ fields:
|
||||
- decimal
|
||||
- integer
|
||||
allowNone: true
|
||||
locked: true
|
||||
sort: 15
|
||||
width: half
|
||||
81
api/src/database/system-data/fields/fields.yaml
Normal file
81
api/src/database/system-data/fields/fields.yaml
Normal file
@@ -0,0 +1,81 @@
|
||||
# directus_fields isn't surfaced in the app
|
||||
table: directus_fields
|
||||
|
||||
fields:
|
||||
- collection: directus_fields
|
||||
field: id
|
||||
width: half
|
||||
|
||||
- collection: directus_fields
|
||||
field: collection
|
||||
width: half
|
||||
|
||||
- collection: directus_fields
|
||||
field: field
|
||||
width: half
|
||||
|
||||
- collection: directus_fields
|
||||
field: special
|
||||
hidden: true
|
||||
special: csv
|
||||
width: half
|
||||
|
||||
- collection: directus_fields
|
||||
field: interface
|
||||
width: half
|
||||
|
||||
- collection: directus_fields
|
||||
field: options
|
||||
hidden: true
|
||||
special: json
|
||||
width: half
|
||||
|
||||
- collection: directus_fields
|
||||
field: display
|
||||
width: half
|
||||
|
||||
- collection: directus_fields
|
||||
field: display_options
|
||||
hidden: true
|
||||
special: json
|
||||
width: half
|
||||
|
||||
- collection: directus_fields
|
||||
field: locked
|
||||
hidden: true
|
||||
special: boolean
|
||||
width: half
|
||||
|
||||
- collection: directus_fields
|
||||
field: readonly
|
||||
hidden: true
|
||||
special: boolean
|
||||
width: half
|
||||
|
||||
- collection: directus_fields
|
||||
field: hidden
|
||||
hidden: true
|
||||
special: boolean
|
||||
width: half
|
||||
|
||||
- collection: directus_fields
|
||||
field: sort
|
||||
width: half
|
||||
|
||||
- collection: directus_fields
|
||||
field: width
|
||||
width: half
|
||||
|
||||
- collection: directus_fields
|
||||
field: group
|
||||
width: half
|
||||
|
||||
- collection: directus_fields
|
||||
field: translations
|
||||
hidden: true
|
||||
special: json
|
||||
width: half
|
||||
|
||||
- collection: directus_fields
|
||||
field: note
|
||||
width: half
|
||||
@@ -1,114 +1,117 @@
|
||||
table: directus_files
|
||||
|
||||
fields:
|
||||
- collection: directus_files
|
||||
field: id
|
||||
- field: id
|
||||
hidden: true
|
||||
interface: text-input
|
||||
locked: true
|
||||
special: uuid
|
||||
- collection: directus_files
|
||||
field: title
|
||||
|
||||
- field: title
|
||||
interface: text-input
|
||||
locked: true
|
||||
options:
|
||||
iconRight: title
|
||||
placeholder: A unique title...
|
||||
sort: 1
|
||||
width: full
|
||||
- collection: directus_files
|
||||
field: description
|
||||
|
||||
- field: description
|
||||
interface: textarea
|
||||
locked: true
|
||||
sort: 2
|
||||
width: full
|
||||
options:
|
||||
placeholder: An optional description...
|
||||
- collection: directus_files
|
||||
field: tags
|
||||
|
||||
- field: tags
|
||||
interface: tags
|
||||
locked: true
|
||||
options:
|
||||
iconRight: local_offer
|
||||
special: json
|
||||
sort: 3
|
||||
width: full
|
||||
display: tags
|
||||
- collection: directus_files
|
||||
field: location
|
||||
|
||||
- field: location
|
||||
interface: text-input
|
||||
locked: true
|
||||
options:
|
||||
iconRight: place
|
||||
placeholder: An optional location...
|
||||
sort: 4
|
||||
width: half
|
||||
- collection: directus_files
|
||||
field: storage
|
||||
|
||||
- field: storage
|
||||
interface: text-input
|
||||
locked: true
|
||||
options:
|
||||
iconRight: storage
|
||||
sort: 5
|
||||
width: half
|
||||
readonly: true
|
||||
- collection: directus_files
|
||||
field: storage_divider
|
||||
|
||||
- field: storage_divider
|
||||
interface: divider
|
||||
locked: true
|
||||
options:
|
||||
icon: insert_drive_file
|
||||
title: File Naming
|
||||
color: '#2F80ED'
|
||||
special: alias
|
||||
sort: 6
|
||||
width: full
|
||||
- collection: directus_files
|
||||
field: filename_disk
|
||||
|
||||
- field: filename_disk
|
||||
interface: text-input
|
||||
locked: true
|
||||
options:
|
||||
iconRight: publish
|
||||
placeholder: Name on disk storage...
|
||||
sort: 7
|
||||
width: half
|
||||
- collection: directus_files
|
||||
field: filename_download
|
||||
|
||||
- field: filename_download
|
||||
interface: text-input
|
||||
locked: true
|
||||
options:
|
||||
iconRight: get_app
|
||||
placeholder: Name when downloading...
|
||||
sort: 8
|
||||
width: half
|
||||
- collection: directus_files
|
||||
field: metadata
|
||||
|
||||
- field: metadata
|
||||
hidden: true
|
||||
locked: true
|
||||
special: json
|
||||
- collection: directus_files
|
||||
field: type
|
||||
|
||||
- field: type
|
||||
display: mime-type
|
||||
- collection: directus_files
|
||||
field: filesize
|
||||
|
||||
- field: filesize
|
||||
display: filesize
|
||||
- collection: directus_files
|
||||
field: modified_by
|
||||
|
||||
- field: modified_by
|
||||
interface: user
|
||||
locked: true
|
||||
special: user-updated
|
||||
width: half
|
||||
display: user
|
||||
- collection: directus_files
|
||||
field: modified_on
|
||||
|
||||
- field: modified_on
|
||||
interface: datetime
|
||||
locked: true
|
||||
special: date-updated
|
||||
width: half
|
||||
display: datetime
|
||||
- collection: directus_files
|
||||
field: created_on
|
||||
|
||||
- field: created_on
|
||||
display: datetime
|
||||
- collection: directus_files
|
||||
field: created_by
|
||||
|
||||
- field: created_by
|
||||
display: user
|
||||
|
||||
- field: embed
|
||||
width: half
|
||||
|
||||
- field: uploaded_by
|
||||
width: half
|
||||
|
||||
- field: folder
|
||||
width: half
|
||||
|
||||
- field: width
|
||||
width: half
|
||||
|
||||
- field: uploaded_on
|
||||
width: half
|
||||
|
||||
- field: height
|
||||
width: half
|
||||
|
||||
- field: charset
|
||||
width: half
|
||||
|
||||
- field: duration
|
||||
width: half
|
||||
14
api/src/database/system-data/fields/folders.yaml
Normal file
14
api/src/database/system-data/fields/folders.yaml
Normal file
@@ -0,0 +1,14 @@
|
||||
table: directus_folders
|
||||
|
||||
fields:
|
||||
- field: id
|
||||
interface: text-input
|
||||
special: uuid
|
||||
width: half
|
||||
|
||||
- field: parent
|
||||
width: half
|
||||
|
||||
- field: name
|
||||
width: full
|
||||
|
||||
25
api/src/database/system-data/fields/index.ts
Normal file
25
api/src/database/system-data/fields/index.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { requireYAML } from '../../../utils/require-yaml';
|
||||
import { merge } from 'lodash';
|
||||
import { FieldMeta } from '../../../types';
|
||||
import fse from 'fs-extra';
|
||||
import path from 'path';
|
||||
|
||||
const defaults = requireYAML(require.resolve('./_defaults.yaml'));
|
||||
const fieldData = fse.readdirSync(path.resolve(__dirname));
|
||||
|
||||
export let systemFieldRows: FieldMeta[] = [];
|
||||
|
||||
for (const filepath of fieldData) {
|
||||
if (['_defaults.yaml', 'index.ts'].includes(filepath)) continue;
|
||||
|
||||
const systemFields = requireYAML(path.resolve(__dirname, filepath));
|
||||
|
||||
(systemFields.fields as FieldMeta[]).forEach((field, index) => {
|
||||
systemFieldRows.push(
|
||||
merge({ system: true }, defaults, field, {
|
||||
collection: systemFields.table,
|
||||
sort: index + 1,
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
34
api/src/database/system-data/fields/permissions.yaml
Normal file
34
api/src/database/system-data/fields/permissions.yaml
Normal file
@@ -0,0 +1,34 @@
|
||||
# directus_permissions isn't surfaced in the app
|
||||
table: directus_permissions
|
||||
|
||||
fields:
|
||||
- field: permissions
|
||||
hidden: true
|
||||
special: json
|
||||
width: half
|
||||
|
||||
- field: presets
|
||||
hidden: true
|
||||
special: json
|
||||
width: half
|
||||
|
||||
- field: role
|
||||
width: half
|
||||
|
||||
- field: limit
|
||||
width: half
|
||||
|
||||
- field: collection
|
||||
width: half
|
||||
|
||||
- field: id
|
||||
width: half
|
||||
|
||||
- field: fields
|
||||
width: half
|
||||
|
||||
- field: action
|
||||
width: half
|
||||
|
||||
- field: validation
|
||||
width: half
|
||||
35
api/src/database/system-data/fields/presets.yaml
Normal file
35
api/src/database/system-data/fields/presets.yaml
Normal file
@@ -0,0 +1,35 @@
|
||||
table: directus_presets
|
||||
|
||||
fields:
|
||||
- field: filters
|
||||
hidden: true
|
||||
special: json
|
||||
|
||||
- field: layout_query
|
||||
hidden: true
|
||||
special: json
|
||||
|
||||
- field: layout_options
|
||||
hidden: true
|
||||
special: json
|
||||
|
||||
- field: role
|
||||
width: half
|
||||
|
||||
- field: user
|
||||
width: half
|
||||
|
||||
- field: id
|
||||
width: half
|
||||
|
||||
- field: bookmark
|
||||
width: half
|
||||
|
||||
- field: search
|
||||
width: half
|
||||
|
||||
- field: collection
|
||||
width: half
|
||||
|
||||
- field: layout
|
||||
width: half
|
||||
33
api/src/database/system-data/fields/relations.yaml
Normal file
33
api/src/database/system-data/fields/relations.yaml
Normal file
@@ -0,0 +1,33 @@
|
||||
table: directus_relations
|
||||
|
||||
fields:
|
||||
- field: id
|
||||
width: half
|
||||
|
||||
- field: many_collection
|
||||
width: half
|
||||
|
||||
- field: many_field
|
||||
width: half
|
||||
|
||||
- field: many_primary
|
||||
width: half
|
||||
|
||||
- field: one_collection
|
||||
width: half
|
||||
|
||||
- field: one_field
|
||||
width: half
|
||||
|
||||
- field: one_primary
|
||||
width: half
|
||||
|
||||
- field: one_collection_field
|
||||
width: half
|
||||
|
||||
- field: one_allowed_collections
|
||||
special: csv
|
||||
width: half
|
||||
|
||||
- field: junction_field
|
||||
width: half
|
||||
25
api/src/database/system-data/fields/revisions.yaml
Normal file
25
api/src/database/system-data/fields/revisions.yaml
Normal file
@@ -0,0 +1,25 @@
|
||||
table: directus_revisions
|
||||
|
||||
fields:
|
||||
- field: id
|
||||
width: half
|
||||
|
||||
- field: activity
|
||||
width: half
|
||||
|
||||
- field: collection
|
||||
width: half
|
||||
|
||||
- field: item
|
||||
width: half
|
||||
|
||||
- field: data
|
||||
hidden: true
|
||||
special: json
|
||||
|
||||
- field: delta
|
||||
hidden: true
|
||||
special: json
|
||||
|
||||
- field: parent
|
||||
width: half
|
||||
@@ -1,80 +1,61 @@
|
||||
table: directus_roles
|
||||
|
||||
fields:
|
||||
- collection: directus_roles
|
||||
field: id
|
||||
- field: id
|
||||
hidden: true
|
||||
interface: text-input
|
||||
locked: true
|
||||
special: uuid
|
||||
- collection: directus_roles
|
||||
field: name
|
||||
|
||||
- field: name
|
||||
interface: text-input
|
||||
options:
|
||||
placeholder: The unique name for this role...
|
||||
locked: true
|
||||
sort: 1
|
||||
width: half
|
||||
- collection: directus_roles
|
||||
field: icon
|
||||
|
||||
- field: icon
|
||||
interface: icon
|
||||
display: icon
|
||||
locked: true
|
||||
sort: 2
|
||||
width: half
|
||||
- collection: directus_roles
|
||||
field: description
|
||||
|
||||
- field: description
|
||||
interface: text-input
|
||||
options:
|
||||
placeholder: A description of this role...
|
||||
locked: true
|
||||
sort: 3
|
||||
width: full
|
||||
- collection: directus_roles
|
||||
field: app_access
|
||||
|
||||
- field: app_access
|
||||
interface: toggle
|
||||
locked: true
|
||||
special: boolean
|
||||
sort: 4
|
||||
width: half
|
||||
- collection: directus_roles
|
||||
field: admin_access
|
||||
|
||||
- field: admin_access
|
||||
interface: toggle
|
||||
locked: true
|
||||
special: boolean
|
||||
sort: 5
|
||||
width: half
|
||||
- collection: directus_roles
|
||||
field: ip_access
|
||||
|
||||
- field: ip_access
|
||||
interface: tags
|
||||
options:
|
||||
placeholder: Add allowed IP addresses, leave empty to allow all...
|
||||
locked: true
|
||||
special: csv
|
||||
sort: 6
|
||||
width: full
|
||||
- collection: directus_roles
|
||||
field: enforce_tfa
|
||||
|
||||
- field: enforce_tfa
|
||||
interface: toggle
|
||||
locked: true
|
||||
sort: 7
|
||||
special: boolean
|
||||
width: half
|
||||
- collection: directus_roles
|
||||
field: users
|
||||
|
||||
- field: users
|
||||
interface: one-to-many
|
||||
locked: true
|
||||
special: o2m
|
||||
sort: 8
|
||||
options:
|
||||
fields:
|
||||
- first_name
|
||||
- last_name
|
||||
width: full
|
||||
- collection: directus_roles
|
||||
field: module_list
|
||||
|
||||
- field: module_list
|
||||
interface: repeater
|
||||
locked: true
|
||||
options:
|
||||
template: '{{ name }}'
|
||||
addLabel: Add New Module...
|
||||
@@ -104,12 +85,10 @@ fields:
|
||||
iconRight: link
|
||||
placeholder: Relative or absolute URL...
|
||||
special: json
|
||||
sort: 9
|
||||
width: full
|
||||
- collection: directus_roles
|
||||
field: collection_list
|
||||
|
||||
- field: collection_list
|
||||
interface: repeater
|
||||
locked: true
|
||||
options:
|
||||
template: '{{ group_name }}'
|
||||
addLabel: Add New Group...
|
||||
@@ -159,5 +138,4 @@ fields:
|
||||
schema:
|
||||
is_nullable: false
|
||||
special: json
|
||||
sort: 10
|
||||
width: full
|
||||
17
api/src/database/system-data/fields/sessions.yaml
Normal file
17
api/src/database/system-data/fields/sessions.yaml
Normal file
@@ -0,0 +1,17 @@
|
||||
table: directus_sessions
|
||||
|
||||
fields:
|
||||
- field: token
|
||||
width: half
|
||||
|
||||
- field: user
|
||||
width: half
|
||||
|
||||
- field: expires
|
||||
width: half
|
||||
|
||||
- field: ip
|
||||
width: half
|
||||
|
||||
- field: user_agent
|
||||
width: half
|
||||
@@ -1,102 +1,85 @@
|
||||
table: directus_settings
|
||||
|
||||
fields:
|
||||
- collection: directus_settings
|
||||
field: project_name
|
||||
- field: id
|
||||
hidden: true
|
||||
|
||||
- field: project_name
|
||||
interface: text-input
|
||||
locked: true
|
||||
options:
|
||||
iconRight: title
|
||||
placeholder: My project...
|
||||
sort: 1
|
||||
translations:
|
||||
language: en-US
|
||||
translations: Name
|
||||
width: half
|
||||
- collection: directus_settings
|
||||
field: project_url
|
||||
|
||||
- field: project_url
|
||||
interface: text-input
|
||||
locked: true
|
||||
options:
|
||||
iconRight: link
|
||||
placeholder: https://example.com
|
||||
sort: 2
|
||||
translations:
|
||||
language: en-US
|
||||
translations: Website
|
||||
width: half
|
||||
- collection: directus_settings
|
||||
field: project_color
|
||||
|
||||
- field: project_color
|
||||
interface: color
|
||||
locked: true
|
||||
note: Login & Logo Background
|
||||
sort: 3
|
||||
translations:
|
||||
language: en-US
|
||||
translations: Brand Color
|
||||
width: half
|
||||
- collection: directus_settings
|
||||
field: project_logo
|
||||
|
||||
- field: project_logo
|
||||
interface: file
|
||||
locked: true
|
||||
note: White 40x40 SVG/PNG
|
||||
sort: 4
|
||||
translations:
|
||||
language: en-US
|
||||
translations: Brand Logo
|
||||
width: half
|
||||
- collection: directus_settings
|
||||
field: public_divider
|
||||
|
||||
- field: public_divider
|
||||
interface: divider
|
||||
locked: true
|
||||
options:
|
||||
icon: public
|
||||
title: Public Pages
|
||||
color: '#2F80ED'
|
||||
special: alias
|
||||
sort: 5
|
||||
width: full
|
||||
- collection: directus_settings
|
||||
field: public_foreground
|
||||
|
||||
- field: public_foreground
|
||||
interface: file
|
||||
locked: true
|
||||
sort: 6
|
||||
translations:
|
||||
language: en-US
|
||||
translations: Login Foreground
|
||||
width: half
|
||||
- collection: directus_settings
|
||||
field: public_background
|
||||
|
||||
- field: public_background
|
||||
interface: file
|
||||
locked: true
|
||||
sort: 7
|
||||
translations:
|
||||
language: en-US
|
||||
translations: Login Background
|
||||
width: half
|
||||
- collection: directus_settings
|
||||
field: public_note
|
||||
|
||||
- field: public_note
|
||||
interface: textarea
|
||||
locked: true
|
||||
options:
|
||||
placeholder: A short, public message that supports markdown formatting...
|
||||
sort: 8
|
||||
width: full
|
||||
- collection: directus_settings
|
||||
field: security_divider
|
||||
|
||||
- field: security_divider
|
||||
interface: divider
|
||||
locked: true
|
||||
options:
|
||||
icon: security
|
||||
title: Security
|
||||
color: '#2F80ED'
|
||||
special: alias
|
||||
sort: 9
|
||||
width: full
|
||||
- collection: directus_settings
|
||||
field: auth_password_policy
|
||||
|
||||
- field: auth_password_policy
|
||||
interface: dropdown
|
||||
locked: true
|
||||
options:
|
||||
choices:
|
||||
- value: null
|
||||
@@ -105,31 +88,25 @@ fields:
|
||||
text: Weak – Minimum 8 Characters
|
||||
- value: "/(?=^.{8,}$)(?=.*\\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[!@#$%^&*()_+}{';'?>.<,])(?!.*\\s).*$/"
|
||||
text: Strong – Upper / Lowercase / Numbers / Special
|
||||
sort: 10
|
||||
width: half
|
||||
- collection: directus_settings
|
||||
field: auth_login_attempts
|
||||
|
||||
- field: auth_login_attempts
|
||||
interface: numeric
|
||||
locked: true
|
||||
options:
|
||||
iconRight: lock
|
||||
sort: 11
|
||||
width: half
|
||||
- collection: directus_settings
|
||||
field: files_divider
|
||||
|
||||
- field: files_divider
|
||||
interface: divider
|
||||
locked: true
|
||||
options:
|
||||
icon: storage
|
||||
title: Files & Thumbnails
|
||||
color: '#2F80ED'
|
||||
special: alias
|
||||
sort: 12
|
||||
width: full
|
||||
- collection: directus_settings
|
||||
field: storage_asset_presets
|
||||
|
||||
- field: storage_asset_presets
|
||||
interface: repeater
|
||||
locked: true
|
||||
options:
|
||||
fields:
|
||||
- field: key
|
||||
@@ -202,12 +179,10 @@ fields:
|
||||
label: Don't upscale images
|
||||
template: '{{key}}'
|
||||
special: json
|
||||
sort: 13
|
||||
width: full
|
||||
- collection: directus_settings
|
||||
field: storage_asset_transform
|
||||
|
||||
- field: storage_asset_transform
|
||||
interface: dropdown
|
||||
locked: true
|
||||
options:
|
||||
choices:
|
||||
- value: all
|
||||
@@ -216,29 +191,20 @@ fields:
|
||||
text: None
|
||||
- value: presets
|
||||
text: Presets Only
|
||||
sort: 14
|
||||
width: half
|
||||
- collection: directus_settings
|
||||
field: id
|
||||
hidden: true
|
||||
locked: true
|
||||
- collection: directus_settings
|
||||
field: overrides_divider
|
||||
|
||||
- field: overrides_divider
|
||||
interface: divider
|
||||
locked: true
|
||||
options:
|
||||
icon: brush
|
||||
title: App Overrides
|
||||
color: '#2F80ED'
|
||||
special: alias
|
||||
sort: 15
|
||||
width: full
|
||||
- collection: directus_settings
|
||||
field: custom_css
|
||||
|
||||
- field: custom_css
|
||||
interface: code
|
||||
locked: true
|
||||
options:
|
||||
language: css
|
||||
lineNumber: true
|
||||
sort: 16
|
||||
width: full
|
||||
@@ -1,87 +1,70 @@
|
||||
table: directus_users
|
||||
|
||||
fields:
|
||||
- collection: directus_users
|
||||
field: first_name
|
||||
- field: first_name
|
||||
interface: text-input
|
||||
locked: true
|
||||
options:
|
||||
iconRight: account_circle
|
||||
sort: 1
|
||||
width: half
|
||||
- collection: directus_users
|
||||
field: last_name
|
||||
|
||||
- field: last_name
|
||||
interface: text-input
|
||||
locked: true
|
||||
options:
|
||||
iconRight: account_circle
|
||||
sort: 2
|
||||
width: half
|
||||
- collection: directus_users
|
||||
field: email
|
||||
|
||||
- field: email
|
||||
interface: text-input
|
||||
locked: true
|
||||
options:
|
||||
iconRight: email
|
||||
sort: 3
|
||||
width: half
|
||||
- collection: directus_users
|
||||
field: password
|
||||
|
||||
- field: password
|
||||
special: hash, conceal
|
||||
interface: hash
|
||||
locked: true
|
||||
options:
|
||||
iconRight: lock
|
||||
masked: true
|
||||
sort: 4
|
||||
width: half
|
||||
- collection: directus_users
|
||||
field: avatar
|
||||
|
||||
- field: avatar
|
||||
interface: file
|
||||
locked: true
|
||||
sort: 5
|
||||
width: full
|
||||
- collection: directus_users
|
||||
field: location
|
||||
|
||||
- field: location
|
||||
interface: text-input
|
||||
options:
|
||||
iconRight: place
|
||||
sort: 6
|
||||
width: half
|
||||
- collection: directus_users
|
||||
field: title
|
||||
|
||||
- field: title
|
||||
interface: text-input
|
||||
options:
|
||||
iconRight: work
|
||||
sort: 7
|
||||
width: half
|
||||
- collection: directus_users
|
||||
field: description
|
||||
|
||||
- field: description
|
||||
interface: textarea
|
||||
sort: 8
|
||||
width: full
|
||||
- collection: directus_users
|
||||
field: tags
|
||||
|
||||
- field: tags
|
||||
interface: tags
|
||||
special: json
|
||||
sort: 9
|
||||
width: full
|
||||
options:
|
||||
iconRight: local_offer
|
||||
- collection: directus_users
|
||||
field: preferences_divider
|
||||
|
||||
- field: preferences_divider
|
||||
interface: divider
|
||||
options:
|
||||
icon: face
|
||||
title: User Preferences
|
||||
color: '#2F80ED'
|
||||
special: alias
|
||||
sort: 10
|
||||
width: full
|
||||
- collection: directus_users
|
||||
field: language
|
||||
|
||||
- field: language
|
||||
interface: dropdown
|
||||
locked: true
|
||||
options:
|
||||
choices:
|
||||
- text: Afrikaans (South Africa)
|
||||
@@ -146,12 +129,10 @@ fields:
|
||||
value: uk-UA
|
||||
- text: Vietnamese (Vietnam)
|
||||
value: vi-VN
|
||||
sort: 11
|
||||
width: half
|
||||
- collection: directus_users
|
||||
field: theme
|
||||
|
||||
- field: theme
|
||||
interface: dropdown
|
||||
locked: true
|
||||
options:
|
||||
choices:
|
||||
- value: auto
|
||||
@@ -160,30 +141,24 @@ fields:
|
||||
text: Light Mode
|
||||
- value: dark
|
||||
text: Dark Mode
|
||||
sort: 12
|
||||
width: half
|
||||
- collection: directus_users
|
||||
field: tfa_secret
|
||||
|
||||
- field: tfa_secret
|
||||
interface: tfa-setup
|
||||
locked: true
|
||||
special: conceal
|
||||
sort: 13
|
||||
width: half
|
||||
- collection: directus_users
|
||||
field: admin_divider
|
||||
|
||||
- field: admin_divider
|
||||
interface: divider
|
||||
locked: true
|
||||
options:
|
||||
icon: verified_user
|
||||
title: Admin Options
|
||||
color: '#F2994A'
|
||||
special: alias
|
||||
sort: 14
|
||||
width: full
|
||||
- collection: directus_users
|
||||
field: status
|
||||
|
||||
- field: status
|
||||
interface: dropdown
|
||||
locked: true
|
||||
options:
|
||||
choices:
|
||||
- text: Draft
|
||||
@@ -196,32 +171,31 @@ fields:
|
||||
value: suspended
|
||||
- text: Archived
|
||||
value: archived
|
||||
sort: 15
|
||||
width: half
|
||||
- collection: directus_users
|
||||
field: role
|
||||
|
||||
- field: role
|
||||
interface: many-to-one
|
||||
locked: true
|
||||
options:
|
||||
template: '{{ name }}'
|
||||
special: m2o
|
||||
sort: 16
|
||||
width: half
|
||||
- collection: directus_users
|
||||
field: token
|
||||
|
||||
- field: token
|
||||
interface: token
|
||||
locked: true
|
||||
options:
|
||||
iconRight: vpn_key
|
||||
placeholder: Enter a secure access token...
|
||||
sort: 17
|
||||
width: full
|
||||
- collection: directus_users
|
||||
field: id
|
||||
|
||||
- field: id
|
||||
special: uuid
|
||||
interface: text-input
|
||||
locked: true
|
||||
options:
|
||||
iconRight: vpn_key
|
||||
sort: 18
|
||||
width: full
|
||||
|
||||
- field: last_page
|
||||
width: half
|
||||
|
||||
- field: last_access
|
||||
width: half
|
||||
@@ -1,43 +1,35 @@
|
||||
table: directus_webhooks
|
||||
|
||||
fields:
|
||||
- collection: directus_webhooks
|
||||
field: id
|
||||
- field: id
|
||||
hidden: true
|
||||
locked: true
|
||||
- collection: directus_webhooks
|
||||
field: name
|
||||
|
||||
- field: name
|
||||
interface: text-input
|
||||
locked: true
|
||||
options:
|
||||
iconRight: title
|
||||
sort: 1
|
||||
width: full
|
||||
- collection: directus_webhooks
|
||||
field: method
|
||||
|
||||
- field: method
|
||||
interface: dropdown
|
||||
display: labels
|
||||
display_options:
|
||||
defaultBackground: "#ECEFF1"
|
||||
choices: null
|
||||
format: false
|
||||
locked: true
|
||||
options:
|
||||
choices:
|
||||
- GET
|
||||
- POST
|
||||
sort: 2
|
||||
width: half
|
||||
- collection: directus_webhooks
|
||||
field: url
|
||||
|
||||
- field: url
|
||||
interface: text-input
|
||||
locked: true
|
||||
options:
|
||||
iconRight: link
|
||||
sort: 3
|
||||
width: half
|
||||
- collection: directus_webhooks
|
||||
field: status
|
||||
|
||||
- field: status
|
||||
interface: dropdown
|
||||
display: labels
|
||||
display_options:
|
||||
@@ -53,36 +45,31 @@ fields:
|
||||
value: inactive
|
||||
foreground: "#607D8B"
|
||||
background: "#ECEFF1"
|
||||
locked: true
|
||||
options:
|
||||
choices:
|
||||
- text: Active
|
||||
value: active
|
||||
- text: Inactive
|
||||
value: inactive
|
||||
sort: 4
|
||||
width: half
|
||||
- collection: directus_webhooks
|
||||
field: data
|
||||
|
||||
- field: data
|
||||
interface: toggle
|
||||
locked: true
|
||||
options:
|
||||
label: Send Event Data
|
||||
special: boolean
|
||||
sort: 5
|
||||
width: half
|
||||
- collection: directus_webhooks
|
||||
field: triggers_divider
|
||||
|
||||
- field: triggers_divider
|
||||
interface: divider
|
||||
options:
|
||||
icon: api
|
||||
title: Triggers
|
||||
color: '#2F80ED'
|
||||
special: alias
|
||||
sort: 6
|
||||
width: full
|
||||
- collection: directus_webhooks
|
||||
field: actions
|
||||
|
||||
- field: actions
|
||||
interface: checkboxes
|
||||
options:
|
||||
choices:
|
||||
@@ -93,11 +80,9 @@ fields:
|
||||
- text: Delete
|
||||
value: delete
|
||||
special: csv
|
||||
sort: 7
|
||||
width: full
|
||||
- collection: directus_webhooks
|
||||
field: collections
|
||||
|
||||
- field: collections
|
||||
interface: collections
|
||||
special: csv
|
||||
sort: 8
|
||||
width: full
|
||||
9
api/src/database/system-data/relations/index.ts
Normal file
9
api/src/database/system-data/relations/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { requireYAML } from '../../../utils/require-yaml';
|
||||
import { merge } from 'lodash';
|
||||
import { Relation } from '../../../types';
|
||||
|
||||
const systemData = requireYAML(require.resolve('./relations.yaml'));
|
||||
|
||||
export const systemRelationRows: Relation[] = systemData.data.map((row: Record<string, any>) => {
|
||||
return merge({ system: true }, systemData.defaults, row);
|
||||
});
|
||||
@@ -6,6 +6,8 @@ import { RequestHandler } from 'express';
|
||||
import asyncHandler from 'express-async-handler';
|
||||
import database from '../database';
|
||||
import { ForbiddenException } from '../exceptions';
|
||||
import { systemCollectionRows } from '../database/system-data/collections';
|
||||
import { Collection } from '../types';
|
||||
|
||||
const collectionExists: RequestHandler = asyncHandler(async (req, res, next) => {
|
||||
if (!req.params.collection) return next();
|
||||
@@ -18,13 +20,21 @@ const collectionExists: RequestHandler = asyncHandler(async (req, res, next) =>
|
||||
|
||||
req.collection = req.params.collection;
|
||||
|
||||
const collectionInfo = await database
|
||||
.select('singleton')
|
||||
.from('directus_collections')
|
||||
.where({ collection: req.collection })
|
||||
.first();
|
||||
if (req.collection.startsWith('directus_')) {
|
||||
const systemRow = systemCollectionRows.find((collection) => {
|
||||
return collection?.collection === req.collection;
|
||||
});
|
||||
|
||||
req.singleton = collectionInfo?.singleton === true || collectionInfo?.singleton === 1;
|
||||
req.singleton = !!systemRow?.singleton;
|
||||
} else {
|
||||
const collectionInfo = await database
|
||||
.select('singleton')
|
||||
.from('directus_collections')
|
||||
.where({ collection: req.collection })
|
||||
.first();
|
||||
|
||||
req.singleton = collectionInfo?.singleton === true || collectionInfo?.singleton === 1;
|
||||
}
|
||||
|
||||
return next();
|
||||
});
|
||||
|
||||
@@ -20,6 +20,7 @@ import { ItemsService } from './items';
|
||||
import { PayloadService } from './payload';
|
||||
import { parseFilter } from '../utils/parse-filter';
|
||||
import { toArray } from '../utils/to-array';
|
||||
import { systemFieldRows } from '../database/system-data/fields';
|
||||
|
||||
export class AuthorizationService {
|
||||
knex: Knex;
|
||||
@@ -268,13 +269,18 @@ export class AuthorizationService {
|
||||
let requiredColumns: string[] = [];
|
||||
|
||||
for (const column of columns) {
|
||||
const field = await this.knex
|
||||
.select<{ special: string }>('special')
|
||||
.from('directus_fields')
|
||||
.where({ collection, field: column.name })
|
||||
.first();
|
||||
const field =
|
||||
(await this.knex
|
||||
.select<{ special: string }>('special')
|
||||
.from('directus_fields')
|
||||
.where({ collection, field: column.name })
|
||||
.first()) ||
|
||||
systemFieldRows.find(
|
||||
(fieldMeta) =>
|
||||
fieldMeta.field === column.name && fieldMeta.collection === collection
|
||||
);
|
||||
|
||||
const specials = (field?.special || '').split(',');
|
||||
const specials = field?.special ? toArray(field.special) : [];
|
||||
|
||||
const hasGenerateSpecial = [
|
||||
'uuid',
|
||||
@@ -326,9 +332,11 @@ export class AuthorizationService {
|
||||
}
|
||||
|
||||
validateJoi(
|
||||
validation: Record<string, any>,
|
||||
validation: null | Record<string, any>,
|
||||
payloads: Partial<Record<string, any>>[]
|
||||
): FailedValidationException[] {
|
||||
if (!validation) return [];
|
||||
|
||||
const errors: FailedValidationException[] = [];
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import database, { schemaInspector } from '../database';
|
||||
import { AbstractServiceOptions, Accountability, Collection, Relation } from '../types';
|
||||
import {
|
||||
AbstractServiceOptions,
|
||||
Accountability,
|
||||
Collection,
|
||||
CollectionMeta,
|
||||
Relation,
|
||||
} from '../types';
|
||||
import Knex from 'knex';
|
||||
import { ForbiddenException, InvalidPayloadException } from '../exceptions';
|
||||
import SchemaInspector from 'knex-schema-inspector';
|
||||
@@ -7,6 +13,7 @@ import { FieldsService } from '../services/fields';
|
||||
import { ItemsService } from '../services/items';
|
||||
import cache from '../cache';
|
||||
import { toArray } from '../utils/to-array';
|
||||
import { systemCollectionRows } from '../database/system-data/collections';
|
||||
|
||||
export class CollectionsService {
|
||||
knex: Knex;
|
||||
@@ -106,6 +113,7 @@ export class CollectionsService {
|
||||
knex: this.knex,
|
||||
accountability: this.accountability,
|
||||
});
|
||||
|
||||
const collectionKeys = toArray(collection);
|
||||
|
||||
if (this.accountability && this.accountability.admin !== true) {
|
||||
@@ -135,7 +143,9 @@ export class CollectionsService {
|
||||
const tables = tablesInDatabase.filter((table) => collectionKeys.includes(table.name));
|
||||
const meta = (await collectionItemsService.readByQuery({
|
||||
filter: { collection: { _in: collectionKeys } },
|
||||
})) as Collection['meta'][];
|
||||
})) as CollectionMeta[];
|
||||
|
||||
meta.push(...systemCollectionRows);
|
||||
|
||||
const collections: Collection[] = [];
|
||||
|
||||
@@ -173,7 +183,9 @@ export class CollectionsService {
|
||||
const tablesToFetchInfoFor = tablesInDatabase.map((table) => table.name);
|
||||
const meta = (await collectionItemsService.readByQuery({
|
||||
filter: { collection: { _in: tablesToFetchInfoFor } },
|
||||
})) as Collection['meta'][];
|
||||
})) as CollectionMeta[];
|
||||
|
||||
meta.push(...systemCollectionRows);
|
||||
|
||||
const collections: Collection[] = [];
|
||||
|
||||
|
||||
@@ -11,6 +11,9 @@ import { PayloadService } from '../services/payload';
|
||||
import getDefaultValue from '../utils/get-default-value';
|
||||
import cache from '../cache';
|
||||
import SchemaInspector from 'knex-schema-inspector';
|
||||
import { toArray } from '../utils/to-array';
|
||||
|
||||
import { systemFieldRows } from '../database/system-data/fields/';
|
||||
|
||||
type RawField = Partial<Field> & { field: string; type: typeof types[number] };
|
||||
|
||||
@@ -38,8 +41,13 @@ export class FieldsService {
|
||||
filter: { collection: { _eq: collection } },
|
||||
limit: -1,
|
||||
})) as FieldMeta[];
|
||||
|
||||
fields.push(
|
||||
...systemFieldRows.filter((fieldMeta) => fieldMeta.collection === collection)
|
||||
);
|
||||
} else {
|
||||
fields = (await nonAuthorizedItemsService.readByQuery({ limit: -1 })) as FieldMeta[];
|
||||
fields.push(...systemFieldRows);
|
||||
}
|
||||
|
||||
let columns = await schemaInspector.columnInfo(collection);
|
||||
@@ -73,12 +81,15 @@ export class FieldsService {
|
||||
aliasQuery.andWhere('collection', collection);
|
||||
}
|
||||
|
||||
let aliasFields = await aliasQuery;
|
||||
let aliasFields = [
|
||||
...((await this.payloadService.processValues('read', await aliasQuery)) as FieldMeta[]),
|
||||
...systemFieldRows,
|
||||
];
|
||||
|
||||
const aliasTypes = ['alias', 'o2m', 'm2m', 'files', 'files', 'translations'];
|
||||
|
||||
aliasFields = aliasFields.filter((field) => {
|
||||
const specials = (field.special || '').split(',');
|
||||
const specials = toArray(field.special);
|
||||
|
||||
for (const type of aliasTypes) {
|
||||
if (specials.includes(type)) return true;
|
||||
@@ -87,19 +98,17 @@ export class FieldsService {
|
||||
return false;
|
||||
});
|
||||
|
||||
aliasFields = (await this.payloadService.processValues('read', aliasFields)) as FieldMeta[];
|
||||
|
||||
const aliasFieldsAsField = aliasFields.map((field) => {
|
||||
const data = {
|
||||
collection: field.collection,
|
||||
field: field.field,
|
||||
type: field.special[0],
|
||||
type: field.special?.[0],
|
||||
schema: null,
|
||||
meta: field,
|
||||
};
|
||||
|
||||
return data;
|
||||
});
|
||||
}) as Field[];
|
||||
|
||||
const result = [...columnsWithSystem, ...aliasFieldsAsField];
|
||||
|
||||
@@ -163,6 +172,12 @@ export class FieldsService {
|
||||
fieldInfo = (await this.payloadService.processValues('read', fieldInfo)) as FieldMeta[];
|
||||
}
|
||||
|
||||
fieldInfo =
|
||||
fieldInfo ||
|
||||
systemFieldRows.find(
|
||||
(fieldMeta) => fieldMeta.collection === collection && fieldMeta.field === field
|
||||
);
|
||||
|
||||
try {
|
||||
column = await schemaInspector.columnInfo(collection, field);
|
||||
column.default_value = getDefaultValue(column);
|
||||
|
||||
@@ -41,7 +41,7 @@ export class FilesService extends ItemsService {
|
||||
const fileExtension =
|
||||
(payload.type && extension(payload.type)) || path.extname(payload.filename_download);
|
||||
|
||||
payload.filename_disk = primaryKey + fileExtension;
|
||||
payload.filename_disk = primaryKey + '.' + fileExtension;
|
||||
|
||||
if (!payload.type) {
|
||||
payload.type = 'application/octet-stream';
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
Field,
|
||||
Relation,
|
||||
Query,
|
||||
AbstractService,
|
||||
} from '../types';
|
||||
import {
|
||||
GraphQLString,
|
||||
@@ -49,6 +48,7 @@ import { UsersService } from './users';
|
||||
import { WebhooksService } from './webhooks';
|
||||
|
||||
import { getRelationType } from '../utils/get-relation-type';
|
||||
import { systemCollectionRows } from '../database/system-data/collections';
|
||||
|
||||
export class GraphQLService {
|
||||
accountability: Accountability | null;
|
||||
@@ -504,11 +504,16 @@ export class GraphQLService {
|
||||
});
|
||||
}
|
||||
|
||||
const collectionInfo = await this.knex
|
||||
.select('singleton')
|
||||
.from('directus_collections')
|
||||
.where({ collection: collection })
|
||||
.first();
|
||||
const collectionInfo =
|
||||
(await this.knex
|
||||
.select('singleton')
|
||||
.from('directus_collections')
|
||||
.where({ collection: collection })
|
||||
.first()) ||
|
||||
systemCollectionRows.find(
|
||||
(collectionMeta) => collectionMeta?.collection === collection
|
||||
);
|
||||
|
||||
const result =
|
||||
collectionInfo?.singleton === true
|
||||
? await service.readSingleton(query)
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
Action,
|
||||
Accountability,
|
||||
PermissionsAction,
|
||||
Item,
|
||||
Item as AnyItem,
|
||||
Query,
|
||||
PrimaryKey,
|
||||
AbstractService,
|
||||
@@ -26,7 +26,7 @@ import getDefaultValue from '../utils/get-default-value';
|
||||
import { InvalidPayloadException } from '../exceptions';
|
||||
import { ForbiddenException } from '../exceptions';
|
||||
|
||||
export class ItemsService implements AbstractService {
|
||||
export class ItemsService<Item extends AnyItem = AnyItem> implements AbstractService {
|
||||
collection: string;
|
||||
knex: Knex;
|
||||
accountability: Accountability | null;
|
||||
@@ -52,7 +52,7 @@ export class ItemsService implements AbstractService {
|
||||
const primaryKeyField = await this.schemaInspector.primary(this.collection);
|
||||
const columns = await this.schemaInspector.columns(this.collection);
|
||||
|
||||
let payloads = clone(toArray(data));
|
||||
let payloads: AnyItem[] = clone(toArray(data));
|
||||
|
||||
const savedPrimaryKeys = await this.knex.transaction(async (trx) => {
|
||||
const payloadService = new PayloadService(this.collection, {
|
||||
@@ -194,7 +194,7 @@ export class ItemsService implements AbstractService {
|
||||
return Array.isArray(data) ? savedPrimaryKeys : savedPrimaryKeys[0];
|
||||
}
|
||||
|
||||
async readByQuery(query: Query): Promise<null | Item | Item[]> {
|
||||
async readByQuery(query: Query): Promise<null | Partial<Item> | Partial<Item>[]> {
|
||||
const authorizationService = new AuthorizationService({
|
||||
accountability: this.accountability,
|
||||
knex: this.knex,
|
||||
@@ -210,20 +210,24 @@ export class ItemsService implements AbstractService {
|
||||
}
|
||||
|
||||
const records = await runAST(ast, { knex: this.knex });
|
||||
return records;
|
||||
return records as Partial<Item> | Partial<Item>[] | null;
|
||||
}
|
||||
|
||||
readByKey(
|
||||
keys: PrimaryKey[],
|
||||
query?: Query,
|
||||
action?: PermissionsAction
|
||||
): Promise<null | Item[]>;
|
||||
readByKey(key: PrimaryKey, query?: Query, action?: PermissionsAction): Promise<null | Item>;
|
||||
): Promise<null | Partial<Item>[]>;
|
||||
readByKey(
|
||||
key: PrimaryKey,
|
||||
query?: Query,
|
||||
action?: PermissionsAction
|
||||
): Promise<null | Partial<Item>>;
|
||||
async readByKey(
|
||||
key: PrimaryKey | PrimaryKey[],
|
||||
query: Query = {},
|
||||
action: PermissionsAction = 'read'
|
||||
): Promise<null | Item | Item[]> {
|
||||
): Promise<null | Partial<Item> | Partial<Item>[]> {
|
||||
query = clone(query);
|
||||
const primaryKeyField = await this.schemaInspector.primary(this.collection);
|
||||
const keys = toArray(key);
|
||||
@@ -260,7 +264,7 @@ export class ItemsService implements AbstractService {
|
||||
|
||||
if (result === null) throw new ForbiddenException();
|
||||
|
||||
return result;
|
||||
return result as Partial<Item> | Partial<Item>[] | null;
|
||||
}
|
||||
|
||||
update(data: Partial<Item>, keys: PrimaryKey[]): Promise<PrimaryKey[]>;
|
||||
@@ -277,7 +281,7 @@ export class ItemsService implements AbstractService {
|
||||
if (data && key) {
|
||||
const keys = toArray(key);
|
||||
|
||||
let payload = clone(data);
|
||||
let payload: Partial<AnyItem> | Partial<AnyItem>[] = clone(data);
|
||||
|
||||
const customProcessed = await emitter.emitAsync(
|
||||
`${this.eventScope}.update.before`,
|
||||
@@ -550,11 +554,11 @@ export class ItemsService implements AbstractService {
|
||||
return await this.delete(keys);
|
||||
}
|
||||
|
||||
async readSingleton(query: Query) {
|
||||
async readSingleton(query: Query): Promise<Partial<Item>> {
|
||||
query = clone(query);
|
||||
query.single = true;
|
||||
|
||||
const record = (await this.readByQuery(query)) as Item;
|
||||
const record = (await this.readByQuery(query)) as Partial<Item>;
|
||||
|
||||
if (!record) {
|
||||
const columns = await this.schemaInspector.columnInfo(this.collection);
|
||||
@@ -564,7 +568,7 @@ export class ItemsService implements AbstractService {
|
||||
defaults[column.name] = getDefaultValue(column);
|
||||
}
|
||||
|
||||
return defaults;
|
||||
return defaults as Partial<Item>;
|
||||
}
|
||||
|
||||
return record;
|
||||
|
||||
@@ -40,7 +40,7 @@ export class MetaService {
|
||||
const dbQuery = database(collection).count('*', { as: 'count' });
|
||||
|
||||
if (query.filter) {
|
||||
await applyFilter(dbQuery, query.filter, collection);
|
||||
await applyFilter(this.knex, dbQuery, query.filter, collection);
|
||||
}
|
||||
|
||||
const records = await dbQuery;
|
||||
|
||||
@@ -17,6 +17,9 @@ import getLocalType from '../utils/get-local-type';
|
||||
import { format, formatISO } from 'date-fns';
|
||||
import { ForbiddenException } from '../exceptions';
|
||||
import { toArray } from '../utils/to-array';
|
||||
import { FieldMeta } from '../types';
|
||||
import { systemFieldRows } from '../database/system-data/fields';
|
||||
import { systemRelationRows } from '../database/system-data/relations';
|
||||
|
||||
type Action = 'create' | 'read' | 'update';
|
||||
|
||||
@@ -148,17 +151,21 @@ export class PayloadService {
|
||||
|
||||
const fieldsInPayload = Object.keys(processedPayload[0]);
|
||||
|
||||
const specialFieldsQuery = this.knex
|
||||
let specialFieldsInCollection: FieldMeta[] = await this.knex
|
||||
.select('field', 'special')
|
||||
.from('directus_fields')
|
||||
.where({ collection: this.collection })
|
||||
.whereNotNull('special');
|
||||
|
||||
if (action === 'read') {
|
||||
specialFieldsQuery.whereIn('field', fieldsInPayload);
|
||||
}
|
||||
specialFieldsInCollection.push(
|
||||
...systemFieldRows.filter((fieldMeta) => fieldMeta.collection === this.collection)
|
||||
);
|
||||
|
||||
const specialFieldsInCollection = await specialFieldsQuery;
|
||||
if (action === 'read') {
|
||||
specialFieldsInCollection = specialFieldsInCollection.filter((fieldMeta) => {
|
||||
return fieldsInPayload.includes(fieldMeta.field);
|
||||
});
|
||||
}
|
||||
|
||||
await Promise.all(
|
||||
processedPayload.map(async (record: any) => {
|
||||
@@ -203,13 +210,13 @@ export class PayloadService {
|
||||
}
|
||||
|
||||
async processField(
|
||||
field: { field: string; special: string },
|
||||
field: FieldMeta,
|
||||
payload: Partial<Item>,
|
||||
action: Action,
|
||||
accountability: Accountability | null
|
||||
) {
|
||||
if (!field.special) return payload[field.field];
|
||||
const fieldSpecials = field.special.split(',').map((s) => s.trim());
|
||||
const fieldSpecials = field.special ? toArray(field.special) : [];
|
||||
|
||||
let value = clone(payload[field.field]);
|
||||
|
||||
@@ -284,10 +291,15 @@ export class PayloadService {
|
||||
async processM2O(
|
||||
payload: Partial<Item> | Partial<Item>[]
|
||||
): Promise<Partial<Item> | Partial<Item>[]> {
|
||||
const relations = await this.knex
|
||||
.select<Relation[]>('*')
|
||||
.from('directus_relations')
|
||||
.where({ many_collection: this.collection });
|
||||
const relations = [
|
||||
...(await this.knex
|
||||
.select<Relation[]>('*')
|
||||
.from('directus_relations')
|
||||
.where({ many_collection: this.collection })),
|
||||
...systemRelationRows.filter(
|
||||
(systemRelation) => systemRelation.many_collection === this.collection
|
||||
),
|
||||
];
|
||||
|
||||
const payloads = clone(Array.isArray(payload) ? payload : [payload]);
|
||||
|
||||
@@ -334,10 +346,15 @@ export class PayloadService {
|
||||
* Recursively save/update all nested related o2m items
|
||||
*/
|
||||
async processO2M(payload: Partial<Item> | Partial<Item>[], parent?: PrimaryKey) {
|
||||
const relations = await this.knex
|
||||
.select<Relation[]>('*')
|
||||
.from('directus_relations')
|
||||
.where({ one_collection: this.collection });
|
||||
const relations = [
|
||||
...(await this.knex
|
||||
.select<Relation[]>('*')
|
||||
.from('directus_relations')
|
||||
.where({ one_collection: this.collection })),
|
||||
...systemRelationRows.filter(
|
||||
(systemRelation) => systemRelation.one_collection === this.collection
|
||||
),
|
||||
];
|
||||
|
||||
const payloads = clone(toArray(payload));
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@ import { AbstractServiceOptions, Query, PrimaryKey, PermissionsAction, Relation
|
||||
import { PermissionsService } from './permissions';
|
||||
import { toArray } from '../utils/to-array';
|
||||
|
||||
import { systemRelationRows } from '../database/system-data/relations';
|
||||
|
||||
/**
|
||||
* @TODO update foreign key constraints when relations are updated
|
||||
*/
|
||||
@@ -26,6 +28,10 @@ export class RelationsService extends ItemsService {
|
||||
| ParsedRelation[]
|
||||
| null;
|
||||
|
||||
if (results && Array.isArray(results)) {
|
||||
results.push(...(systemRelationRows as ParsedRelation[]));
|
||||
}
|
||||
|
||||
const filteredResults = await this.filterForbidden(results);
|
||||
|
||||
return filteredResults;
|
||||
@@ -48,6 +54,9 @@ export class RelationsService extends ItemsService {
|
||||
| ParsedRelation[]
|
||||
| null;
|
||||
|
||||
// No need to merge system relations here. They don't have PKs so can never be directly
|
||||
// targetted
|
||||
|
||||
const filteredResults = await this.filterForbidden(results);
|
||||
return filteredResults;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import database from '../database';
|
||||
import Knex from 'knex';
|
||||
import { InvalidPayloadException, ForbiddenException } from '../exceptions';
|
||||
import SchemaInspector from 'knex-schema-inspector';
|
||||
import { systemCollectionRows } from '../database/system-data/collections';
|
||||
|
||||
export class UtilsService {
|
||||
knex: Knex;
|
||||
@@ -16,11 +17,12 @@ export class UtilsService {
|
||||
async sort(collection: string, { item, to }: { item: PrimaryKey; to: PrimaryKey }) {
|
||||
const schemaInspector = SchemaInspector(this.knex);
|
||||
|
||||
const sortFieldResponse = await this.knex
|
||||
.select('sort_field')
|
||||
.from('directus_collections')
|
||||
.where({ collection })
|
||||
.first();
|
||||
const sortFieldResponse =
|
||||
(await this.knex
|
||||
.select('sort_field')
|
||||
.from('directus_collections')
|
||||
.where({ collection })
|
||||
.first()) || systemCollectionRows;
|
||||
|
||||
const sortField = sortFieldResponse?.sort_field;
|
||||
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
import { Field } from './field';
|
||||
import { Table } from 'knex-schema-inspector/lib/types/table';
|
||||
|
||||
export type CollectionMeta = {
|
||||
collection: string;
|
||||
note: string | null;
|
||||
hidden: boolean;
|
||||
singleton: boolean;
|
||||
icon: string | null;
|
||||
translations: Record<string, string>;
|
||||
};
|
||||
|
||||
export type Collection = {
|
||||
collection: string;
|
||||
fields?: Field[];
|
||||
meta: {
|
||||
collection: string;
|
||||
note: string | null;
|
||||
hidden: boolean;
|
||||
singleton: boolean;
|
||||
icon: string | null;
|
||||
translations: Record<string, string>;
|
||||
} | null;
|
||||
meta: CollectionMeta | null;
|
||||
schema: Table;
|
||||
};
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
import { QueryBuilder } from 'knex';
|
||||
import { Query, Filter } from '../types';
|
||||
import database, { schemaInspector } from '../database';
|
||||
import { Query, Filter, Relation } from '../types';
|
||||
import { schemaInspector } from '../database';
|
||||
import Knex from 'knex';
|
||||
import { clone, isPlainObject } from 'lodash';
|
||||
import { systemRelationRows } from '../database/system-data/relations';
|
||||
|
||||
export default async function applyQuery(collection: string, dbQuery: QueryBuilder, query: Query) {
|
||||
export default async function applyQuery(
|
||||
knex: Knex,
|
||||
collection: string,
|
||||
dbQuery: QueryBuilder,
|
||||
query: Query
|
||||
) {
|
||||
if (query.filter) {
|
||||
await applyFilter(dbQuery, query.filter, collection);
|
||||
await applyFilter(knex, dbQuery, query.filter, collection);
|
||||
}
|
||||
|
||||
if (query.sort) {
|
||||
@@ -46,8 +53,16 @@ export default async function applyQuery(collection: string, dbQuery: QueryBuild
|
||||
}
|
||||
}
|
||||
|
||||
export async function applyFilter(rootQuery: QueryBuilder, rootFilter: Filter, collection: string) {
|
||||
const relations = await database.select('*').from('directus_relations');
|
||||
export async function applyFilter(
|
||||
knex: Knex,
|
||||
rootQuery: QueryBuilder,
|
||||
rootFilter: Filter,
|
||||
collection: string
|
||||
) {
|
||||
const relations: Relation[] = [
|
||||
...(await knex.select('*').from('directus_relations')),
|
||||
...systemRelationRows,
|
||||
];
|
||||
|
||||
addWhereClauses(rootQuery, rootFilter, collection);
|
||||
addJoins(rootQuery, rootFilter, collection);
|
||||
|
||||
@@ -16,6 +16,8 @@ import { cloneDeep } from 'lodash';
|
||||
import Knex from 'knex';
|
||||
import SchemaInspector from 'knex-schema-inspector';
|
||||
import { getRelationType } from '../utils/get-relation-type';
|
||||
import { systemFieldRows } from '../database/system-data/fields';
|
||||
import { systemRelationRows } from '../database/system-data/relations';
|
||||
|
||||
type GetASTOptions = {
|
||||
accountability?: Accountability | null;
|
||||
@@ -39,7 +41,10 @@ export default async function getASTFromQuery(
|
||||
* we might not need al this info at all times, but it's easier to fetch it all once, than trying to fetch it for every
|
||||
* requested field. @todo look into utilizing graphql/dataloader for this purpose
|
||||
*/
|
||||
const relations = await knex.select<Relation[]>('*').from('directus_relations');
|
||||
const relations = [
|
||||
...(await knex.select<Relation[]>('*').from('directus_relations')),
|
||||
...systemRelationRows,
|
||||
];
|
||||
|
||||
const permissions =
|
||||
accountability && accountability.admin !== true
|
||||
@@ -261,9 +266,12 @@ export default async function getASTFromQuery(
|
||||
|
||||
async function getFieldsInCollection(collection: string) {
|
||||
const columns = (await schemaInspector.columns(collection)).map((column) => column.column);
|
||||
const fields = (
|
||||
await knex.select('field').from('directus_fields').where({ collection })
|
||||
).map((field) => field.field);
|
||||
const fields = [
|
||||
...(await knex.select('field').from('directus_fields').where({ collection })).map(
|
||||
(field) => field.field
|
||||
),
|
||||
...systemFieldRows.map((fieldMeta) => fieldMeta.field),
|
||||
];
|
||||
|
||||
const fieldsInCollection = [
|
||||
...columns,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import database, { schemaInspector } from '../database';
|
||||
import { uniq } from 'lodash';
|
||||
import { systemFieldRows } from '../database/system-data/fields';
|
||||
|
||||
export default async function hasFields(fields: { collection: string; field: string }[]) {
|
||||
const fieldsObject: { [collection: string]: string[] } = {};
|
||||
@@ -33,6 +34,9 @@ export async function collectionHasFields(collection: string, fieldKeys: string[
|
||||
const existingFields = uniq([
|
||||
...columns.map(({ column }) => column),
|
||||
...fields.map(({ field }) => field),
|
||||
...systemFieldRows
|
||||
.filter((fieldMeta) => fieldMeta.collection === collection)
|
||||
.map((fieldMeta) => fieldMeta.field),
|
||||
]);
|
||||
|
||||
for (const key of fieldKeys) {
|
||||
|
||||
8
api/src/utils/require-yaml.ts
Normal file
8
api/src/utils/require-yaml.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import fse from 'fs-extra';
|
||||
import yaml from 'js-yaml';
|
||||
|
||||
export function requireYAML(filepath: string) {
|
||||
const yamlRaw = fse.readFileSync(filepath, 'utf8');
|
||||
|
||||
return yaml.safeLoad(yamlRaw) as Record<string, any>;
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
export function toArray(val: string): string[];
|
||||
export function toArray(val: any | any[]): any[] {
|
||||
export function toArray<T = any>(val: T | T[]): T[] {
|
||||
if (typeof val === 'string') {
|
||||
return val.split(',');
|
||||
return (val.split(',') as unknown) as T[];
|
||||
}
|
||||
|
||||
return Array.isArray(val) ? val : [val];
|
||||
|
||||
Reference in New Issue
Block a user