Merge branch 'main' into add-modified_on

This commit is contained in:
Rijk van Zanten
2020-10-11 18:53:42 -04:00
committed by GitHub
142 changed files with 31727 additions and 2875 deletions

3
.gitignore vendored
View File

@@ -7,7 +7,4 @@ npm-debug.log
lerna-debug.log
.nova
*.code-workspace
api/package-lock.json
app/package-lock.json
packages/**/package-lock.json
dist

8021
api/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "directus",
"version": "9.0.0-beta.9",
"version": "9.0.0-beta.10",
"license": "GPL-3.0-only",
"homepage": "https://github.com/directus/next#readme",
"description": "Directus is a real-time API and App dashboard for managing SQL database content.",
@@ -66,7 +66,7 @@
"dependencies": {
"@directus/app": "file:../app",
"@directus/format-title": "^3.2.0",
"@directus/specs": "^9.0.0-beta.8",
"@directus/specs": "file:../packages/spec",
"@slynova/flydrive": "^1.0.2",
"@slynova/flydrive-gcs": "^1.0.2",
"@slynova/flydrive-s3": "^1.0.2",
@@ -127,7 +127,7 @@
"mssql": "^6.2.0",
"mysql": "^2.18.1",
"oracledb": "^5.0.0",
"pg": "^8.3.3",
"pg": "^8.4.0",
"sqlite3": "^5.0.0"
},
"gitHead": "4476da28dbbc2824e680137aa28b2b91b5afabec",

View File

@@ -13,14 +13,20 @@ type RunASTOptions = {
child?: boolean;
};
export default async function runAST(originalAST: AST, options?: RunASTOptions): Promise<null | Item | Item[]> {
export default async function runAST(
originalAST: AST,
options?: RunASTOptions
): Promise<null | Item | Item[]> {
const ast = cloneDeep(originalAST);
const query = options?.query || ast.query;
const knex = options?.knex || database;
// Retrieve the database columns to select in the current AST
const { columnsToSelect, primaryKeyField, nestedCollectionASTs } = await parseCurrentLevel(ast, knex);
const { columnsToSelect, primaryKeyField, nestedCollectionASTs } = await parseCurrentLevel(
ast,
knex
);
// The actual knex query builder instance. This is a promise that resolves with the raw items from the db
const dbQuery = await getDBQuery(knex, ast.name, columnsToSelect, query, primaryKeyField);
@@ -63,7 +69,7 @@ export default async function runAST(originalAST: AST, options?: RunASTOptions):
// and nesting is done, we parse through the output structure, and filter out all non-requested
// fields
if (options?.child !== true) {
items = removeTemporaryFields(items, originalAST);
items = removeTemporaryFields(items, originalAST, primaryKeyField);
}
return items;
@@ -109,7 +115,13 @@ async function parseCurrentLevel(ast: AST, knex: Knex) {
return { columnsToSelect, nestedCollectionASTs, primaryKeyField };
}
async function getDBQuery(knex: Knex, table: string, columns: string[], query: Query, primaryKeyField: string): Promise<QueryBuilder> {
async function getDBQuery(
knex: Knex,
table: string,
columns: string[],
query: Query,
primaryKeyField: string
): Promise<QueryBuilder> {
let dbQuery = knex.select(columns.map((column) => `${table}.${column}`)).from(table);
const queryCopy = clone(query);
@@ -127,7 +139,10 @@ async function getDBQuery(knex: Knex, table: string, columns: string[], query: Q
return dbQuery;
}
function applyParentFilters(nestedCollectionASTs: NestedCollectionAST[], parentItem: Item | Item[]) {
function applyParentFilters(
nestedCollectionASTs: NestedCollectionAST[],
parentItem: Item | Item[]
) {
const parentItems = Array.isArray(parentItem) ? parentItem : [parentItem];
for (const nestedAST of nestedCollectionASTs) {
@@ -139,15 +154,15 @@ function applyParentFilters(nestedCollectionASTs: NestedCollectionAST[], parentI
filter: {
...(nestedAST.query.filter || {}),
[nestedAST.relation.one_primary]: {
_in: uniq(parentItems.map((res) => res[nestedAST.relation.many_field])).filter(
(id) => id
),
}
}
}
_in: uniq(
parentItems.map((res) => res[nestedAST.relation.many_field])
).filter((id) => id),
},
},
};
} else {
const relatedM2OisFetched = !!nestedAST.children.find((child) => {
return child.type === 'field' && child.name === nestedAST.relation.many_field
return child.type === 'field' && child.name === nestedAST.relation.many_field;
});
if (relatedM2OisFetched === false) {
@@ -159,24 +174,33 @@ function applyParentFilters(nestedCollectionASTs: NestedCollectionAST[], parentI
filter: {
...(nestedAST.query.filter || {}),
[nestedAST.relation.many_field]: {
_in: uniq(parentItems.map((res) => res[nestedAST.parentKey])).filter((id) => id),
}
}
}
_in: uniq(parentItems.map((res) => res[nestedAST.parentKey])).filter(
(id) => id
),
},
},
};
}
}
return nestedCollectionASTs;
}
function mergeWithParentItems(nestedItem: Item | Item[], parentItem: Item | Item[], nestedAST: NestedCollectionAST, o2mLimit?: number | null) {
function mergeWithParentItems(
nestedItem: Item | Item[],
parentItem: Item | Item[],
nestedAST: NestedCollectionAST,
o2mLimit?: number | null
) {
const nestedItems = Array.isArray(nestedItem) ? nestedItem : [nestedItem];
const parentItems = clone(Array.isArray(parentItem) ? parentItem : [parentItem]);
if (isM2O(nestedAST)) {
for (const parentItem of parentItems) {
const itemChild = nestedItems.find((nestedItem) => {
return nestedItem[nestedAST.relation.one_primary] === parentItem[nestedAST.fieldKey];
return (
nestedItem[nestedAST.relation.one_primary] === parentItem[nestedAST.fieldKey]
);
});
parentItem[nestedAST.fieldKey] = itemChild || null;
@@ -188,8 +212,10 @@ function mergeWithParentItems(nestedItem: Item | Item[], parentItem: Item | Item
if (Array.isArray(nestedItem[nestedAST.relation.many_field])) return true;
return (
nestedItem[nestedAST.relation.many_field] === parentItem[nestedAST.relation.one_primary] ||
nestedItem[nestedAST.relation.many_field]?.[nestedAST.relation.many_primary] === parentItem[nestedAST.relation.one_primary]
nestedItem[nestedAST.relation.many_field] ===
parentItem[nestedAST.relation.one_primary] ||
nestedItem[nestedAST.relation.many_field]?.[nestedAST.relation.many_primary] ===
parentItem[nestedAST.relation.one_primary]
);
});
@@ -206,21 +232,34 @@ function mergeWithParentItems(nestedItem: Item | Item[], parentItem: Item | Item
return Array.isArray(parentItem) ? parentItems : parentItems[0];
}
function removeTemporaryFields(rawItem: Item | Item[], ast: AST | NestedCollectionAST): Item | Item[] {
function removeTemporaryFields(
rawItem: Item | Item[],
ast: AST | NestedCollectionAST,
primaryKeyField: string
): Item | Item[] {
const rawItems: Item[] = Array.isArray(rawItem) ? rawItem : [rawItem];
const items: Item[] = [];
const fields = ast.children.filter((child) => child.type === 'field').map((child) => child.name);
const nestedCollections = ast.children.filter((child) => child.type === 'collection') as NestedCollectionAST[];
const fields = ast.children
.filter((child) => child.type === 'field')
.map((child) => child.name);
const nestedCollections = ast.children.filter(
(child) => child.type === 'collection'
) as NestedCollectionAST[];
for (const rawItem of rawItems) {
if (rawItem === null) return rawItem;
const item = fields.includes('*') ? rawItem : pick(rawItem, fields);
const item = fields.length > 0 ? pick(rawItem, fields) : rawItem[primaryKeyField];
for (const nestedCollection of nestedCollections) {
if (item[nestedCollection.fieldKey] !== null) {
item[nestedCollection.fieldKey] = removeTemporaryFields(rawItem[nestedCollection.fieldKey], nestedCollection);
item[nestedCollection.fieldKey] = removeTemporaryFields(
rawItem[nestedCollection.fieldKey],
nestedCollection,
nestedCollection.relatedKey
);
}
}

View File

@@ -11,28 +11,42 @@ defaults:
data:
- 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

View File

@@ -32,6 +32,11 @@ data:
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

View File

@@ -33,6 +33,7 @@ fields:
special: json
sort: 3
width: full
display: tags
- collection: directus_files
field: location
interface: text-input
@@ -104,4 +105,10 @@ fields:
locked: true
special: date-updated
width: half
display: datetime
display: datetime
- collection: directus_files
field: created_on
display: datetime
- collection: directus_files
field: created_by
display: user

View File

@@ -20,7 +20,7 @@ import logger from '../logger';
import { PayloadService } from './payload';
import { AuthorizationService } from './authorization';
import { pick, clone } from 'lodash';
import { pick, clone, cloneDeep } from 'lodash';
import getDefaultValue from '../utils/get-default-value';
import { InvalidPayloadException } from '../exceptions';
@@ -29,6 +29,7 @@ export class ItemsService implements AbstractService {
knex: Knex;
accountability: Accountability | null;
eventScope: string;
schemaInspector: ReturnType<typeof SchemaInspector>;
constructor(collection: string, options?: AbstractServiceOptions) {
this.collection = collection;
@@ -38,15 +39,16 @@ export class ItemsService implements AbstractService {
? this.collection.substring(9)
: 'items';
this.schemaInspector = SchemaInspector(this.knex);
return this;
}
async create(data: Partial<Item>[]): Promise<PrimaryKey[]>;
async create(data: Partial<Item>): Promise<PrimaryKey>;
async create(data: Partial<Item> | Partial<Item>[]): Promise<PrimaryKey | PrimaryKey[]> {
const schemaInspector = SchemaInspector(this.knex);
const primaryKeyField = await schemaInspector.primary(this.collection);
const columns = await schemaInspector.columns(this.collection);
const primaryKeyField = await this.schemaInspector.primary(this.collection);
const columns = await this.schemaInspector.columns(this.collection);
let payloads = clone(Array.isArray(data) ? data : [data]);
@@ -220,8 +222,7 @@ export class ItemsService implements AbstractService {
action: PermissionsAction = 'read'
): Promise<null | Item | Item[]> {
query = clone(query);
const schemaInspector = SchemaInspector(this.knex);
const primaryKeyField = await schemaInspector.primary(this.collection);
const primaryKeyField = await this.schemaInspector.primary(this.collection);
const keys = Array.isArray(key) ? key : [key];
if (keys.length === 1) {
@@ -263,9 +264,8 @@ export class ItemsService implements AbstractService {
data: Partial<Item> | Partial<Item>[],
key?: PrimaryKey | PrimaryKey[]
): Promise<PrimaryKey | PrimaryKey[]> {
const schemaInspector = SchemaInspector(this.knex);
const primaryKeyField = await schemaInspector.primary(this.collection);
const columns = await schemaInspector.columns(this.collection);
const primaryKeyField = await this.schemaInspector.primary(this.collection);
const columns = await this.schemaInspector.columns(this.collection);
// Updating one or more items to the same payload
if (data && key) {
@@ -421,12 +421,58 @@ export class ItemsService implements AbstractService {
return keys;
}
async updateByQuery(data: Partial<Item>, query: Query): Promise<PrimaryKey[]> {
const primaryKeyField = await this.schemaInspector.primary(this.collection);
const readQuery = cloneDeep(query);
readQuery.fields = [primaryKeyField];
// Not authenticated:
const itemsService = new ItemsService(this.collection);
let itemsToUpdate = await itemsService.readByQuery(readQuery);
itemsToUpdate = Array.isArray(itemsToUpdate) ? itemsToUpdate : [itemsToUpdate];
const keys: PrimaryKey[] = itemsToUpdate.map(
(item: Partial<Item>) => item[primaryKeyField]
);
return await this.update(data, keys);
}
upsert(data: Partial<Item>): Promise<PrimaryKey>;
upsert(data: Partial<Item>[]): Promise<PrimaryKey[]>;
async upsert(data: Partial<Item> | Partial<Item>[]): Promise<PrimaryKey | PrimaryKey[]> {
const primaryKeyField = await this.schemaInspector.primary(this.collection);
const payloads = Array.isArray(data) ? data : [data];
const primaryKeys: PrimaryKey[] = [];
for (const payload of payloads) {
const primaryKey = payload[primaryKeyField];
const exists =
primaryKey &&
!!(await this.knex
.select(primaryKeyField)
.from(this.collection)
.where({ [primaryKeyField]: primaryKey })
.first());
if (exists) {
const keys = await this.update([payload]);
primaryKeys.push(...keys);
} else {
const key = await this.create(payload);
primaryKeys.push(key);
}
}
return Array.isArray(data) ? primaryKeys : primaryKeys[0];
}
delete(key: PrimaryKey): Promise<PrimaryKey>;
delete(keys: PrimaryKey[]): Promise<PrimaryKey[]>;
async delete(key: PrimaryKey | PrimaryKey[]): Promise<PrimaryKey | PrimaryKey[]> {
const keys = (Array.isArray(key) ? key : [key]) as PrimaryKey[];
const schemaInspector = SchemaInspector(this.knex);
const primaryKeyField = await schemaInspector.primary(this.collection);
const primaryKeyField = await this.schemaInspector.primary(this.collection);
if (this.accountability && this.accountability.admin !== true) {
const authorizationService = new AuthorizationService({
@@ -480,15 +526,31 @@ export class ItemsService implements AbstractService {
return key;
}
async deleteByQuery(query: Query): Promise<PrimaryKey[]> {
const primaryKeyField = await this.schemaInspector.primary(this.collection);
const readQuery = cloneDeep(query);
readQuery.fields = [primaryKeyField];
// Not authenticated:
const itemsService = new ItemsService(this.collection);
let itemsToDelete = await itemsService.readByQuery(readQuery);
itemsToDelete = Array.isArray(itemsToDelete) ? itemsToDelete : [itemsToDelete];
const keys: PrimaryKey[] = itemsToDelete.map(
(item: Partial<Item>) => item[primaryKeyField]
);
return await this.delete(keys);
}
async readSingleton(query: Query) {
query = clone(query);
const schemaInspector = SchemaInspector(this.knex);
query.single = true;
const record = (await this.readByQuery(query)) as Item;
if (!record) {
const columns = await schemaInspector.columnInfo(this.collection);
const columns = await this.schemaInspector.columnInfo(this.collection);
const defaults: Record<string, any> = {};
for (const column of columns) {
@@ -502,8 +564,7 @@ export class ItemsService implements AbstractService {
}
async upsertSingleton(data: Partial<Item>) {
const schemaInspector = SchemaInspector(this.knex);
const primaryKeyField = await schemaInspector.primary(this.collection);
const primaryKeyField = await this.schemaInspector.primary(this.collection);
const record = await this.knex
.select(primaryKeyField)

View File

@@ -6,7 +6,7 @@
import argon2 from 'argon2';
import { v4 as uuidv4 } from 'uuid';
import database from '../database';
import { clone, isObject } from 'lodash';
import { clone, isObject, cloneDeep } from 'lodash';
import { Relation, Item, AbstractServiceOptions, Accountability, PrimaryKey } from '../types';
import { ItemsService } from './items';
import { URL } from 'url';
@@ -15,6 +15,7 @@ import env from '../env';
import SchemaInspector from 'knex-schema-inspector';
import getLocalType from '../utils/get-local-type';
import { format, formatISO } from 'date-fns';
import { ForbiddenException } from '../exceptions';
type Action = 'create' | 'read' | 'update';
@@ -313,11 +314,7 @@ export class PayloadService {
const exists = hasPrimaryKey && !!(await itemsService.readByKey(relatedPrimaryKey));
if (exists) {
if (relatedRecord.hasOwnProperty('$delete') && relatedRecord.$delete) {
await itemsService.delete(relatedPrimaryKey);
} else {
await itemsService.update(relatedRecord, relatedPrimaryKey);
}
await itemsService.update(relatedRecord, relatedPrimaryKey);
} else {
relatedPrimaryKey = await itemsService.create(relatedRecord);
}
@@ -353,48 +350,52 @@ export class PayloadService {
});
for (const relation of relationsToProcess) {
const relatedRecords: Partial<Item>[] = payload[relation.one_field].map(
(record: string | number | Partial<Item>) => {
if (typeof record === 'string' || typeof record === 'number') {
record = {
[relation.many_primary]: record,
};
}
return {
...record,
[relation.many_field]: parent || payload[relation.one_primary],
};
}
);
const itemsService = new ItemsService(relation.many_collection, {
accountability: this.accountability,
knex: this.knex,
});
const toBeCreated = relatedRecords.filter(
(record) => record.hasOwnProperty(relation.many_primary) === false
const relatedRecords: Partial<Item>[] = [];
for (const relatedRecord of payload[relation.one_field]) {
let record = cloneDeep(relatedRecord);
if (typeof relatedRecord === 'string' || typeof relatedRecord === 'number') {
const exists = !!(await this.knex
.select(relation.many_primary)
.from(relation.many_collection)
.where({ [relation.many_primary]: record })
.first());
if (exists === false)
throw new ForbiddenException(undefined, {
item: record,
collection: relation.many_collection,
});
record = {
[relation.many_primary]: relatedRecord,
};
}
relatedRecords.push({
...record,
[relation.many_field]: parent || payload[relation.one_primary],
});
}
const primaryKeys = await itemsService.upsert(relatedRecords);
await itemsService.updateByQuery(
{ [relation.many_field]: null },
{
filter: {
[relation.many_primary]: {
_nin: primaryKeys,
},
},
}
);
const toBeUpdated = relatedRecords.filter(
(record) =>
record.hasOwnProperty(relation.many_primary) === true &&
record.hasOwnProperty('$delete') === false
);
const toBeDeleted = relatedRecords
.filter(
(record) =>
record.hasOwnProperty(relation.many_primary) === true &&
record.hasOwnProperty('$delete') &&
record.$delete === true
)
.map((record) => record[relation.many_primary]);
await itemsService.create(toBeCreated);
await itemsService.update(toBeUpdated);
await itemsService.delete(toBeDeleted);
}
}
}

View File

@@ -9,6 +9,7 @@ export type NestedCollectionAST = {
fieldKey: string;
relation: Relation;
parentKey: string;
relatedKey: string;
};
export type FieldAST = {

View File

@@ -62,16 +62,84 @@ export default async function getASTFromQuery(
delete query.fields;
delete query.deep;
ast.children = (await parseFields(collection, fields, deep)).filter(filterEmptyChildCollections);
ast.children = await parseFields(collection, fields, deep);
return ast;
function convertWildcards(parentCollection: string, fields: string[]) {
async function parseFields(
parentCollection: string,
fields: string[],
deep?: Record<string, Query>
) {
fields = await convertWildcards(parentCollection, fields);
if (!fields) return [];
const children: (NestedCollectionAST | FieldAST)[] = [];
const relationalStructure: Record<string, string[]> = {};
for (const field of fields) {
const isRelational =
field.includes('.') ||
!!relations.find(
(relation) =>
(relation.many_collection === parentCollection &&
relation.many_field === field) ||
(relation.one_collection === parentCollection &&
relation.one_field === field)
);
if (isRelational) {
// field is relational
const parts = field.split('.');
if (relationalStructure.hasOwnProperty(parts[0]) === false) {
relationalStructure[parts[0]] = [];
}
if (parts.length > 1) {
relationalStructure[parts[0]].push(parts.slice(1).join('.'));
}
} else {
children.push({ type: 'field', name: field });
}
}
for (const [relationalField, nestedFields] of Object.entries(relationalStructure)) {
const relatedCollection = getRelatedCollection(parentCollection, relationalField);
if (!relatedCollection) continue;
const relation = getRelation(parentCollection, relationalField);
if (!relation) continue;
const child: NestedCollectionAST = {
type: 'collection',
name: relatedCollection,
fieldKey: relationalField,
parentKey: await schemaInspector.primary(parentCollection),
relatedKey: await schemaInspector.primary(relatedCollection),
relation: relation,
query: deep?.[relationalField] || {},
children: await parseFields(relatedCollection, nestedFields),
};
children.push(child);
}
return children;
}
async function convertWildcards(parentCollection: string, fields: string[]) {
const fieldsInCollection = await getFieldsInCollection(parentCollection);
const allowedFields = permissions
? permissions
.find((permission) => parentCollection === permission.collection)
?.fields?.split(',')
: ['*'];
: fieldsInCollection;
if (!allowedFields || allowedFields.length === 0) return [];
@@ -81,8 +149,13 @@ export default async function getASTFromQuery(
if (fieldKey.includes('*') === false) continue;
if (fieldKey === '*') {
if (allowedFields.includes('*')) continue;
fields.splice(index, 1, ...allowedFields);
// Set to all fields in collection
if (allowedFields.includes('*')) {
fields.splice(index, 1, ...fieldsInCollection);
} else {
// Set to all allowed fields
fields.splice(index, 1, ...allowedFields);
}
}
// Swap *.* case for *,<relational-field>.*,<another-relational>.*
@@ -122,57 +195,6 @@ export default async function getASTFromQuery(
return fields;
}
async function parseFields(parentCollection: string, fields: string[], deep?: Record<string, Query>) {
fields = convertWildcards(parentCollection, fields);
if (!fields) return [];
const children: (NestedCollectionAST | FieldAST)[] = [];
const relationalStructure: Record<string, string[]> = {};
for (const field of fields) {
if (field.includes('.') === false) {
children.push({ type: 'field', name: field });
} else {
// field is relational
const parts = field.split('.');
if (relationalStructure.hasOwnProperty(parts[0]) === false) {
relationalStructure[parts[0]] = [];
}
relationalStructure[parts[0]].push(parts.slice(1).join('.'));
}
}
for (const [relationalField, nestedFields] of Object.entries(relationalStructure)) {
const relatedCollection = getRelatedCollection(parentCollection, relationalField);
if (!relatedCollection) continue;
const relation = getRelation(parentCollection, relationalField);
if (!relation) continue;
const child: NestedCollectionAST = {
type: 'collection',
name: relatedCollection,
fieldKey: relationalField,
parentKey: await schemaInspector.primary(parentCollection),
relation: relation,
query: deep?.[relationalField] || {},
children: (await parseFields(relatedCollection, nestedFields)).filter(
filterEmptyChildCollections
),
};
children.push(child);
}
return children;
}
function getRelation(collection: string, field: string) {
const relation = relations.find((relation) => {
return (
@@ -198,8 +220,19 @@ export default async function getASTFromQuery(
}
}
function filterEmptyChildCollections(childAST: FieldAST | NestedCollectionAST) {
if (childAST.type === 'collection' && childAST.children.length === 0) return false;
return true;
async function getFieldsInCollection(collection: string) {
const columns = (await schemaInspector.columns(collection)).map((column) => column.column);
const fields = (
await database.select('field').from('directus_fields').where({ collection })
).map((field) => field.field);
const fieldsInCollection = [
...columns,
...fields.filter((field) => {
return columns.includes(field) === false;
}),
];
return fieldsInCollection;
}
}

17245
app/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "@directus/app",
"version": "9.0.0-beta.9",
"version": "9.0.0-beta.10",
"private": false,
"description": "Directus is an Open-Source Headless CMS & API for Managing Custom Databases",
"author": "Rijk van Zanten <rijk@rngr.org>",

View File

@@ -1,7 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<g fill="none" fill-rule="evenodd">
<path fill="#ECEFF1" d="M0 0h64v64H0z"/>
<path d="M32 12a20 20 0 100 40 20 20 0 000-40zm-9.86 32.56C23 42.76 28.24 41 32 41s9.02 1.76 9.86 3.56a15.8 15.8 0 01-19.72 0zm22.58-2.9C41.86 38.18 34.92 37 32 37s-9.86 1.18-12.72 4.66A16.02 16.02 0 1148 32a15.9 15.9 0 01-3.28 9.66zM32 20c-3.88 0-7 3.12-7 7s3.12 7 7 7 7-3.12 7-7-3.12-7-7-7zm0 10a3 3 0 110-6 3 3 0 010 6z" fill="#B0BEC5"/>
<path d="M32 12a20 20 0 100 40 20 20 0 000-40zm-9.86 32.56C23 42.76 28.24 41 32 41s9.02 1.76 9.86 3.56a15.8 15.8 0 01-19.72 0zm22.58-2.9C41.86 38.18 34.92 37 32 37s-9.86 1.18-12.72 4.66A16.02 16.02 0 1148 32a15.9 15.9 0 01-3.28 9.66zM32 20c-3.88 0-7 3.12-7 7s3.12 7 7 7 7-3.12 7-7-3.12-7-7-7zm0 10a3 3 0 110-6 3 3 0 010 6z" fill="#B0BEC5"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 539 B

After

Width:  |  Height:  |  Size: 496 B

View File

@@ -2,7 +2,7 @@ import capitalizeFirst from '@/utils/capitalize-first';
interface HTMLExpandElement extends HTMLElement {
_parent?: (Node & ParentNode & HTMLElement) | null;
_initialStyle: {
_initialStyle?: {
transition: string;
visibility: string;
overflow: string;
@@ -28,6 +28,7 @@ export default function (expandedParentClass = '', xAxis = false) {
enter(el: HTMLExpandElement) {
const initialStyle = el._initialStyle;
if (!initialStyle) return;
const offset = `${el[offsetProperty]}px`;
el.style.setProperty('transition', 'none', 'important');
@@ -82,6 +83,7 @@ export default function (expandedParentClass = '', xAxis = false) {
}
function resetStyles(el: HTMLExpandElement) {
if (!el._initialStyle) return;
const size = el._initialStyle[sizeProperty];
el.style.overflow = el._initialStyle.overflow;
if (size != null) el.style[sizeProperty] = size;

View File

@@ -83,9 +83,14 @@ function mapKeys(key: string) {
function callHandlers(event: KeyboardEvent) {
Object.entries(handlers).forEach(([key, value]) => {
const rest = key.split('+').filter((keySegment) => keysdown.has(keySegment) === false);
const keys = key.split('+')
if (rest.length > 0) return;
for(key of keysdown) {
if(keys.includes(key) === false) return;
}
for(key of keys) {
if(keysdown.has(key) === false) return;
}
for (let i = 0; i < value.length; i++) {
let cancel = false;

View File

@@ -55,6 +55,7 @@ export default defineComponent({
if (props.value.avatar?.id) {
return `${getRootPath()}assets/${props.value.avatar.id}?key=system-small-cover`;
}
return null
});
return { src };

View File

@@ -924,6 +924,7 @@
"page_help_collections_detail": "**Item Detail** — A form for viewing and managing this item. This sidebar also contains a full history of revisions, and embedded comments.",
"page_help_activity_browse": "**Browse Activity** — A comprehensive listing of all your user's system and content activity.",
"page_help_activity_detail": "**Activity Detail** — Shows accountability info, revision data, and the update message for this activity record.",
"page_help_docs_global": "**Documentation Overview** — Docs tailored specifically to this project's version and schema.",
"page_help_files_browse": "**File Library** — Lists all file assets uploaded to this project. Customize layout, filters, and sorting to tailor your view, and even save bookmarks of these different configurations for quick access.",
"page_help_files_detail": "**File Detail** — A form for managing file metadata, editing the original asset, and updating access settings.",
"page_help_settings_project": "**Project Settings** — Your project's global configuration options.",
@@ -977,6 +978,8 @@
"collection_removed": "Collection Removed",
"collection_updated": "Collection Updated",
"collections_and_fields": "Collection & Fields",
"singleton": "Singleton",
"singleton_label": "Treat as single object",
"fields": {
"directus_activity": {

View File

@@ -0,0 +1,445 @@
<template>
<div class="md" v-html="html" @click="onClick" />
</template>
<script lang="ts">
import { defineComponent, ref, computed, watch, PropType, onMounted, onUpdated } from '@vue/composition-api';
import marked, { Renderer } from 'marked';
import highlight from 'highlight.js';
export default defineComponent({
setup(props, { slots }) {
const html = ref('');
onMounted(generateHTML);
onUpdated(generateHTML);
return { html, onClick };
async function onClick($event: Event) {
if ($event.target instanceof HTMLElement && $event.target.classList.contains('copy')) {
await navigator.clipboard.writeText(
window.location.href.split('#')[0] + $event.target.getAttribute('href')
);
}
}
function generateHTML() {
if (slots.default === null || !slots.default()?.[0]?.text) {
html.value = '';
return;
}
let htmlString = slots.default()[0].text!;
const hintRegex = /<p>:::(.*?) (.*?)\r?\n((\s|.)*?):::<\/p>/gm;
const renderer: Partial<Renderer> = {
heading(text, level) {
const escapedText = text.toLowerCase().replace(/[^\w]+/g, '-');
return `
<h${level} id="${escapedText}">
<a class="heading-link copy" href="#${escapedText}">#</a>
${text}
</h${level}>`;
},
};
// Marked merges it's default rendered with our extension. It's typed as a full rendered however
marked.use({ renderer } as any);
htmlString = marked(htmlString, {
highlight: (code, lang) => {
return highlight.highlightAuto(code, [lang]).value;
},
});
htmlString = htmlString.replaceAll(
hintRegex,
(match: string, type: string, title: string, body: string) => {
return `<div class="hint ${type}"><p class="hint-title">${title}</p><p class="hint-body">${body}</p></div>`;
}
);
html.value = htmlString;
}
},
});
</script>
<style lang="scss" scoped>
.error {
padding: 20vh 0;
}
.md {
::v-deep {
font-weight: 400;
font-size: 16px;
line-height: 27px;
& > *:first-child {
margin-top: 0;
}
& > *:last-child {
margin-bottom: 0;
}
a {
color: var(--primary);
text-decoration: none;
}
h1,
h2,
h3,
h4,
h5,
h6 {
position: relative;
margin: 40px 0 8px;
padding: 0;
font-weight: 600;
cursor: text;
a {
position: absolute;
right: 100%;
padding-right: 4px;
opacity: 0;
&:hover {
text-decoration: underline;
}
}
&:hover a {
opacity: 1;
}
}
h1 {
margin-bottom: 40px;
font-size: 35px;
line-height: 44px;
}
h2 {
margin-top: 60px;
margin-bottom: 20px;
padding-bottom: 12px;
font-size: 26px;
line-height: 33px;
border-bottom: 2px solid var(--border-subdued);
}
h3 {
margin-bottom: 0px;
font-size: 19px;
line-height: 24px;
}
h4 {
font-size: 16px;
}
h5 {
font-size: 14px;
}
h6 {
color: var(--foreground-normal);
font-size: 14px;
}
.heading-link {
color: var(--foreground-subdued);
font-size: 16px;
&:hover {
color: var(--primary);
text-decoration: none;
}
}
pre {
padding: 16px 20px;
overflow: auto;
font-size: 13px;
line-height: 19px;
background-color: var(--background-page);
border: 1px solid var(--background-normal);
border-radius: var(--border-radius);
}
code,
tt {
margin: 0 1px;
padding: 0 4px;
font-size: 15px;
font-family: var(--family-monospace);
white-space: nowrap;
background-color: var(--background-page);
border: 1px solid var(--background-normal);
border-radius: var(--border-radius);
}
pre code {
margin: 0;
padding: 0;
white-space: pre;
background: transparent;
border: none;
}
pre code,
pre tt {
background-color: transparent;
border: none;
}
h1 tt,
h1 code {
font-size: inherit;
}
h2 tt,
h2 code {
font-size: inherit;
}
h3 tt,
h3 code {
font-size: inherit;
}
h4 tt,
h4 code {
font-size: inherit;
}
h5 tt,
h5 code {
font-size: inherit;
}
h6 tt,
h6 code {
font-size: inherit;
}
p,
blockquote,
ul,
ol,
dl,
li,
table,
pre {
margin: 8px 0;
}
p {
margin-block-start: 1em;
margin-block-end: 1em;
margin-inline-start: 0px;
margin-inline-end: 0px;
}
h3 + p {
margin-block-start: 0.5em;
}
& > h2:first-child {
margin-top: 0;
padding-top: 0;
}
& > h1:first-child {
margin-top: 0;
padding-top: 0;
}
& > h3:first-child,
& > h4:first-child,
& > h5:first-child,
& > h6:first-child {
margin-top: 0;
padding-top: 0;
}
a:first-child h1,
a:first-child h2,
a:first-child h3,
a:first-child h4,
a:first-child h5,
a:first-child h6 {
margin-top: 0;
padding-top: 0;
}
h1 p,
h2 p,
h3 p,
h4 p,
h5 p,
h6 p {
margin-top: 0;
}
li p.first {
display: inline-block;
}
ul,
ol {
margin: 20px 0;
padding-left: 20px;
li {
margin: 8px 0;
line-height: 24px;
}
ul,
ol {
margin: 4px 0;
li {
margin: 4px 0;
line-height: 24px;
}
}
}
blockquote {
padding: 0 20px;
color: var(--foreground-subdued);
font-size: 18px;
border-left: 2px solid var(--background-normal);
}
blockquote > :first-child {
margin-top: 0;
}
blockquote > :last-child {
margin-bottom: 0;
}
table {
min-width: 100%;
margin: 40px 0;
padding: 0;
border-collapse: collapse;
border-spacing: 0;
}
table tr {
margin: 0;
padding: 0;
background-color: white;
border-top: 1px solid var(--background-normal);
}
table tr:nth-child(2n) {
background-color: var(--background-page);
}
table tr th {
margin: 0;
padding: 8px 20px;
font-weight: bold;
text-align: left;
border: 1px solid var(--background-normal);
}
table tr td {
margin: 0;
padding: 8px 20px;
text-align: left;
border: 1px solid var(--background-normal);
}
table tr th :first-child,
table tr td :first-child {
margin-top: 0;
}
table tr th :last-child,
table tr td :last-child {
margin-bottom: 0;
}
img {
max-width: 100%;
margin: 20px 0;
&.no-margin {
margin: 0;
}
&.full {
width: 100%;
}
&.shadow {
box-shadow: 0px 5px 10px 0px rgba(23, 41, 64, 0.1), 0px 2px 40px 0px rgba(23, 41, 64, 0.05);
}
}
.highlight pre {
padding: 8px 20px;
overflow: auto;
font-size: 13px;
line-height: 19px;
background-color: var(--background-page);
border: 1px solid var(--background-normal);
border-radius: var(--border-radius);
}
hr {
margin: 40px auto;
border: none;
border-top: 2px solid var(--background-normal);
}
b,
strong {
font-weight: 600;
}
.hint {
display: inline-block;
width: 100%;
margin: 20px 0;
padding: 0 20px;
background-color: var(--background-subdued);
border-left: 2px solid var(--primary);
&-title {
margin-bottom: 0.5em;
font-weight: bold;
}
&-body {
margin-top: 0.5em;
}
&.tip {
border-left: 2px solid var(--success);
}
&.warning {
background-color: var(--warning-10);
border-left: 2px solid var(--warning);
}
&.danger {
background-color: var(--danger-10);
border-left: 2px solid var(--danger);
}
}
}
}
</style>

View File

@@ -1,20 +1,6 @@
<template>
<v-list-item v-if="section.children === undefined" :to="section.to" :dense="dense" :subdued="subdued">
<v-list-item-icon v-if="section.icon !== undefined"><v-icon :name="section.icon" /></v-list-item-icon>
<v-list-item-content>
<v-list-item-text>{{ section.name }}</v-list-item-text>
</v-list-item-content>
</v-list-item>
<div v-else-if="section.flat === true">
<v-divider></v-divider>
<navigation-list-item
v-for="(childSection, index) in section.children"
:key="index"
:section="childSection"
dense
/>
</div>
<v-list-group v-else>
<v-divider v-if="section.divider" />
<v-list-group v-else-if="section.children" :dense="dense">
<template #activator>
<v-list-item-icon v-if="section.icon !== undefined"><v-icon :name="section.icon" /></v-list-item-icon>
<v-list-item-content>
@@ -28,48 +14,30 @@
dense
/>
</v-list-group>
<v-list-item v-else :to="`/docs${section.to}`" :dense="dense">
<v-list-item-icon v-if="section.icon !== undefined"><v-icon :name="section.icon" /></v-list-item-icon>
<v-list-item-content>
<v-list-item-text>{{ section.name }}</v-list-item-text>
</v-list-item-content>
</v-list-item>
</template>
<script lang="ts">
import { defineComponent, PropType } from '@vue/composition-api';
import { Section } from './sections';
import { Link, Group } from '@directus/docs';
export default defineComponent({
name: 'navigation-list-item',
props: {
section: {
type: Object as PropType<Section>,
type: Object as PropType<Link | Group>,
default: null,
},
dense: {
type: Boolean,
default: false,
},
subdued: {
type: Boolean,
default: false,
},
},
});
</script>
<style lang="scss" scoped>
.version {
.v-icon {
color: var(--foreground-subdued);
transition: color var(--fast) var(--transition);
}
::v-deep .type-text {
color: var(--foreground-subdued);
transition: color var(--fast) var(--transition);
}
&:hover {
.v-icon {
color: var(--foreground-normal);
}
::v-deep .type-text {
color: var(--foreground-normal);
}
}
}
</style>

View File

@@ -1,18 +1,18 @@
<template>
<v-list large>
<navigation-item v-for="item in sections" :key="item.to" :section="item"></navigation-item>
<navigation-item v-for="item in navSections" :key="item.name" :section="item" />
</v-list>
</template>
<script lang="ts">
import { defineComponent } from '@vue/composition-api';
import { defineComponent, PropType, computed } from '@vue/composition-api';
import NavigationItem from './navigation-item.vue';
import sections from './sections';
import { nav } from '@directus/docs';
export default defineComponent({
components: { NavigationItem },
setup() {
return { sections };
return { navSections: nav.app };
},
});
</script>

View File

@@ -1,188 +0,0 @@
export type Section = {
name: string;
to: string;
description?: string;
icon?: string;
sectionIcon?: string;
sectionName?: string;
children?: Section[];
default?: string;
flat?: boolean;
};
export const defaultSection = '/docs/getting-started/introduction';
const sections: Section[] = [
{
icon: 'play_arrow',
name: 'Getting Started',
to: '/docs/getting-started',
default: '',
children: [
{
name: 'Introduction',
to: '/docs/getting-started/introduction',
},
{
name: 'Support & FAQ',
to: '/docs/getting-started/support',
},
{
name: 'Contributing',
to: '/docs/getting-started/contributing',
},
{
name: 'Backing Directus',
to: '/docs/getting-started/backing-directus',
},
],
},
{
icon: 'school',
name: 'Concepts',
to: '/docs/concepts',
default: 'readme',
children: [
{
name: 'Platform Overview',
to: '/docs/concepts/platform-overview',
},
{
name: 'App Overview',
to: '/docs/concepts/app-overview',
},
{
name: 'App Extensions',
to: '/docs/concepts/app-extensions',
},
{
name: 'Activity & Versions',
to: '/docs/concepts/activity-and-versions',
},
{
name: 'Files & Thumbnails',
to: '/docs/concepts/files-and-thumbnails',
},
{
name: 'Internationalization',
to: '/docs/concepts/internationalization',
},
{
name: 'Relationships',
to: '/docs/concepts/relationships',
},
{
name: 'Users, Roles & Permissions',
to: '/docs/concepts/users-roles-and-permissions',
},
],
},
{
icon: 'article',
name: 'Guides',
to: '/docs/guides',
default: 'readme',
children: [
{
name: 'Collections',
to: '/docs/guides/collections',
},
{
name: 'Fields',
to: '/docs/guides/fields',
},
{
name: 'Presets',
to: '/docs/guides/presets',
},
{
name: 'Projects',
to: '/docs/guides/projects',
},
{
name: 'Roles & Permissions',
to: '/docs/guides/roles-and-permissions',
},
{
name: 'Users',
to: '/docs/guides/users',
},
{
name: 'Webhooks',
to: '/docs/guides/webhooks',
},
{
name: 'White-Labeling',
to: '/docs/guides/white-labeling',
},
{
name: 'Extensions',
to: '/docs/guides/extensions',
children: [
{
name: 'Custom Displays',
to: '/docs/guides/extensions/creating-a-custom-display',
},
{
name: 'Custom Interfaces',
to: '/docs/guides/extensions/creating-a-custom-interface',
},
{
name: 'Custom Layouts',
to: '/docs/guides/extensions/creating-a-custom-layout',
},
{
name: 'Custom Modules',
to: '/docs/guides/extensions/creating-a-custom-module',
},
{
name: 'Custom API Endpoints',
to: '/docs/guides/extensions/creating-a-custom-api-endpoint',
},
{
name: 'Custom API Hooks',
to: '/docs/guides/extensions/creating-a-custom-api-hook',
},
{
name: 'Custom Email Templates',
to: '/docs/guides/extensions/creating-a-custom-email-template',
},
{
name: 'Custom Storage Adapters',
to: '/docs/guides/extensions/creating-a-custom-storage-adapter',
},
{
name: 'Accessing Data',
to: '/docs/guides/extensions/accessing-data',
},
],
},
],
},
{
icon: 'code',
name: 'Reference',
to: '/docs/reference',
default: 'readme',
children: [
{
name: 'Environment Variables',
to: '/docs/reference/environment-variables',
},
{
name: 'Command Line Interface',
to: '/docs/reference/command-line-interface',
},
{
name: 'Error Codes',
to: '/docs/reference/error-codes',
},
{
name: 'Item Rules',
to: '/docs/reference/item-rules',
},
],
},
];
export default sections;

View File

@@ -1,71 +1,49 @@
import { defineModule } from '@/modules/define';
import Docs from './routes/docs.vue';
import sections, { Section, defaultSection } from './components/sections';
import { Route } from 'vue-router';
import { cloneDeep } from 'lodash';
import { RouteConfig } from 'vue-router';
import { files, Directory } from '@directus/docs';
import StaticDocs from './routes/static.vue';
import NotFound from './routes/not-found.vue';
function urlSplitter(url: string) {
if (url.startsWith('/docs')) url = url.replace('/docs', '');
if (url.startsWith('/')) url = url.substr(1);
return url.split('/');
}
function urlToSection(urlSections: string[], sections: Section[]): Section | null {
let section = sections.find((s) => urlSplitter(s.to).pop() === urlSections[0]);
if (section === undefined) {
return null;
}
section = cloneDeep(section);
if (urlSections.length === 1) {
let finalSection = section;
while (finalSection.children !== undefined) {
finalSection = finalSection.children[0];
}
if (section.icon) finalSection.icon = section.icon;
if (finalSection.sectionName === undefined) finalSection.sectionName = section.name;
return finalSection;
}
if (section.children === undefined) return null;
const sectionDeep = urlToSection(urlSections.slice(1), section.children);
if (sectionDeep !== null && sectionDeep.sectionName === undefined) {
sectionDeep.sectionName = section.name;
}
if (
sectionDeep !== null &&
sectionDeep.icon === undefined &&
sectionDeep.sectionIcon === undefined &&
section.icon !== undefined
)
sectionDeep.sectionIcon = section.icon;
return sectionDeep;
}
function props(route: Route) {
const section = urlToSection(urlSplitter(route.path), sections);
return { section };
}
export default defineModule(({ i18n }) => ({
id: 'docs',
name: i18n.t('documentation'),
icon: 'info',
routes: [
export default defineModule(({ i18n }) => {
const routes: RouteConfig[] = [
{
path: '/',
component: StaticDocs,
},
...parseRoutes(files),
{
path: '/*',
beforeEnter: (to, from, next) => {
if (to.path === '/docs/') next(defaultSection);
else next();
},
component: Docs,
props: props,
component: NotFound,
},
],
order: 20,
}));
];
return {
id: 'docs',
name: i18n.t('documentation'),
icon: 'info',
routes,
order: 20,
};
function parseRoutes(directory: Directory): RouteConfig[] {
const routes: RouteConfig[] = [];
for (const doc of directory.children) {
if (doc.type === 'file') {
routes.push({
path: '/' + doc.path.replace('.md', ''),
component: StaticDocs,
});
} else if (doc.type === 'directory') {
routes.push({
path: '/' + doc.path,
redirect: '/' + doc.children![0].path.replace('.md', ''),
});
routes.push(...parseRoutes(doc));
}
}
return routes;
}
});

View File

@@ -1,95 +0,0 @@
<template>
<private-view :title="notFound ? $t('page_not_found') : title">
<template #headline>{{ $t('documentation') }}</template>
<template #title-outer:prepend>
<v-button rounded disabled icon>
<v-icon :name="isAPIReference ? 'code' : section.icon || section.sectionIcon" />
</v-button>
</template>
<template #navigation>
<docs-navigation />
</template>
<div v-if="notFound" class="not-found">
<v-info :title="$t('page_not_found')" icon="not_interested">
{{ $t('page_not_found_body') }}
</v-info>
</div>
<markdown v-else>{{ mdString }}</markdown>
</private-view>
</template>
<script lang="ts">
import { defineComponent, ref, computed, watch, PropType } from '@vue/composition-api';
import DocsNavigation from '../components/navigation.vue';
import { Section } from '../components/sections';
import Markdown from './markdown.vue';
import i18n from '@/lang';
declare module '*.md';
export default defineComponent({
components: { DocsNavigation, Markdown },
props: {
section: {
type: Object as PropType<Section>,
default: null,
},
},
setup(props) {
const mdString = ref<string | null>(null);
const loading = ref(true);
const error = ref(null);
const isAPIReference = computed(() => props.section && props.section.to.startsWith('/docs/api-reference'));
const notFound = computed(() => {
return props.section === null || error.value !== null;
});
const title = computed(() => {
return isAPIReference.value ? i18n.t('api_reference') : props.section.sectionName;
});
watch(() => props.section, loadMD, { immediate: true });
return { isAPIReference, notFound, title, mdString };
async function loadMD() {
loading.value = true;
error.value = null;
if (props.section === null) {
mdString.value = null;
return;
}
const loadString = props.section.to.replace('/docs', '');
try {
const md = await import(`raw-loader!@directus/docs${loadString}.md`);
mdString.value = md.default;
} catch (err) {
mdString.value = null;
error.value = err;
} finally {
loading.value = false;
}
}
},
});
</script>
<style lang="scss" scoped>
.v-info {
padding: 20vh 0;
}
.not-found {
display: flex;
align-items: center;
justify-content: center;
padding: 20vh 0;
}
</style>

View File

@@ -1,407 +0,0 @@
<template>
<div class="docs selectable">
<div class="md" v-html="html" />
</div>
</template>
<script lang="ts">
import { defineComponent, ref, computed, watch, PropType, onMounted, onUpdated } from '@vue/composition-api';
import marked from 'marked';
import highlight from 'highlight.js';
import 'highlight.js/styles/github.css'
export default defineComponent({
setup(props, { slots }) {
const html = ref('');
onMounted(generateHTML);
onUpdated(generateHTML);
return { html };
function generateHTML() {
if (slots.default === null || !slots.default()?.[0]?.text) {
html.value = '';
return;
}
let htmlString = slots.default()[0].text!;
const hintRegex = /<p>:::(.*?) (.*?)\r?\n((\s|.)*?):::<\/p>/gm;
htmlString = marked(htmlString, {
highlight: (code, lang) => {
return highlight.highlightAuto(code, [lang]).value
},
});
htmlString = htmlString.replaceAll(
hintRegex,
(match: string, type: string, title: string, body: string) => {
return `<div class="hint ${type}"><p class="hint-title">${title}</p><p class="hint-body">${body}</p></div>`;
}
);
html.value = htmlString;
}
},
});
</script>
<style lang="scss" scoped>
.error {
padding: 20vh 0;
}
.docs {
padding: 0 var(--content-padding) var(--content-padding-bottom);
.md {
max-width: 740px;
::v-deep {
font-weight: 400;
font-size: 16px;
line-height: 27px;
& > *:first-child {
margin-top: 0;
}
& > *:last-child {
margin-bottom: 0;
}
a {
color: var(--primary);
text-decoration: none;
}
h1,
h2,
h3,
h4,
h5,
h6 {
position: relative;
margin: 40px 0 8px;
padding: 0;
font-weight: 600;
cursor: text;
}
h1 {
margin-bottom: 40px;
font-size: 35px;
line-height: 44px;
}
h2 {
margin-top: 60px;
margin-bottom: 20px;
padding-bottom: 12px;
font-size: 26px;
line-height: 33px;
border-bottom: 2px solid var(--border-subdued);
}
h3 {
margin-bottom: 0px;
font-size: 19px;
line-height: 24px;
}
h4 {
font-size: 16px;
}
h5 {
font-size: 14px;
}
h6 {
color: var(--foreground-normal);
font-size: 14px;
}
pre {
padding: 16px 20px;
overflow: auto;
font-size: 13px;
line-height: 19px;
background-color: var(--background-page);
border: 1px solid var(--background-normal);
border-radius: var(--border-radius);
}
code,
tt {
margin: 0 1px;
padding: 0 4px;
font-family: var(--family-monospace);
font-size: 15px;
white-space: nowrap;
background-color: var(--background-page);
border: 1px solid var(--background-normal);
border-radius: var(--border-radius);
}
pre code {
margin: 0;
padding: 0;
white-space: pre;
background: transparent;
border: none;
}
pre code,
pre tt {
background-color: transparent;
border: none;
}
h1 tt,
h1 code {
font-size: inherit;
}
h2 tt,
h2 code {
font-size: inherit;
}
h3 tt,
h3 code {
font-size: inherit;
}
h4 tt,
h4 code {
font-size: inherit;
}
h5 tt,
h5 code {
font-size: inherit;
}
h6 tt,
h6 code {
font-size: inherit;
}
p,
blockquote,
ul,
ol,
dl,
li,
table,
pre {
margin: 8px 0;
}
p {
margin-block-start: 1em;
margin-block-end: 1em;
margin-inline-start: 0px;
margin-inline-end: 0px;
}
h3 + p {
margin-block-start: 0.5em;
}
& > h2:first-child {
margin-top: 0;
padding-top: 0;
}
& > h1:first-child {
margin-top: 0;
padding-top: 0;
}
& > h3:first-child,
& > h4:first-child,
& > h5:first-child,
& > h6:first-child {
margin-top: 0;
padding-top: 0;
}
a:first-child h1,
a:first-child h2,
a:first-child h3,
a:first-child h4,
a:first-child h5,
a:first-child h6 {
margin-top: 0;
padding-top: 0;
}
h1 p,
h2 p,
h3 p,
h4 p,
h5 p,
h6 p {
margin-top: 0;
}
li p.first {
display: inline-block;
}
ul,
ol {
margin: 20px 0;
padding-left: 20px;
li {
margin: 8px 0;
line-height: 24px;
}
ul,
ol {
margin: 4px 0;
li {
margin: 4px 0;
line-height: 24px;
}
}
}
blockquote {
font-size: 18px;
padding: 0 20px;
color: var(--foreground-subdued);
border-left: 2px solid var(--background-normal);
}
blockquote > :first-child {
margin-top: 0;
}
blockquote > :last-child {
margin-bottom: 0;
}
table {
min-width: 100%;
margin: 40px 0;
padding: 0;
border-collapse: collapse;
border-spacing: 0;
}
table tr {
margin: 0;
padding: 0;
background-color: white;
border-top: 1px solid var(--background-normal);
}
table tr:nth-child(2n) {
background-color: var(--background-page);
}
table tr th {
margin: 0;
padding: 8px 20px;
font-weight: bold;
text-align: left;
border: 1px solid var(--background-normal);
}
table tr td {
margin: 0;
padding: 8px 20px;
text-align: left;
border: 1px solid var(--background-normal);
}
table tr th :first-child,
table tr td :first-child {
margin-top: 0;
}
table tr th :last-child,
table tr td :last-child {
margin-bottom: 0;
}
img {
max-width: 100%;
margin: 20px 0;
&.no-margin {
margin: 0;
}
&.full {
width: 100%;
}
&.shadow {
box-shadow: 0px 5px 10px 0px rgba(23,41,64,0.1),
0px 2px 40px 0px rgba(23,41,64,0.05);
}
}
.highlight pre {
padding: 8px 20px;
overflow: auto;
font-size: 13px;
line-height: 19px;
background-color: var(--background-page);
border: 1px solid var(--background-normal);
border-radius: var(--border-radius);
}
hr {
margin: 40px auto;
border: none;
border-top: 2px solid var(--background-normal);
}
b,
strong {
font-weight: 600;
}
.hint {
display: inline-block;
margin: 20px 0;
padding: 0 20px;
background-color: var(--background-subdued);
border-left: 2px solid var(--primary);
width: 100%;
&-title {
font-weight: bold;
margin-bottom: 0.5em;
}
&-body {
margin-top: 0.5em;
}
&.tip {
border-left: 2px solid var(--success);
}
&.warning {
background-color: var(--warning-10);
border-left: 2px solid var(--warning);
}
&.danger {
background-color: var(--danger-10);
border-left: 2px solid var(--danger);
}
}
}
}
}
</style>

View File

@@ -0,0 +1,32 @@
<template>
<private-view :title="$t('page_not_found')">
<template #navigation>
<docs-navigation />
</template>
<div class="not-found">
<v-info :title="$t('page_not_found')" icon="not_interested">
{{ $t('page_not_found_body') }}
</v-info>
</div>
</private-view>
</template>
<script lang="ts">
import { defineComponent } from '@vue/composition-api';
import DocsNavigation from '../components/navigation.vue';
export default defineComponent({
name: 'NotFound',
components: { DocsNavigation },
});
</script>
<style lang="scss" scoped>
.not-found {
display: flex;
align-items: center;
justify-content: center;
padding: 20vh 0;
}
</style>

View File

@@ -0,0 +1,96 @@
<template>
<private-view :title="title" ref="view">
<template #headline>Documentation</template>
<template #title-outer:prepend>
<v-button rounded disabled icon>
<v-icon name="info" />
</v-button>
</template>
<template #navigation>
<docs-navigation />
</template>
<div class="docs-content selectable">
<markdown>{{ markdownWithoutTitle }}</markdown>
</div>
<template #drawer>
<drawer-detail icon="info_outline" :title="$t('information')" close>
<div class="page-description" v-html="marked($t('page_help_docs_global'))" />
</drawer-detail>
</template>
</private-view>
</template>
<script lang="ts">
import { defineComponent, ref, computed, inject, onUpdated } from '@vue/composition-api';
import DocsNavigation from '../components/navigation.vue';
import Markdown from '../components/markdown.vue';
import marked from 'marked';
async function getMarkdownForPath(path: string) {
const pathParts = path.split('/');
while (pathParts.includes('docs')) {
pathParts.shift();
}
let docsPath = pathParts.join('/');
// Home
if (!docsPath) {
docsPath = 'readme';
}
const mdModule = await import('raw-loader!@directus/docs/' + docsPath + '.md');
return mdModule.default;
}
export default defineComponent({
name: 'StaticDocs',
components: { DocsNavigation, Markdown },
async beforeRouteEnter(to, from, next) {
const md = await getMarkdownForPath(to.path);
next((vm) => {
(vm as any).markdown = md;
});
},
async beforeRouteUpdate(to, from, next) {
this.markdown = await getMarkdownForPath(to.path);
next();
},
setup() {
const markdown = ref('');
const view = ref<Vue>();
const title = computed(() => {
const firstLine = markdown.value.split('\n').shift();
return firstLine?.substring(2).trim();
});
const markdownWithoutTitle = computed(() => {
const lines = markdown.value.split('\n');
lines.shift();
return lines.join('\n');
});
onUpdated(() => {
view.value?.$data.contentEl?.scrollTo({ top: 0 });
});
return { markdown, title, markdownWithoutTitle, view, marked };
},
});
</script>
<style lang="scss" scoped>
.docs-content {
max-width: 740px;
padding: 0 var(--content-padding) var(--content-padding-bottom);
}
</style>

View File

@@ -65,13 +65,13 @@ export default defineComponent({
{
icon: 'bug_report',
name: i18n.t('report_bug'),
href: 'https://github.com/directus/directus/issues/new/choose',
href: 'https://github.com/directus/next/issues/new?body=%23%23%23+Project+Details%0A%60%60%60%0ADirectus+Version:+'+version+'%0AEnvironment:+Development%0AOS:+Mac%0ADatabase:+MySQL+5.2%0A%60%60%60',
outline: true,
},
{
icon: 'new_releases',
name: i18n.t('request_feature'),
href: 'https://github.com/directus/directus/discussions/new',
href: 'https://github.com/directus/next/discussions/new',
outline: true,
},
];

View File

@@ -291,21 +291,21 @@ export default defineComponent({
}
await Promise.all(
state.newCollections.map((newCollection: Partial<Collection> & { $type: string }) => {
state.newCollections.map((newCollection: Partial<Collection> & { $type?: string }) => {
delete newCollection.$type;
return api.post(`/collections`, newCollection);
})
);
await Promise.all(
state.newFields.map((newField: Partial<Field> & { $type: string }) => {
state.newFields.map((newField: Partial<Field> & { $type?: string }) => {
delete newField.$type;
return api.post(`/fields/${newField.collection}`, newField);
})
);
await Promise.all(
state.updateFields.map((updateField: Partial<Field> & { $type: string }) => {
state.updateFields.map((updateField: Partial<Field> & { $type?: string }) => {
delete updateField.$type;
return api.post(`/fields/${updateField.collection}/${updateField.field}`, updateField);
})

View File

@@ -298,7 +298,7 @@ export default defineComponent({
};
async function saveDuplicate() {
const newField = {
const newField: any = {
...props.field,
field: duplicateName.value,
collection: duplicateTo.value,

View File

@@ -26,19 +26,25 @@
<v-tabs-items v-model="currentTab">
<v-tab-item value="collection">
<h2 class="type-title">{{ $t('creating_collection_info') }}</h2>
<div class="type-label">
{{ $t('name') }}
<v-icon class="required" v-tooltip="$t('required')" name="star" sup />
</div>
<v-input
autofocus
class="monospace"
v-model="collectionName"
db-safe
:placeholder="$t('a_unique_table_name')"
/>
<v-divider />
<div class="grid">
<div>
<div class="type-label">
{{ $t('name') }}
<v-icon class="required" v-tooltip="$t('required')" name="star" sup />
</div>
<v-input
autofocus
class="monospace"
v-model="collectionName"
db-safe
:placeholder="$t('a_unique_table_name')"
/>
</div>
<div>
<div class="type-label">{{ $t('singleton') }}</div>
<v-checkbox block :label="$t('singleton_label')" v-model="singleton" />
</div>
<v-divider class="full" />
<div>
<div class="type-label">{{ $t('primary_key_field') }}</div>
<v-input
@@ -73,7 +79,7 @@
<v-tab-item value="system">
<h2 class="type-title">{{ $t('creating_collection_system') }}</h2>
<div class="grid system">
<div class="field" v-for="(info, field) in systemFields" :key="field">
<div v-for="(info, field) in systemFields" :key="field">
<div class="type-label">{{ $t(info.label) }}</div>
<v-input v-model="info.name" class="monospace" :class="{active: info.enabled}" @click.native="info.enabled = true">
<template #prepend>
@@ -124,6 +130,7 @@ export default defineComponent({
const currentTab = ref(['collection']);
const collectionName = ref(null);
const singleton = ref(false);
const primaryKeyFieldName = ref('id');
const primaryKeyFieldType = ref<'auto_int' | 'uuid' | 'manual'>('auto_int');
@@ -184,6 +191,7 @@ export default defineComponent({
collectionName,
saveError,
saving,
singleton
};
async function save() {
@@ -198,6 +206,7 @@ export default defineComponent({
archive_field: archiveField.value,
archive_value: archiveValue.value,
unarchive_value: unarchiveValue.value,
singleton: singleton.value
},
});
@@ -412,22 +421,14 @@ export default defineComponent({
</script>
<style lang="scss" scoped>
@import '@/styles/mixins/form-grid';
.type-title {
margin-bottom: 48px;
}
.type-label {
margin-bottom: 12px;
}
.v-divider {
margin: 48px 0;
}
.grid {
display: grid;
grid-gap: 48px 36px;
grid-template-columns: repeat(2, 1fr);
@include form-grid;
}
.system {

View File

@@ -106,7 +106,7 @@ import PresetsInfoDrawerDetail from './components/presets-info-drawer-detail.vue
type PresetRaw = {
id: number;
title: null | string;
bookmark: null | string;
user: null | { first_name: string; last_name: string };
role: null | { name: string };
collection: string;
@@ -184,7 +184,7 @@ export default defineComponent({
scope: scope,
collection: collection,
layout: layout,
name: preset.title,
name: preset.bookmark,
} as Preset;
});
});
@@ -199,7 +199,7 @@ export default defineComponent({
params: {
fields: [
'id',
'title',
'bookmark',
'user.first_name',
'user.last_name',
'role.name',

View File

@@ -40,7 +40,7 @@ export default defineComponent({
try {
const response = await api.get(`/presets`, {
params: {
[`filter[title][nnull]`]: 1,
[`filter[bookmark][_nnull]`]: 1,
fields: ['id'],
meta: 'filter_count,total_count',
},

View File

@@ -4,16 +4,43 @@ const { promisify } = require('util');
const copyfiles = promisify(require('copyfiles'));
const rimraf = promisify(require('rimraf'));
const dirTree = require('directory-tree');
const yaml = require('js-yaml');
async function build() {
console.log('Building docs...');
const start = Date.now();
const distPath = path.resolve(__dirname, './dist');
await rimraf(distPath);
const tree = dirTree('.', { extensions: /\.md/, exclude: /dist/ });
await fse.ensureDir(distPath);
const tree = dirTree('.', { extensions: /\.md/ });
await fse.writeFile('./dist/index.json', JSON.stringify(tree, null, '\t'));
await fse.writeJSON('./dist/index.json', tree);
await copyfiles(['./**/*.md', distPath]);
const yamlFiles = [];
const filesInRoot = await fse.readdir(__dirname);
for (const file of filesInRoot) {
if (file.endsWith('.yaml')) {
yamlFiles.push(file);
}
}
for (const yamlFile of yamlFiles) {
const yamlString = await fse.readFile(yamlFile, 'utf8');
await fse.writeJSON(
'./dist/' + yamlFile.replace('.yaml', '.json'),
yaml.safeLoad(yamlString)
);
}
console.log(`Built docs in ${Date.now() - start} ms`);
}
build();

View File

@@ -9,7 +9,7 @@
### 1. Module Bar
* **Project Logo** — Displays your configured project logo and project color (defaults to the Directus logo and color). If configured, clicking this component will navigate to the Project URL. During platform activity, an indeterminate progress indicator will also be shown here.
* **Project Logo** — Displays your configured project logo and project color (defaults to the Directus logo and color). If [configured](#), clicking this component will navigate to the Project URL. During platform activity, an indeterminate progress indicator will also be shown here.
* **Modules** — Any available [modules](#) are listed below the project logo. These may be turned off or reordered based on your [role's configuration](#), but by default this includes:
* [Collections](#)
* [User Directory](#)

View File

@@ -76,18 +76,21 @@ git checkout -b YOUR-BRANCH-NAME
```bash
npm install
npm run setup
```
### 5. Setup the Database & Env File
For this step, you'll need to already have a SQL database up-and-running, otherwise you can only use the SQLite driver, which will create the database for you.
For this step, you'll need to already have a SQL database up-and-running, otherwise you can only use the SQLite driver, which will create the database for you. Run the following command from within the `/api` directory.
```bash
@TODO
./cli.js init
```
### 6. Start the development server
Run the following command from the root directory.
```bash
npm run dev
```

View File

@@ -12,11 +12,13 @@ Our [Discord](https://discord.gg/directus) community is another great way to get
Premium support is included with our Enterprise Cloud service. On-Demand Cloud customers and On-Premise users interested in learning more about our monthly retainer agreements should contact us at [support@directus.io](mailto:support@directus.io).
## Commissioned Features
## Sponsored Work
### Commissioned Features
If you need a specific feature added to Directus faster than the normal development timeline, [reach out to us](#) for a quote. Our parent agency will often help subsidize the cost of developing new features if they pass our [80/20 Rule](#) and can be merged into the core codebase. Other custom/proprietary development will be built bespoke within our robust extension system.
## Expedited Fixes
### Expedited Fixes
We triage all reported bugs based on priority and how long the fix is estimated to take. If you need a specific issue resolved sooner, [reach out to us](#) for a quote.

View File

@@ -11,11 +11,11 @@ Directus currently supports the following databases, with our minimum version be
| Database | Version |
| ------------- | ------- |
| PostgreSQL | 9.5+ |
| MySQL | 5.6 |
| SQLite | 3 |
| MySQL | 5.6+ |
| SQLite | 3+ |
| MS-SQL Server | 13.0+ |
| OracleDB | TBD |
| MariaDB | 10.1 |
| MariaDB | 10.1+ |
::: Variants
In addition to the databases above, other variants are also supported, including **AWS Aurora** (MySQL), and **AWS Redshift** (PostgreSQL).

View File

@@ -1,6 +1,6 @@
# Webhooks
>
> In addition to writing custom code for more complex [event hooks](#), Directus provides a way to quickly configure webhooks through the App. These send HTTP requests when a specific event is triggered within the project.
## Creating Webhooks

View File

@@ -1,13 +1,65 @@
# White-Labeling a Project
> TK
> The Directus App UX/UI is meant to be _transparent_, and uses "form follows function" as the guiding design principle. This allows the platform to be completely tailored to your branding, end-to-end.
## Project Settings
FavIcon
1. Navigate to **Settings > Project Settings**
2. Configure any of the following **branding fields**
* **Project Name** — The name used at the top of the [Navigation Bar](#) and on the login/public pages
* **Project URL** — The URL when clicking the logo at the top of the [Module Bar](#)
* **Project Color** — The color used behind the logo at the top of the [Module Bar](#), on the login/public pages, and for the browser's FavIcon
* **Project Logo** — A 40x40 pixel logo at the top of the [Module Bar](#) and on the login/public pages
::: Recommended Logo Styling
The 40x40 pixel Project Logo is inset within the 64x64 pixel Project Color square. To avoid a "boxy" look, we recommend using a SVG or PNG logo with transparency.
:::
::: Browser FavIcon & Title
The project color and logo are also used to set the dynamic favicon, and the project title is used in the browser's title. This furthers the bespoke appearance of your platform and makes it easier to differentiate between different Directus projects.
:::
### Public Page Styling
In addition to the above options, you can also apply the following additional styling to your prject's [public pages](#).
* **Public Foreground** — An image shown on the right-side pane of public pages; max 400px width
* **Public Background** — An image displayed behind the above foreground image, shown full-bleed within the right-side pane of public pages
* **Public Note** — A helpful note displayed at the bottom of the right-side pane of public pages; supports markdown for rich-text formatting
::: Default Background Color
When a Public Background image is not set, the right-side pane of public pages uses the Project Color instead.
:::
## Themes & Custom CSS
The Directus App has been developed with customization and extensibility in mind. Colors and styles referenced within the codebase all use CSS variables, and therefore it is easy to make comprehensive changes to the App styling.
* **Themes** — See the [Light Theme](https://github.com/directus/next/blob/main/app/src/styles/themes/_light.scss) or [Dark Theme](https://github.com/directus/next/blob/main/app/src/styles/themes/_dark.scss)
* **Typography** — See the [Fonts](https://github.com/directus/next/blob/main/app/src/styles/_type-styles.scss) and [Type Styles](https://github.com/directus/next/blob/main/app/src/styles/mixins/type-styles.scss)
* **Variables** — See the [Global Variables](https://github.com/directus/next/blob/main/app/src/styles/_variables.scss)
You can override any core CSS, including the above variables, directly within the App through project Settings.
1. Navigate to **Settings > Project Settings**
2. Scroll to the **CSS Overrides** field
3. Enter any **valid CSS**
4. Click the **Save** action button in the header
::: Action Styling
The `--primary` variable (and its shades) control call-to-actions and all other "Directus blue" elements within the App. While it may be tempting to override this variable with your brand's color, please first review the following warnings:
* Avoid using yellow, orange, or red hues that give a sense of "danger"
* Avoid low-contrast colors like yellows, grays, etc, that might not be easily visible
* Avoid low-saturation colors like black, which might not properly highlight CTAs
:::
## API Reference
In addition to the static core docs, Directus also includes a [Dynamic API Reference](#) based on your project's schema. This includes tailored endpoint info for each collection within your data model, customizing the documentation to your specific project.
## System Table Prefix
Most white-labeling takes place in the presentation layer of the platform's App, but in some cases even the API and/or database needs to be a bit more agnostic. For that reason, Directus allows changing the prefix used by system tables. By default this is set to use `directus_*` to avoid any potential conflicts, but you can override this within the [Environment Variables](#).

37
docs/index.d.ts vendored Normal file
View File

@@ -0,0 +1,37 @@
export type File = {
type: 'file';
size: number;
path: string;
name: string;
extension: string;
};
export type Directory = {
type: 'directory';
size: number;
path: string;
name: string;
children: (Directory | File)[];
};
export type Link = {
name: string;
to: string;
};
export type Group = {
name: string;
children: (Group | Link | Divider)[];
icon?: string;
};
export type Divider = {
divider: true;
};
export const files: Directory;
export const nav: {
app: (Group | Link | Divider)[];
web: (Group | Link | Divider)[];
};

11
docs/index.js Normal file
View File

@@ -0,0 +1,11 @@
const appNav = require('./dist/nav-app.json');
const webNav = require('./dist/nav-web.json');
const index = require('./dist/index.json');
module.exports = {
files: index,
nav: {
app: appNav,
web: webNav,
},
};

86
docs/nav-app.yaml Normal file
View File

@@ -0,0 +1,86 @@
- name: Getting Started
icon: play_arrow
children:
- name: Introduction
to: "/getting-started/introduction"
- name: Support & FAQ
to: "/getting-started/support"
- name: Contributing
to: "/getting-started/contributing"
- name: Backing Directus
to: "/getting-started/backing-directus"
- name: Concepts
icon: school
children:
- name: Platform Overview
to: "/concepts/platform-overview"
- name: App Overview
to: "/concepts/app-overview"
- name: App Extensions
to: "/concepts/app-extensions"
- name: Activity & Versions
to: "/concepts/activity-and-versions"
- name: Files & Thumbnails
to: "/concepts/files-and-thumbnails"
- name: Internationalization
to: "/concepts/internationalization"
- name: Relationships
to: "/concepts/relationships"
- name: Users, Roles & Permissions
to: "/concepts/users-roles-and-permissions"
- name: Guides
icon: article
children:
- name: Collections
to: "/guides/collections"
- name: Fields
to: "/guides/fields"
- name: Presets
to: "/guides/presets"
- name: Projects
to: "/guides/projects"
- name: Roles & Permissions
to: "/guides/roles-and-permissions"
- name: Users
to: "/guides/users"
- name: Webhooks
to: "/guides/webhooks"
- name: White-Labeling
to: "/guides/white-labeling"
- name: Extensions
to: "/guides/extensions"
children:
- name: Displays
to: "/guides/extensions/displays"
- name: Interfaces
to: "/guides/extensions/interfaces"
- name: Layouts
to: "/guides/extensions/layouts"
- name: Modules
to: "/guides/extensions/modules"
- name: API Endpoints
to: "/guides/extensions/api-endpoints"
- name: API Hooks
to: "/guides/extensions/api-hooks"
- name: Email Templates
to: "/guides/extensions/email-templates"
- name: Storage Adapters
to: "/guides/extensions/storage-adapters"
- name: Accessing Data
to: "/guides/extensions/accessing-data"
- name: Reference
icon: code
children:
- name: Command Line Interface
to: "/reference/command-line-interface"
- name: Environment Variables
to: "/reference/environment-variables"
- name: Error Codes
to: "/reference/error-codes"
- name: Filter Rules
to: "/reference/filter-rules"
- name: Item Objects
to: "/reference/item-objects"

84
docs/nav-web.yaml Normal file
View File

@@ -0,0 +1,84 @@
- name: Getting Started
emoji: 🐰
children:
- name: Introduction
to: "/getting-started/introduction"
- name: Support & FAQ
to: "/getting-started/support"
- name: Contributing
to: "/getting-started/contributing"
- name: Backing Directus
to: "/getting-started/backing-directus"
- name: Concepts
emoji: 🎓
children:
- name: Platform Overview
to: "/concepts/platform-overview"
- name: App Overview
to: "/concepts/app-overview"
- name: App Extensions
to: "/concepts/app-extensions"
- name: Activity & Versions
to: "/concepts/activity-and-versions"
- name: Files & Thumbnails
to: "/concepts/files-and-thumbnails"
- name: Internationalization
to: "/concepts/internationalization"
- name: Relationships
to: "/concepts/relationships"
- name: Users, Roles & Permissions
to: "/concepts/users-roles-and-permissions"
- name: Guides
emoji: 📖
children:
- name: Collections
to: "/guides/collections"
- name: Fields
to: "/guides/fields"
- name: Presets
to: "/guides/presets"
- name: Projects
to: "/guides/projects"
- name: Roles & Permissions
to: "/guides/roles-and-permissions"
- name: Users
to: "/guides/users"
- name: Webhooks
to: "/guides/webhooks"
- name: White-Labeling
to: "/guides/white-labeling"
- name: Extensions
to: "/guides/extensions"
children:
- name: Custom Displays
to: "/guides/extensions/creating-a-custom-display"
- name: Custom Interfaces
to: "/guides/extensions/creating-a-custom-interface"
- name: Custom Layouts
to: "/guides/extensions/creating-a-custom-layout"
- name: Custom Modules
to: "/guides/extensions/creating-a-custom-module"
- name: Custom API Endpoints
to: "/guides/extensions/creating-a-custom-api-endpoint"
- name: Custom API Hooks
to: "/guides/extensions/creating-a-custom-api-hook"
- name: Custom Email Templates
to: "/guides/extensions/creating-a-custom-email-template"
- name: Custom Storage Adapters
to: "/guides/extensions/creating-a-custom-storage-adapter"
- name: Accessing Data
to: "/guides/extensions/accessing-data"
- name: Reference
emoji: 🚀
children:
- name: Environment Variables
to: "/reference/environment-variables"
- name: Command Line Interface
to: "/reference/command-line-interface"
- name: Error Codes
to: "/reference/error-codes"
- name: Item Rules
to: "/reference/item-rules"

1082
docs/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,12 +1,22 @@
{
"name": "@directus/docs",
"private": false,
"version": "9.0.0-beta.9",
"version": "9.0.0-beta.10",
"description": "",
"main": "dist/index.json",
"main": "index.js",
"scripts": {
"build": "node build.js",
"prepublish": "npm run build"
"prepublish": "npm run build",
"dev": "npm-watch build"
},
"watch": {
"build": {
"patterns": ["."],
"ignore": "dist",
"extensions": "md,yaml",
"silent": true,
"quiet": true
}
},
"files": [
"dist"
@@ -14,5 +24,8 @@
"keywords": [],
"author": "",
"license": "ISC",
"gitHead": "4476da28dbbc2824e680137aa28b2b91b5afabec"
"gitHead": "4476da28dbbc2824e680137aa28b2b91b5afabec",
"devDependencies": {
"npm-watch": "^0.7.0"
}
}

View File

@@ -5,7 +5,7 @@
"docs",
"packages/*"
],
"version": "9.0.0-beta.9",
"version": "9.0.0-beta.10",
"command": {
"bootstrap": {
"npmClientArgs": [

13
package-lock.json generated
View File

@@ -1453,7 +1453,6 @@
"mitt": "^2.1.0",
"mousetrap": "^1.6.5",
"nanoid": "^3.1.10",
"openapi3-ts": "^1.4.0",
"pinia": "^0.0.7",
"portal-vue": "^2.1.7",
"pretty-ms": "^7.0.0",
@@ -26348,9 +26347,12 @@
}
},
"openapi3-ts": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/openapi3-ts/-/openapi3-ts-1.4.0.tgz",
"integrity": "sha512-8DmE2oKayvSkIR3XSZ4+pRliBsx19bSNeIzkTPswY8r4wvjX86bMxsORdqwAwMxE8PefOcSAT2auvi/0TZe9yA=="
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/openapi3-ts/-/openapi3-ts-2.0.0.tgz",
"integrity": "sha512-q4p8OX/mD7qXeDKkhdLhpEz1Zh/IxPBDWmuq7f07fQJpo7exUW20sMrHfws1xzihYPktTXVV5MDOZkG/1uguEg==",
"requires": {
"yaml": "^1.10.0"
}
},
"opencollective-postinstall": {
"version": "2.0.3",
@@ -37444,8 +37446,7 @@
"yaml": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.0.tgz",
"integrity": "sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg==",
"dev": true
"integrity": "sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg=="
},
"yargs": {
"version": "13.3.2",

View File

@@ -3,7 +3,8 @@
"private": true,
"scripts": {
"dev": "lerna run dev --stream --parallel",
"release": "lerna publish --force-publish"
"release": "lerna publish --force-publish",
"setup": "lerna exec npm install && lerna run build"
},
"repository": {
"type": "git",

View File

@@ -0,0 +1,383 @@
{
"name": "create-directus-project",
"version": "9.0.0-beta.10",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"ansi-regex": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
"integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg=="
},
"ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"requires": {
"color-convert": "^2.0.1"
}
},
"at-least-node": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz",
"integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg=="
},
"chalk": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
"integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
"requires": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
}
},
"cli-cursor": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz",
"integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==",
"requires": {
"restore-cursor": "^3.1.0"
}
},
"cli-spinners": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.4.0.tgz",
"integrity": "sha512-sJAofoarcm76ZGpuooaO0eDy8saEy+YoZBLjC4h8srt4jeBnkYeOgqxgsJQTpyt2LjI5PTfLJHSL+41Yu4fEJA=="
},
"clone": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz",
"integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4="
},
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"requires": {
"color-name": "~1.1.4"
}
},
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
},
"commander": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-6.1.0.tgz",
"integrity": "sha512-wl7PNrYWd2y5mp1OK/LhTlv8Ff4kQJQRXXAvF+uU/TPNiVJUxZLRYGj/B0y/lPGAVcSbJqH2Za/cvHmrPMC8mA=="
},
"cross-spawn": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
"integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
"requires": {
"path-key": "^3.1.0",
"shebang-command": "^2.0.0",
"which": "^2.0.1"
}
},
"defaults": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz",
"integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=",
"requires": {
"clone": "^1.0.2"
}
},
"end-of-stream": {
"version": "1.4.4",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
"integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
"requires": {
"once": "^1.4.0"
}
},
"escape-string-regexp": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
},
"execa": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/execa/-/execa-4.0.3.tgz",
"integrity": "sha512-WFDXGHckXPWZX19t1kCsXzOpqX9LWYNqn4C+HqZlk/V0imTkzJZqf87ZBhvpHaftERYknpk0fjSylnXVlVgI0A==",
"requires": {
"cross-spawn": "^7.0.0",
"get-stream": "^5.0.0",
"human-signals": "^1.1.1",
"is-stream": "^2.0.0",
"merge-stream": "^2.0.0",
"npm-run-path": "^4.0.0",
"onetime": "^5.1.0",
"signal-exit": "^3.0.2",
"strip-final-newline": "^2.0.0"
}
},
"fs-extra": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.1.tgz",
"integrity": "sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ==",
"requires": {
"at-least-node": "^1.0.0",
"graceful-fs": "^4.2.0",
"jsonfile": "^6.0.1",
"universalify": "^1.0.0"
}
},
"get-stream": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz",
"integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==",
"requires": {
"pump": "^3.0.0"
}
},
"graceful-fs": {
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz",
"integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw=="
},
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="
},
"human-signals": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz",
"integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw=="
},
"is-interactive": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz",
"integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w=="
},
"is-stream": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz",
"integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw=="
},
"isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA="
},
"jsonfile": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.0.1.tgz",
"integrity": "sha512-jR2b5v7d2vIOust+w3wtFKZIfpC2pnRmFAhAC/BuweZFQR8qZzxH1OyrQ10HmdVYiXWkYUqPVsz91cG7EL2FBg==",
"requires": {
"graceful-fs": "^4.1.6",
"universalify": "^1.0.0"
}
},
"log-symbols": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz",
"integrity": "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==",
"requires": {
"chalk": "^2.4.2"
},
"dependencies": {
"ansi-styles": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"requires": {
"color-convert": "^1.9.0"
}
},
"chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"requires": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
"supports-color": "^5.3.0"
}
},
"color-convert": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
"requires": {
"color-name": "1.1.3"
}
},
"color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
},
"has-flag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0="
},
"supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"requires": {
"has-flag": "^3.0.0"
}
}
}
},
"merge-stream": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
"integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="
},
"mimic-fn": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
"integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="
},
"mute-stream": {
"version": "0.0.8",
"resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz",
"integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA=="
},
"npm-run-path": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
"integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==",
"requires": {
"path-key": "^3.0.0"
}
},
"once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"requires": {
"wrappy": "1"
}
},
"onetime": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
"integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
"requires": {
"mimic-fn": "^2.1.0"
}
},
"ora": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/ora/-/ora-4.1.1.tgz",
"integrity": "sha512-sjYP8QyVWBpBZWD6Vr1M/KwknSw6kJOz41tvGMlwWeClHBtYKTbHMki1PsLZnxKpXMPbTKv9b3pjQu3REib96A==",
"requires": {
"chalk": "^3.0.0",
"cli-cursor": "^3.1.0",
"cli-spinners": "^2.2.0",
"is-interactive": "^1.0.0",
"log-symbols": "^3.0.0",
"mute-stream": "0.0.8",
"strip-ansi": "^6.0.0",
"wcwidth": "^1.0.1"
},
"dependencies": {
"chalk": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
"integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==",
"requires": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
}
}
}
},
"path-key": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="
},
"pump": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
"integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
"requires": {
"end-of-stream": "^1.1.0",
"once": "^1.3.1"
}
},
"restore-cursor": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz",
"integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==",
"requires": {
"onetime": "^5.1.0",
"signal-exit": "^3.0.2"
}
},
"shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
"requires": {
"shebang-regex": "^3.0.0"
}
},
"shebang-regex": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="
},
"signal-exit": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz",
"integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA=="
},
"strip-ansi": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
"integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
"requires": {
"ansi-regex": "^5.0.0"
}
},
"strip-final-newline": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz",
"integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA=="
},
"supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"requires": {
"has-flag": "^4.0.0"
}
},
"universalify": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz",
"integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug=="
},
"wcwidth": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz",
"integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=",
"requires": {
"defaults": "^1.0.3"
}
},
"which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
"requires": {
"isexe": "^2.0.0"
}
},
"wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
}
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "create-directus-project",
"version": "9.0.0-beta.9",
"version": "9.0.0-beta.10",
"description": "A small installer util that will create a directory, add boilerplate folders, and install Directus through npm.",
"main": "lib/index.js",
"bin": "./lib/index.js",

1423
packages/spec/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,14 +1,23 @@
{
"name": "@directus/specs",
"version": "9.0.0-beta.9",
"description": "Specification of the Directus Api",
"version": "9.0.0-beta.10",
"description": "OpenAPI Specification of the Directus API",
"main": "index.js",
"scripts": {
"ui:watch": "swagger-ui-watcher specs/openapi.yaml",
"validate": "swagger-cli validate specs/openapi.yaml",
"build": "swagger-cli bundle specs/openapi.yaml -o dist/openapi.json",
"build:deref": "swagger-cli bundle specs/openapi.yaml -o dist/openapi-deref.json --dereference",
"prepublishOnly": "npm run build && npm run build:deref"
"prepublishOnly": "npm run build && npm run build:deref",
"dev": "npm-watch build"
},
"watch": {
"build": {
"patterns": ["specs"],
"extensions": "yaml",
"quiet": true,
"silent": true
}
},
"repository": {
"type": "git",
@@ -26,5 +35,9 @@
"README.md",
"index.js"
],
"gitHead": "4476da28dbbc2824e680137aa28b2b91b5afabec"
"gitHead": "4476da28dbbc2824e680137aa28b2b91b5afabec",
"devDependencies": {
"npm-watch": "^0.7.0",
"swagger-cli": "^4.0.4"
}
}

View File

@@ -8,39 +8,44 @@ properties:
description: Action that was performed.
example: update
type: string
enum: [authenticate, comment, upload, create, update, delete, soft-delete, revert, invalid-credentials]
enum:
- create
- update
- delete
- authenticate
user:
description: Unique identifier of the user account who caused this action.
example: 63716273-0f29-4648-8a2a-2af2948f6f78
type: string
nullable: true # States the SQL structure
description: The user who performed this action.
oneOf:
- type: string
- $ref: "../openapi.yaml#/components/schemas/User"
nullable: true
timestamp:
description: When the action happened.
example: '2019-12-05 22:52:09'
example: "2019-12-05T22:52:09Z"
type: string
format: date-time
ip:
description: The IP address of the user at the time the action took place.
example: 160.72.72.58
example: 127.0.0.1
oneOf:
- type: string
format: ipv4
- type: string
enum: [localhost]
user_agent:
description: User agent string of the browser the user used when the action took place.
example: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_1) AppleWebKit/537.36 (KHTML,like Gecko) Chrome/78.0.3904.108 Safari/537.36
type: string
collection:
description: Collection identifier in which the item resides.
example: movies
type: string
oneOf:
- type: string
- $ref: "../openapi.yaml#/components/schemas/Collection"
item:
description: Unique identifier for the item the action applied to. This is always a string, even for integer primary keys.
example: '328'
example: "328"
type: string
comment:
description: User comment. This will store the comments that show up in the right
description:
User comment. This will store the comments that show up in the right
sidebar of the item edit page in the admin app.
example: null
type: string

View File

@@ -1,43 +1,85 @@
type: object
properties:
collection:
description: The collection name.
description: The collection key.
example: customers
type: string
meta:
description: Metadata of the collection.
type: object
example: null
nullable: true
properties:
collection:
description: The collection name again!
description: The collection key.
example: customers
type: string
hidden:
type: boolean
singleton:
type: boolean
icon:
description: Name of a Google Material Design Icon that's assigned to this collection.
type: string
example: people
nullable: true
note:
description: A note describing the collection.
type: string
nullable: true
translation:
type: string
example: null
nullable: true
display_template:
description: Text representation of how items from this collection are shown across the system.
type: string
example: null
nullable: true
hidden:
description: Whether or not the collection is hidden from the navigation in the admin app.
type: boolean
example: false
singleton:
description: Whether or not the collection is treated as a single object.
type: boolean
example: false
translations:
description: Key value pairs of how to show this collection's name in different languages in the admin app.
type: string
example: null
nullable: true
archive_field:
description: What field holds the archive value.
type: string
example: null
nullable: true
archive_app_filter:
description: What value to use for "archived" items.
type: string
example: null
nullable: true
archive_value:
description: What value to use to "unarchive" items.
type: string
example: null
nullable: true
unarchive_value:
description: Whether or not to show the "archived" filter.
type: string
example: null
nullable: true
sort_field:
description: The sort field in the collection.
type: string
example: null
nullable: true
schema:
type: object
properties:
name:
description: The collection key.
type: string
example: customers
schema:
description: Database schema (pg only).
example: public
type: string
comment:
description: Comment as saved in the database.
type: string
collation:
type: string
engine:
type: string
example: null
nullable: true

View File

@@ -1,10 +1,5 @@
type: object
properties:
id:
description: Unique identifier for the field in the `directus_fields` collection.
example: 167
type: integer
nullable: true
collection:
description: Unique name of the collection this field is in.
example: about_us
@@ -13,102 +8,159 @@ properties:
description: Unique name of the field. Field name is unique within the collection.
example: id
type: string
auto_increment:
description: If the value in this field is auto incremented. Only applies to integer
type fields.
example: true
type: boolean
datatype:
description: SQL datatype of the column that corresponds to this field.
example: INT
type: string
nullable: true
group:
description: What field group this field is part of.
example: null
type: integer
nullable: true
hidden_browse:
description: If this field should be hidden from the item browse (listing) page.
example: true
type: boolean
hidden_detail:
description: If this field should be hidden from the item detail (edit) page.
example: true
type: boolean
interface:
description: What interface is used in the admin app to edit the value for this
field.
example: primary-key
type: string
nullable: true
length:
description: Length of the field. Will be used in SQL to set the `length` property
of the colummn.
example: '10'
type: string
nullable: true
locked:
description: If the field can be altered by the end user. Directus system fields
have this value set to `true`.
example: true
type: boolean
note:
description: A user provided note for the field. Will be rendered alongside the
interface on the edit page.
example: ''
type: string
nullable: true
options:
description: Options for the interface that's used. This format is based on the
individual interface.
example: null
type: object
nullable: true
primary_key:
description: If this field is the primary key of the collection.
example: true
type: boolean
readonly:
description: Prevents the user from editing the value in the field.
example: false
type: boolean
required:
description: If this field requires a value.
example: true
type: boolean
signed:
description: If the value is signed or not. Only applies to integer type fields.
example: false
type: boolean
sort:
description: Sort order of this field on the edit page of the admin app.
example: 1
type: integer
nullable: true
translation:
description: 'Key value pair of `<locale>: <translation>` that allows the user
to change the displayed name of the field in the admin app.'
example: null
type: object
nullable: true
type:
description: Directus specific data type. Used to cast values in the API.
example: integer
type: string
unique:
description: If the value of this field should be unique within the collection.
example: false
type: boolean
validation:
description: User provided regex that will be used in the API to validate incoming
values. It uses the PHP flavor of RegEX.
example: null
type: string
schema:
description: The schema info.
type: object
properties:
name:
description: The name of the field.
example: title
type: string
table:
description: The collection of the field.
example: posts
type: string
type:
description: The datatype of the field.
example: string
type: string
default_value:
description: The default value of the field.
example: null
type: string
nullable: true
max_length:
description: The max length of the field.
example: null
type: integer
nullable: true
is_nullable:
description: If the field is nullable.
example: false
type: boolean
is_primary_key:
description: If the field is primary key.
example: false
type: boolean
has_auto_increment:
description: If the field has auto increment.
example: false
type: boolean
foreign_key_column:
description: Related column from the foreign key constraint.
example: null
type: string
nullable: true
foreign_key_table:
description: Related table from the foreign key constraint.
example: null
type: string
nullable: true
comment:
description: Comment as saved in the database.
example: null
type: string
nullable: true
schema:
description: Database schema (pg only).
example: public
type: string
foreign_key_schema:
description: Related schema from the foreign key constraint (pg only).
example: null
type: string
nullable: true
meta:
description: The meta info.
type: object
nullable: true
width:
description: Width of the field on the edit form.
example: null
type: string
nullable: true
enum: [half, half-left, half-right, full, fill, null]
properties:
id:
description: Unique identifier for the field in the `directus_fields` collection.
example: 3
type: integer
collection:
description: Unique name of the collection this field is in.
example: posts
type: string
field:
description: Unique name of the field. Field name is unique within the collection.
example: title
type: string
special:
description: Transformation flags for field
example: null
type: array
items:
type: string
nullable: true
interface:
description:
What interface is used in the admin app to edit the value for this field.
example: primary-key
type: string
nullable: true
options:
description:
Options for the interface that's used. This format is based on the individual interface.
example: null
type: object
nullable: true
display:
description: What display is used in the admin app to display the value for this field.
example: null
type: string
nullable: true
display_options:
description: Options for the display that's used. This format is based on the individual display.
example: null
type: object
nullable: true
locked:
description:
If the field can be altered by the end user. Most Directus system fields
have this value set to `true`.
example: true
type: boolean
readonly:
description: Prevents the user from editing the value in the field.
example: false
type: boolean
hidden:
description: If this field should be hidden.
example: true
type: boolean
sort:
description: Sort order of this field on the edit page of the admin app.
example: 1
type: integer
nullable: true
width:
description: Width of the field on the edit form.
example: null
type: string
nullable: true
enum: [half, half-left, half-right, full, fill, null]
group:
description: What field group this field is part of.
example: null
type: integer
nullable: true
translations:
description:
"Key value pair of `<locale>: <translation>` that allows the user
to change the displayed name of the field in the admin app."
example: null
type: object
nullable: true
note:
description:
A user provided note for the field. Will be rendered alongside the
interface on the edit page.
example: ""
type: string
nullable: true

View File

@@ -1,60 +1,71 @@
type: object
properties:
id:
description: Unique identifier for the file.
example: 8cbb43fe-4cdf-4991-8352-c461779cec02
type: string
storage:
description:
Where the file is stored. Either `local` for the local filesystem
or the name of the storage adapter (for example `s3`).
example: local
type: string
filename_disk:
description:
Name of the file on disk. By default, Directus uses a random hash
for the filename.
example: a88c3b72-ac58-5436-a4ec-b2858531333a.jpg
type: string
filename_download:
description: How you want to the file to be named when it's being downloaded.
example: avatar.jpg
type: string
title:
description:
Title for the file. Is extracted from the filename on upload, but
can be edited by the user.
example: User Avatar
type: string
type:
description: MIME type of the file.
example: image/jpeg
type: string
folder:
description: Virtual folder where this file resides in.
example: null
oneOf:
- type: string
- $ref: "../openapi.yaml#/components/schemas/Folder"
nullable: true
uploaded_by:
description: Who uploaded the file.
example: 63716273-0f29-4648-8a2a-2af2948f6f78
oneOf:
- type: string
- $ref: "../openapi.yaml#/components/schemas/User"
uploaded_on:
description: When the file was uploaded.
example: "2019-12-03T00:10:15+00:00"
type: string
format: date-time
charset:
description: Character set of the file.
example: binary
type: string
nullable: true # Should not be null?
checksum:
description: Represents the sum of the correct digits of the file, can be used
to detect errors in and duplicates of the file later.
example: d41d8cd98f00b204e9800998ecf8427e
type: string
data:
example:
embed: null
full_url: 'https://demo.directus.io/uploads/thumper/originals/a88c3b72-ac58-5436-a4ec-b2858531333a.jpg'
thumbnails:
dimension: 64x64
height: 64
relative_url: '/thumper/assets/pnw7s9lqy68048g0?key=directus-small-crop'
url: 'https://demo.directus.io/thumper/assets/pnw7s9lqy68048g0?key=directus-small-crop'
width: 64
url: '/uploads/thumper/originals/a88c3b72-ac58-5436-a4ec-b2858531333a.jpg'
properties:
full_url:
description: Full URL to the original file.
type: string
thumbnails:
description: List of all available asset sizes with links.
type: array
nullable: true
items:
type: object
properties:
dimension:
description: Width x height of the thumbnail.
type: string
height:
description: Height of the thumbnail in pixels.
type: integer
relative_url:
description: Relative URL to the thumbnail.
type: string
url:
description: Full URL to the thumbnail.
type: string
width:
description: Width of the thumbnail in pixels.
type: integer
url:
description: Relative URL to the original file.
type: string
type: object
description:
description: Description for the file.
example: ''
type: string
nullable: true
filesize:
description: Size of the file in bytes.
example: 137862
type: integer
width:
description: Width of the file in pixels. Only applies to images.
example: 800
type: integer
nullable: true
height:
description: Height of the file in pixels. Only applies to images.
example: 838
type: integer
nullable: true
duration:
description: Duration of the file in seconds. Only applies to audio and video.
@@ -66,82 +77,25 @@ properties:
example: null
type: string
nullable: true
filename_disk:
description: Name of the file on disk. By default, Directus uses a random hash
for the filename.
example: a88c3b72-ac58-5436-a4ec-b2858531333a.jpg
description:
description: Description for the file.
type: string
filename_download:
description: How you want to the file to be named when it's being downloaded.
example: avatar.jpg
type: string
filesize:
description: Size of the file in bytes.
example: 137862
type: integer
folder:
description: Virtual folder where this file resides in.
example: null
$ref: '../openapi.yaml#/components/schemas/Folder'
nullable: true
height:
description: Height of the file in pixels. Only applies to images.
example: 838
type: integer
nullable: true
id:
description: Unique identifier for the file.
example: 8cbb43fe-4cdf-4991-8352-c461779cec02
type: string
location:
description: Where the file was created. Is automatically populated based on EXIF
description:
Where the file was created. Is automatically populated based on EXIF
data for images.
type: string
nullable: true
metadata:
description: User provided miscellaneous key value pairs that serve as additional
metadata for the file.
example: null
type: object
nullable: true
private_hash:
description: Random hash used to access the file privately. This can be rotated
to prevent unauthorized access to the file.
example: pnw7s9lqy68048g0
type: string
storage:
description: Where the file is stored. Either `local` for the local filesystem
or the name of the storage adapter (for example `s3`).
example: local
type: string
tags:
description: Tags for the file. Is automatically populated based on EXIF data
for images.
description:
Tags for the file. Is automatically populated based on EXIF data for images.
type: array
nullable: true
items:
type: string
title:
description: Title for the file. Is extracted from the filename on upload, but
can be edited by the user.
example: User Avatar
type: string
type:
description: MIME type of the file.
example: image/jpeg
type: string
uploaded_by:
description: Who uploaded the file.
example: 63716273-0f29-4648-8a2a-2af2948f6f78
type: string
# $ref: '../openapi.yaml#/components/schemas/User'
uploaded_on:
description: When the file was uploaded.
example: '2019-12-03T00:10:15+00:00'
type: string
format: date-time
width:
description: Width of the file in pixels. Only applies to images.
example: 800
type: integer
nullable: true
metadata:
description:
IPTC, EXIF, and ICC metadata extracted from file
type: object
nullable: true

View File

@@ -11,5 +11,7 @@ properties:
parent:
description: Unique identifier of the parent folder. This allows for nested folders.
example: null
type: string
oneOf:
- type: string
- $ref: "../openapi.yaml#/components/schemas/Folder"
nullable: true

View File

@@ -1,2 +1,2 @@
type: object
properties: {}
properties: {}

View File

@@ -1,70 +1,46 @@
type: object
properties:
collection:
description: What collection this permission applies to.
example: customers
type: string
comment:
description: If the user can post comments.
example: update
type: string
enum: [none, create, update, full]
create:
description: If the user can create items.
example: full
type: string
enum: [none, full]
delete:
description: If the user can update items.
example: none
type: string
enum: [none, mine, role, full]
explain:
description: If the user is required to leave a comment explaining what was changed.
example: none
type: string
enum: [none, create, update, always]
id:
description: Unique identifier for the permission.
example: 1
type: integer
read:
description: If the user can read items.
example: mine
type: string
enum: [none, mine, role, full]
read_field_blacklist:
description: Explicitly denies read access for specific fields.
example: []
type: array
items:
type: string
role:
description: Unique identifier of the role this permission applies to.
example: 2f24211d-d928-469a-aea3-3c8f53d4e426
type: string
nullable: true # Should this be nullable?
status:
description: What status this permission applies to.
example: null
type: string
nullable: true
status_blacklist:
description: Explicitly denies specific statuses to be used.
example: []
oneOf:
- type: array
nullable: true
items:
type: string
update:
description: If the user can update items.
example: none
collection:
description: What collection this permission applies to.
example: customers
type: string
enum: [none, mine, role, full]
write_field_blacklist:
description: Explicitly denies write access for specific fields.
example: []
action:
description: What action this permission applies to.
example: create
type: string
enum:
- create
- read
- update
- delete
permissions:
description: JSON structure containing the permissions checks for this permission.
type: object
nullable: true
validation:
description: JSON structure containing the validation checks for this permission.
type: object
nullable: true
presets:
description: JSON structure containing the preset value for created/updated items.
type: object
nullable: true
fields:
description: CSV of fields that the user is allowed to interact with.
type: array
items:
type: string
type: string
nullable: true
limit:
description: Maximum amount of items the user can interact with at a time.
type: number
nullable: true

View File

@@ -1,73 +1,72 @@
type: object
properties:
id:
description: Unique identifier for this single collection preset.
example: 155
type: integer
bookmark:
description:
Name for the bookmark. If this is set, the preset will be considered a bookmark.
nullable: true
type: string
user:
description:
The unique identifier of the user to whom this collection preset applies.
example: 63716273-0f29-4648-8a2a-2af2948f6f78
nullable: true
oneOf:
- type: string
- $ref: "../openapi.yaml#/components/schemas/User"
role:
description:
The unique identifier of a role in the platform. If `user` is null,
this will be used to apply the collection preset or bookmark for all users in
the role.
example: 50419801-0f30-8644-2b3c-9bc2d980d0a0
nullable: true
oneOf:
- type: string
- $ref: "../openapi.yaml#/components/schemas/Role"
collection:
description: What collection this collection preset is used for.
example: articles
oneOf:
- type: string
- $ref: "../openapi.yaml#/components/schemas/Collection"
search:
description: Search query.
type: string
nullable: true
filters:
description: The filters that the user applied.
example:
field: title
operator: contains
value: Hello
- key: 7RwVrquB5dPmfbrI1rcWy
field: title
operator: contains
value: Hello
type: array
nullable: true
items:
type: object
id:
description: Unique identifier for this single collection preset.
example: '155'
type: integer
role:
description: The unique identifier of a role in the platform. If `user` is null,
this will be used to apply the collection preset or bookmark for all users in
the role.
example: 63716273-0f29-4648-8a2a-2af2948f6f78
nullable: true
layout:
description: Key of the layout that is used.
type: string
search_query:
description: What the user searched for in search/filter in the header bar.
example: null
type: string
nullable: true
title:
description: Name for the bookmark. If this is set, the collection preset will
be considered to be a bookmark.
example: null
type: string
nullable: true
translation:
description: Key value pair of language-translation. Can be used to translate
the bookmark title in multiple languages.
example: null
type: object
nullable: true
user:
description: The unique identifier of the user to whom this collection preset
applies.
example: 63716273-0f29-4648-8a2a-2af2948f6f78
nullable: true
type: string
view_options:
description: Options of the views. The properties in here are controlled by the
layout.
example:
timeline:
color: action
content: excerpt
date: published_on
title: '{{ title }} ({{ author.first_name }} {{ author.last_name }})'
type: object
nullable: true
view_query:
description: View query that's saved per view type. Controls what data is fetched
layout_query:
description:
Layout query that's saved per layout type. Controls what data is fetched
on load. These follow the same format as the JS SDK parameters.
example:
timeline:
cards:
sort: -published_on
type: object
nullable: true
view_type:
description: Name of the view type that is used.
example: timeline
type: string
layout_options:
description:
Options of the views. The properties in here are controlled by the layout.
example:
cards:
icon: account_circle
title: "{{ first_name }} {{ last_name }}"
subtitle: "{{ title }}"
size: 3
nullable: true

View File

@@ -1,5 +1,9 @@
type: object
properties:
id:
description: Unique identifier for the relation.
example: 1
type: integer
many_collection:
description: Collection that has the field that holds the foreign key.
example: directus_activity
@@ -9,7 +13,7 @@ properties:
example: user
type: string
many_primary:
description: The primary field.
description: The primary key field of the current collection.
example: id
type: string
one_collection:
@@ -22,16 +26,12 @@ properties:
type: string
nullable: true
one_primary:
description: The primary field.
description: The primary key field of the related collection.
example: id
type: string
id:
description: Unique identifier for the relation.
example: 1
type: integer
junction_field:
description: Field on the junction table that holds the primary key of the related
collection.
description:
Field on the junction table that holds the many field of the related relation.
example: null
type: string
nullable: true

View File

@@ -1,12 +1,24 @@
type: object
properties:
activity:
description: Unique identifier for the [activity](/api/activity) record.
example: 2
id:
description: Unique identifier for the revision.
example: 1
type: integer
activity:
description: Unique identifier for the activity record.
example: 2
oneOf:
- type: integer
- $ref: "../openapi.yaml#/components/schemas/Activity"
collection:
description: Collection of the updated item.
example: articles
oneOf:
- type: string
- $ref: "../openapi.yaml#/components/schemas/Collection"
item:
description: Primary key of updated item.
example: "168"
type: string
data:
description: Copy of item state at time of update.
@@ -14,38 +26,18 @@ properties:
author: 1
body: This is my first post
featured_image: 15
id: '168'
id: "168"
title: Hello, World!
type: object
nullable: true # Should this be nullable?
nullable: true
delta:
description: Changes between the previous and the current revision.
example:
title: Hello, World!
oneOf:
- type: object
id:
description: Unique identifier for the revision.
example: 1
type: object
parent:
description:
If the current item was updated relationally, this is the id of the parent revision record
example: null
type: integer
item:
description: Primary key of updated item.
example: '168'
type: string
parent_changed:
description: If the current item was updated relationally, this shows if the parent
item was updated as well.
example: false
type: boolean
parent_collection:
description: If the current item was updated relationally, this is the collection
of the parent item.
example: null
type: string
nullable: true
parent_item:
description: If the current item was updated relationally, this is the unique
identifier of the parent item.
example: null
type: string
nullable: true

View File

@@ -1,43 +1,51 @@
type: object
properties:
collection_listing:
description: Custom override for the admin app collection navigation.
example: null
type: object
nullable: true
id:
description: Unique identifier for the role.
example: 2f24211d-d928-469a-aea3-3c8f53d4e426
type: string
name:
description: Name of the role.
example: Administrator
type: string
icon:
description: The role's icon.
example: verified_user
type: string
description:
description: Description of the role.
example: Admins have access to all managed data within the system by default
type: string
nullable: true
enforce_tfa:
description: Whether or not this role enforces the use of 2FA.
example: false
type: boolean
external_id:
description: ID used with external services in SCIM.
example: null
type: string
nullable: true
id:
description: Unique identifier for the role.
example: 2f24211d-d928-469a-aea3-3c8f53d4e426
type: string
ip_whitelist:
description: Array of IP addresses that are allowed to connect to the API as a
ip_access:
description:
Array of IP addresses that are allowed to connect to the API as a
user of this role.
example: []
type: array
items:
type: string
module_listing:
enforce_tfa:
description: Whether or not this role enforces the use of 2FA.
example: false
type: boolean
module_list:
description: Custom override for the admin app module bar navigation.
example: null
type: array
items:
type: object
nullable: true
name:
description: Name of the role.
example: Administrator
type: string
collection_list:
description: Custom override for the admin app collection navigation.
example: null
type: object
nullable: true
admin_access:
description: Admin role. If true, skips all permission checks.
example: false
type: boolean
app_access:
description: The users in the role are allowed to use the app.
example: true
type: boolean

View File

@@ -2,6 +2,82 @@ type: object
properties:
id:
description: Unique identifier for the setting.
example: 1
type: integer
additionalProperties: true
example: 1
project_name:
description: The name of the project.
type: string
example: Directus
project_url:
description: The url of the project.
type: string
example: null
nullable: true
project_color:
description: The brand color of the project.
type: string
example: null
nullable: true
project_logo:
description: The logo of the project.
type: string
example: null
nullable: true
public_foreground:
description: The foreground of the project.
type: string
example: null
nullable: true
public_background:
description: The background of the project.
type: string
example: null
nullable: true
public_note:
description: Note rendered on the public pages of the app.
type: string
example: null
nullable: true
auth_login_attempts:
description: Allowed authentication login attempts before the user's status is set to blocked.
type: integer
example: 25
auth_password_policy:
description: Authentication password policy.
type: string
nullable: true
storage_asset_transform:
description: What transformations are allowed in the assets endpoint.
type: string
enum:
- all
- none
- presets
example: "all"
nullable: true
storage_asset_presets:
description: Array of allowed
type: array
items:
type: object
properties:
key:
description: Key for the asset. Used in the assets endpoint.
type: string
fit:
description: Whether to crop the thumbnail to match the size, or maintain the aspect ratio.
type: string
enum:
- cover
- contain
width:
description: Width of the thumbnail.
type: integer
height:
description: Height of the thumbnail.
type: integer
quality:
description: Quality of the compression used.
type: integer
example: null
nullable: true

View File

@@ -1,76 +1,91 @@
type: object
properties:
tfa_secret:
description: The 2FA secret string that's used to generate one time passwords.
example: null
id:
description: Unique identifier for the user.
example: 63716273-0f29-4648-8a2a-2af2948f6f78
type: string
first_name:
description: First name of the user.
example: Admin
type: string
last_name:
description: Last name of the user.
example: User
type: string
nullable: true
avatar:
description: The user's avatar.
example: null
oneOf:
- type: integer
nullable: true
- type: string
- $ref: '../openapi.yaml#/components/schemas/File'
nullable: true
email:
description: Unique email address for the user.
example: admin@example.com
type: string
format: email
external_id:
description: ID used for SCIM.
password:
description: Password of the user.
type: string
location:
description: The user's location.
example: null
type: string
nullable: true
first_name:
description: First name of the user.
example: Admin
type: string
id:
description: Unique identifier for the user.
example: 63716273-0f29-4648-8a2a-2af2948f6f78
type: string
last_login:
description: When this user logged in last.
example: '2020-05-31 14:32:37'
title:
description: The user's title.
example: null
type: string
nullable: true
format: date-time
last_name:
description: First name of the user.
example: User
type: string
last_page:
description: Last page that the user was on.
example: /my-project/settings/collections/a
description:
description: The user's description.
example: null
type: string
nullable: true
tags:
description: The user's tags.
example: null
type: array
nullable: true
items:
type: string
avatar:
description: The user's avatar.
example: null
oneOf:
- type: string
- $ref: "../openapi.yaml#/components/schemas/File"
nullable: true
language:
description: The user's language used in Directus.
example: en-US
type: string
role:
description: Unique identifier of the role of this user.
example: 2f24211d-d928-469a-aea3-3c8f53d4e426
type: string
status:
description: Status of the user.
example: active
type: string
enum: [active, invited, draft, suspended, deleted]
theme:
description: What theme the user is using.
example: auto
type: string
enum: [light, dark, auto]
timezone:
description: The user's timezone.
example: America/New_York
type: string
title:
description: The user's title.
tfa_secret:
description: The 2FA secret string that's used to generate one time passwords.
example: null
type: string
nullable: true
nullable: true
status:
description: Status of the user.
example: active
type: string
enum: [active, invited, draft, suspended, deleted]
role:
description: Unique identifier of the role of this user.
example: 2f24211d-d928-469a-aea3-3c8f53d4e426
oneOf:
- type: string
- $ref: "../openapi.yaml#/components/schemas/Role"
token:
description: Static token for the user.
type: string
nullable: true
last_acces:
description: When this user used the API last.
example: "2020-05-31T14:32:37Z"
type: string
nullable: true
format: date-time
last_page:
description: Last page that the user was on.
example: /my-project/settings/collections/a
type: string
nullable: true

View File

@@ -3,4 +3,39 @@ properties:
id:
description: The index of the webhook.
type: integer
example: 1
example: 1
name:
description: The name of the webhook.
type: string
example: create articles
method:
description: Method used in the webhook.
type: string
example: POST
url:
description: The url of the webhook.
type: string
example: null
nullable: true
status:
description: The status of the webhook.
type: string
example: inactive
data:
description: If yes, send the content of what was done
type: boolean
example: true
actions:
description: The actions that triggers this webhook.
type: array
items:
type: string
example: null
nullable: true
collections:
description: The collections that triggers this webhook.
type: array
items:
type: string
example: null
nullable: true

View File

@@ -1,19 +1,16 @@
openapi: 3.0.1
info:
title: Directus SDK
title: Directus API
description: Template for generating any kind of SDK.
contact:
email: contact@directus.io
license:
name: GPL-3.0
url: 'https://www.gnu.org/licenses/gpl-3.0.de.html'
version: 1.0.0
url: "https://www.gnu.org/licenses/gpl-3.0.html"
version: 9.0.0
externalDocs:
description: Directus Docs
url: 'https://docs.directus.io'
servers:
- url: 'https://demo.directus.io/'
- url: '/'
url: "https://docs.directus.io"
tags:
- name: Activity
description: All events that happen within Directus are tracked and stored in the activities collection. This gives you full accountability over everything that happens.
@@ -21,8 +18,8 @@ tags:
description: Image typed files can be dynamically resized and transformed to fit any need.
- name: Authentication
description: All events that happen within Directus are tracked and stored in the activities collection. This gives you full accountability over everything that happens.
- name: Collection presets
description: Collection presets hold the preferences of individual users of the platform. This allows Directus to show and maintain custom item listings for users of the app.
- name: Presets
description: Presets hold the preferences of individual users of the platform. This allows Directus to show and maintain custom item listings for users of the app.
- name: Collections
description: Collections are the individual collections of items, similar to tables in a database. Changes to collections will alter the schema of the database.
- name: Extensions
@@ -35,20 +32,14 @@ tags:
description: Folders don't do anything yet, but will be used in the (near) future to be able to group files.
- name: Items
description: Items are individual pieces of data in your database. They can be anything, from articles, to IoT status checks.
- name: Mail
description: Send electronic mail through the electronic post.
- name: Permissions
description: Permissions control who has access to what and when.
- name: Projects
description: Projects are the individual tenants of the platform. Each project has its own database and data.
- name: Relations
description: What data is linked to what other data. Allows you to assign authors to articles, products to sales, and whatever other structures you can think of.
- name: Revisions
description: Revisions are individual changes to items made. Directus keeps track of changes made, so you're able to revert to a previous state at will.
- name: Roles
description: Roles are groups of users that share permissions.
- name: SCIM
description: Directus partially supports Version 2 of System for Cross-domain Identity Management (SCIM). It is an open standard that allows for the exchange of user information between systems, therefore allowing users to be externally managed using the endpoints described below.
- name: Server
description: Access to where Directus runs. Allows you to make sure your server has everything needed to run the platform, and check what kind of latency we're dealing with.
- name: Settings
@@ -60,244 +51,234 @@ tags:
- name: Webhooks
description: Webhooks.
paths:
# Activity
/activity:
$ref: './paths/activity/activitys.yaml'
/activity/comment:
$ref: './paths/activity/activity-comments.yaml'
/activity/{id}:
$ref: './paths/activity/activity.yaml'
/activity/comment/{id}:
$ref: './paths/activity/activity-comment.yaml'
$ref: "./paths/activity/activities.yaml"
# /activity/comment:
# $ref: "./paths/activity/activity-comments.yaml"
# /activity/{id}:
# $ref: "./paths/activity/activity.yaml"
# /activity/comment/{id}:
# $ref: "./paths/activity/activity-comment.yaml"
# Assets
/assets/{key}:
$ref: './paths/assets/assets.yaml'
# # Assets
# /assets/{id}:
# $ref: "./paths/assets/assets.yaml"
# Authentication
/auth/login:
$ref: './paths/auth/login.yaml'
/auth/refresh:
$ref: './paths/auth/refresh.yaml'
/auth/logout:
$ref: './paths/auth/logout.yaml'
/auth/password/request:
$ref: './paths/auth/password-request.yaml'
/auth/password/reset:
$ref: './paths/auth/password-reset.yaml'
/auth/sso:
$ref: './paths/auth/sso.yaml'
/auth/sso/{provider}:
$ref: './paths/auth/sso-provider.yaml'
# # Authentication
# /auth/login:
# $ref: "./paths/auth/login.yaml"
# /auth/refresh:
# $ref: "./paths/auth/refresh.yaml"
# /auth/logout:
# $ref: "./paths/auth/logout.yaml"
# /auth/password/request:
# $ref: "./paths/auth/password-request.yaml"
# /auth/password/reset:
# $ref: "./paths/auth/password-reset.yaml"
# /auth/sso:
# $ref: "./paths/auth/sso.yaml"
# /auth/sso/{provider}:
# $ref: "./paths/auth/sso-provider.yaml"
# Items
/items/{collection}:
$ref: './paths/items/items.yaml'
/items/{collection}/{id}:
$ref: './paths/items/item.yaml'
# # Items
# /items/{collection}:
# $ref: "./paths/items/items.yaml"
# /items/{collection}/{id}:
# $ref: "./paths/items/item.yaml"
# Presets
/presets:
$ref: './paths/presets/presets.yaml'
/presets/{id}:
$ref: './paths/presets/preset.yaml'
# # Presets
# /presets:
# $ref: "./paths/presets/presets.yaml"
# /presets/{id}:
# $ref: "./paths/presets/preset.yaml"
# Collections
/collections:
$ref: './paths/collections/collections.yaml'
/collections/{collection}:
$ref: './paths/collections/collection.yaml'
# Extensions
/interfaces:
$ref: './paths/extensions/interfaces.yaml'
/layouts:
$ref: './paths/extensions/layouts.yaml'
/modules:
$ref: './paths/extensions/modules.yaml'
# # Collections
# /collections:
# $ref: "./paths/collections/collections.yaml"
# /collections/{id}:
# $ref: "./paths/collections/collection.yaml"
# Fields
/fields:
$ref: './paths/fields/fields.yaml'
/fields/{collection}:
$ref: './paths/fields/collection-fields.yaml'
/fields/{collection}/{field}:
$ref: './paths/fields/collection-field.yaml'
# # Extensions
# /extensions/interfaces:
# $ref: "./paths/extensions/interfaces.yaml"
# /extensions/layouts:
# $ref: "./paths/extensions/layouts.yaml"
# /extensions/displays:
# $ref: "./paths/extensions/displays.yaml"
# /extensions/modules:
# $ref: "./paths/extensions/modules.yaml"
# Files
/files:
$ref: './paths/files/files.yaml'
/files/{id}:
$ref: './paths/files/file.yaml'
/files/{id}/revisions:
$ref: './paths/files/revisions.yaml'
/files/{id}/revisions/{offset}:
$ref: './paths/files/revision.yaml'
# # Fields
# /fields:
# $ref: "./paths/fields/fields.yaml"
# /fields/{collection}:
# $ref: "./paths/fields/collection-fields.yaml"
# /fields/{collection}/{id}:
# $ref: "./paths/fields/collection-field.yaml"
# Folders
/folders:
$ref: './paths/folders/folders.yaml'
/folders/{id}:
$ref: './paths/folders/folder.yaml'
# # Files
# /files:
# $ref: "./paths/files/files.yaml"
# /files/{id}:
# $ref: "./paths/files/file.yaml"
# /files/{id}/revisions:
# $ref: "./paths/files/revisions.yaml"
# /files/{id}/revisions/{offset}:
# $ref: "./paths/files/revision.yaml"
# Mail
/mail:
$ref: './paths/mail/mail.yaml'
# # Folders
# /folders:
# $ref: "./paths/folders/folders.yaml"
# /folders/{id}:
# $ref: "./paths/folders/folder.yaml"
# Permissions
/permissions:
$ref: './paths/permissions/permissions.yaml'
/permissions/me:
$ref: './paths/permissions/permissions-me.yaml'
/permissions/{id}:
$ref: './paths/permissions/permission.yaml'
/permissions/me/{collection}:
$ref: './paths/permissions/permissions-me-collection.yaml'
# # Permissions
# /permissions:
# $ref: "./paths/permissions/permissions.yaml"
# /permissions/me:
# $ref: "./paths/permissions/permissions-me.yaml"
# /permissions/{id}:
# $ref: "./paths/permissions/permission.yaml"
# Relations
/relations:
$ref: './paths/relations/relations.yaml'
/relations/{id}:
$ref: './paths/relations/relation.yaml'
# # Relations
# /relations:
# $ref: "./paths/relations/relations.yaml"
# /relations/{id}:
# $ref: "./paths/relations/relation.yaml"
# Revisions
/revisions:
$ref: './paths/revisions/revisions.yaml'
/revisions/{id}:
$ref: './paths/revisions/revision.yaml'
# # Revisions
# /revisions:
# $ref: "./paths/revisions/revisions.yaml"
# /revisions/{id}:
# $ref: "./paths/revisions/revision.yaml"
# Revisions
/roles:
$ref: './paths/roles/roles.yaml'
/roles/{id}:
$ref: './paths/roles/role.yaml'
# # Roles
# /roles:
# $ref: "./paths/roles/roles.yaml"
# /roles/{id}:
# $ref: "./paths/roles/role.yaml"
# SCIM
/scim/v2/Users:
$ref: './paths/scim/users.yaml'
/scim/v2/Users/{id}:
$ref: './paths/scim/user.yaml'
/scim/v2/Groups:
$ref: './paths/scim/groups.yaml'
/scim/v2/Groups/{id}:
$ref: './paths/scim/group.yaml'
# Server
/server/info:
$ref: './paths/server/info.yaml'
servers:
- url: 'https://demo.directus.io/'
/server/ping:
$ref: './paths/server/ping.yaml'
servers:
- url: 'https://demo.directus.io/'
# # Server
# /server/info:
# $ref: "./paths/server/info.yaml"
# /server/ping:
# $ref: "./paths/server/ping.yaml"
# Settings
/settings:
$ref: './paths/settings/settings.yaml'
# # Settings
# /settings:
# $ref: "./paths/settings/settings.yaml"
# Users
/users:
$ref: './paths/users/users.yaml'
/users/me:
$ref: './paths/users/me.yaml'
/users/invite:
$ref: './paths/users/user-invite.yaml'
/users/{id}:
$ref: './paths/users/user.yaml'
/users/invite/{token}:
$ref: './paths/users/user-invite-token.yaml'
/users/{id}/track/page:
$ref: './paths/users/user-tracking.yaml'
# # Users
# /users:
# $ref: "./paths/users/users.yaml"
# /users/{id}:
# $ref: "./paths/users/user.yaml"
# /users/{id}/track/page:
# $ref: "./paths/users/user-tracking.yaml"
# /users/invite:
# $ref: "./paths/users/user-invite.yaml"
# /users/invite/accept:
# $ref: "./paths/users/user-invite-accept.yaml"
# /users/me:
# $ref: "./paths/users/me.yaml"
# /users/me/track/page:
# $ref: "./paths/users/me-tracking.yaml"
# /users/me/tfa/enable:
# $ref: "./paths/users/me-tfa-enable.yaml"
# /users/me/tfa/disable:
# $ref: "./paths/users/me-tfa-disable.yaml"
# Utilities
/utils/hash:
$ref: './paths/utils/hash.yaml'
/utils/hash/verify:
$ref: './paths/utils/hash-match.yaml'
/utils/random/string:
$ref: './paths/utils/random.yaml'
# # Utilities
# /utils/hash:
# $ref: "./paths/utils/hash.yaml"
# /utils/hash/verify:
# $ref: "./paths/utils/hash-match.yaml"
# /utils/random/string:
# $ref: "./paths/utils/random.yaml"
# /utils/sort/{collection}:
# $ref: "./paths/utils/sort.yaml"
# Webhooks
/webhooks:
$ref: './paths/webhooks/webhooks.yaml'
# # Webhooks
# /webhooks:
# $ref: "./paths/webhooks/webhooks.yaml"
# /webhooks/{id}:
# $ref: "./paths/webhooks/webhook.yaml"
components:
schemas:
Activity:
$ref: './components/activity.yaml'
$ref: "./components/activity.yaml"
Preset:
$ref: './components/preset.yaml'
$ref: "./components/preset.yaml"
Collection:
$ref: './components/collection.yaml'
$ref: "./components/collection.yaml"
Field:
$ref: './components/field.yaml'
$ref: "./components/field.yaml"
File:
$ref: './components/file.yaml'
$ref: "./components/file.yaml"
Folder:
$ref: './components/folder.yaml'
$ref: "./components/folder.yaml"
Item:
$ref: './components/item.yaml'
$ref: "./components/item.yaml"
Permissions:
$ref: './components/permissions.yaml'
$ref: "./components/permissions.yaml"
Relation:
$ref: './components/relation.yaml'
$ref: "./components/relation.yaml"
Revision:
$ref: './components/revision.yaml'
$ref: "./components/revision.yaml"
Role:
$ref: './components/role.yaml'
$ref: "./components/role.yaml"
Setting:
$ref: './components/setting.yaml'
$ref: "./components/setting.yaml"
User:
$ref: './components/user.yaml'
$ref: "./components/user.yaml"
Webhook:
$ref: './components/webhook.yaml'
$ref: "./components/webhook.yaml"
parameters:
# All path parameters
# All path parameters
Id:
$ref: './parameters/id.yaml'
$ref: "./parameters/id.yaml"
UUId:
$ref: './parameters/uuid.yaml'
$ref: "./parameters/uuid.yaml"
Collection:
$ref: './parameters/collection.yaml'
$ref: "./parameters/collection.yaml"
# All query parameters
q:
$ref: './parameters/q.yaml'
Search:
$ref: "./parameters/search.yaml"
Page:
$ref: './parameters/page.yaml'
$ref: "./parameters/page.yaml"
Offset:
$ref: './parameters/offset.yaml'
$ref: "./parameters/offset.yaml"
Single:
$ref: './parameters/single.yaml'
$ref: "./parameters/single.yaml"
Sort:
$ref: './parameters/sort.yaml'
$ref: "./parameters/sort.yaml"
Meta:
$ref: './parameters/meta.yaml'
$ref: "./parameters/meta.yaml"
Limit:
$ref: './parameters/limit.yaml'
$ref: "./parameters/limit.yaml"
Filter:
$ref: './parameters/filter.yaml'
$ref: "./parameters/filter.yaml"
Fields:
$ref: './parameters/fields.yaml'
$ref: "./parameters/fields.yaml"
Mode:
$ref: './parameters/mode.yaml'
$ref: "./parameters/mode.yaml"
responses:
NotFoundError:
$ref: './responses/notFoundError.yaml'
$ref: "./responses/notFoundError.yaml"
UnauthorizedError:
$ref: './responses/unauthorizedError.yaml'
$ref: "./responses/unauthorizedError.yaml"
securitySchemes:
KeyAuth:
type: apiKey
in: query
name: access_token
description: Use the key 'admin' to authenticate to the public api.
Auth:
type: apiKey
in: header
name: 'Authorization'
description: To authenticate, use the "/auth/authenticate" endpoint with the credentials "admin@example.com" | "password". Use the api key here like so "Bearer \<key\>".
security:
name: "Authorization"
security:
- Auth: []
- KeyAuth: []

View File

@@ -1,6 +1,6 @@
description: Collection of which you want to retrieve the permissions.
description: Collection of which you want to retrieve the items from.
name: collection
in: path
required: true
schema:
type: string
type: string

View File

@@ -6,4 +6,4 @@ schema:
type: array
items:
type: string
pattern: '^(\[[^\[\]]*?\]){1}(\[(=|eq|<>|!=|neq|<|lt|<=|lte|>|gt|>=|gte|in|nin|null|nnull|contains|like|ncontains|nlike|rlike|nrlike|between|nbetween|empty|nempty|all|has)\])?=.*?$'
pattern: '^(\[[^\[\]]*?\]){1}(\[(_eq|_neq|_lt|_lte|_gt|_gte|_in|_nin|_null|_nnull|_contains|_ncontains|_between|_nbetween|_empty|_nempty)\])?=.*?$'

View File

@@ -1,6 +1,6 @@
description: Index of the file.
description: Index
name: id
in: path
required: true
schema:
type: integer
type: integer

View File

@@ -3,4 +3,4 @@ in: query
name: page
required: false
schema:
type: integer
type: integer

View File

@@ -1,6 +1,6 @@
description: Filter by items that contain the given search query in one of their fields.
in: query
name: q
name: search
required: false
schema:
type: string
type: string

View File

@@ -0,0 +1,30 @@
get:
operationId: getActivities
description: Returns a list of activity actions.
parameters:
- $ref: "../../openapi.yaml#/components/parameters/Fields"
- $ref: "../../openapi.yaml#/components/parameters/Limit"
- $ref: "../../openapi.yaml#/components/parameters/Meta"
- $ref: "../../openapi.yaml#/components/parameters/Offset"
- $ref: "../../openapi.yaml#/components/parameters/Single"
- $ref: "../../openapi.yaml#/components/parameters/Sort"
- $ref: "../../openapi.yaml#/components/parameters/Filter"
- $ref: "../../openapi.yaml#/components/parameters/Search"
responses:
"200":
content:
application/json:
schema:
type: object
properties:
data:
type: array
items:
$ref: "../../openapi.yaml#/components/schemas/Activity"
description: Successful request
"401":
$ref: "../../openapi.yaml#/components/responses/UnauthorizedError"
"404":
$ref: "../../openapi.yaml#/components/responses/NotFoundError"
tags:
- Activity

View File

@@ -2,7 +2,8 @@ patch:
description: Update the content of an existing comment.
operationId: updateComment
parameters:
- $ref: '../../openapi.yaml#/components/parameters/Meta'
- $ref: "../../#/components/parameters/Id"
- $ref: "../../#/components/parameters/Meta"
requestBody:
content:
application/json:
@@ -13,32 +14,32 @@ patch:
type: string
example: My updated comment
responses:
'200':
"200":
content:
application/json:
schema:
type: object
properties:
data:
$ref: '../../openapi.yaml#/components/schemas/Activity'
$ref: "../../#/components/schemas/Activity"
description: Successful request
'401':
$ref: '../../openapi.yaml#/components/responses/UnauthorizedError'
'404':
$ref: '../../openapi.yaml#/components/responses/NotFoundError'
"401":
$ref: "../../#/components/responses/UnauthorizedError"
"404":
$ref: "../../#/components/responses/NotFoundError"
tags:
- Activity
- Activity
delete:
description: Delete an existing comment. Deleted comments can not be retrieved.
operationId: deleteComment
parameters:
- $ref: "../../#/components/parameters/Id"
responses:
'203':
"203":
description: Deleted succsessfully
'401':
$ref: '../../openapi.yaml#/components/responses/UnauthorizedError'
'404':
$ref: '../../openapi.yaml#/components/responses/NotFoundError'
"401":
$ref: "../../#/components/responses/UnauthorizedError"
"404":
$ref: "../../#/components/responses/NotFoundError"
tags:
- Activity
parameters:
- $ref: '../../openapi.yaml#/components/parameters/Id'
- Activity

View File

@@ -2,13 +2,13 @@ post:
description: Creates a new comment.
operationId: createComment
parameters:
- $ref: '../../openapi.yaml#/components/parameters/Meta'
- $ref: "../../#/components/parameters/Meta"
requestBody:
content:
application/json:
schema:
type: object
required: [ "collection", "item", "comment" ]
required: ["collection", "item", "comment"]
properties:
collection:
type: string
@@ -20,18 +20,18 @@ post:
type: string
example: A new comment
responses:
'200':
"200":
content:
application/json:
schema:
type: object
properties:
data:
$ref: '../../openapi.yaml#/components/schemas/Activity'
$ref: "../../#/components/schemas/Activity"
description: Successful request
'401':
$ref: '../../openapi.yaml#/components/responses/UnauthorizedError'
'404':
$ref: '../../openapi.yaml#/components/responses/NotFoundError'
"401":
$ref: "../../#/components/responses/UnauthorizedError"
"404":
$ref: "../../#/components/responses/NotFoundError"
tags:
- Activity
- Activity

View File

@@ -1,13 +1,14 @@
get:
description: Retrieves the details of an existing activity action. Provide the primary
description:
Retrieves the details of an existing activity action. Provide the primary
key of the activity action and Directus will return the corresponding information.
operationId: getActivity
parameters:
- $ref: '../../openapi.yaml#/components/parameters/Id'
- $ref: '../../openapi.yaml#/components/parameters/Fields'
- $ref: '../../openapi.yaml#/components/parameters/Meta'
- $ref: "../../#/components/parameters/Id"
- $ref: "../../#/components/parameters/Fields"
- $ref: "../../#/components/parameters/Meta"
responses:
'200':
"200":
description: Successful request
content:
application/json:
@@ -15,10 +16,10 @@ get:
type: object
properties:
data:
$ref: '../../openapi.yaml#/components/schemas/Activity'
'401':
$ref: '../../openapi.yaml#/components/responses/UnauthorizedError'
'404':
$ref: '../../openapi.yaml#/components/responses/NotFoundError'
$ref: "../../#/components/schemas/Activity"
"401":
$ref: "../../#/components/responses/UnauthorizedError"
"404":
$ref: "../../#/components/responses/NotFoundError"
tags:
- Activity
- Activity

View File

@@ -1,30 +0,0 @@
get:
operationId: getActivitys
description: Returns a list of activity actions.
parameters:
- $ref: '../../openapi.yaml#/components/parameters/Fields'
- $ref: '../../openapi.yaml#/components/parameters/Limit'
- $ref: '../../openapi.yaml#/components/parameters/Meta'
- $ref: '../../openapi.yaml#/components/parameters/Offset'
- $ref: '../../openapi.yaml#/components/parameters/Single'
- $ref: '../../openapi.yaml#/components/parameters/Sort'
- $ref: '../../openapi.yaml#/components/parameters/Filter'
- $ref: '../../openapi.yaml#/components/parameters/q'
responses:
'200':
content:
application/json:
schema:
type: object
properties:
data:
type: array
items:
$ref: '../../openapi.yaml#/components/schemas/Activity'
description: Successful request
'401':
$ref: '../../openapi.yaml#/components/responses/UnauthorizedError'
'404':
$ref: '../../openapi.yaml#/components/responses/NotFoundError'
tags:
- Activity

View File

@@ -3,12 +3,12 @@ get:
- Assets
operationId: getAsset
description: Image typed files can be dynamically resized and transformed to fit any need.
security:
security:
- Auth: []
parameters:
- name: key
- name: id
in: path
description: private_hash of the file
description: The id of the file.
required: true
schema:
type: string
@@ -41,11 +41,11 @@ get:
minimum: 1
maximum: 100
responses:
'200':
"200":
description: Successful request
content:
text/plain:
schema:
type: string
'404':
$ref: '../../openapi.yaml#/components/responses/NotFoundError'
"404":
$ref: "../../#/components/responses/NotFoundError"

View File

@@ -8,7 +8,7 @@ post:
application/json:
schema:
type: object
required: [ email, password ]
required: [email, password]
properties:
email:
type: string
@@ -21,26 +21,29 @@ post:
example: password
mode:
type: string
enum: [jwt, cookie]
default: jwt
enum: ["json", cookie]
default: "json"
description: Choose between retrieving the token as a string, or setting it as a cookie.
otp:
type: string
description: If 2FA is enabled, you need to pass the one time password.
responses:
'200':
"200":
description: Successful authentification
content:
application/json:
schema:
type: object
properties:
public:
type: boolean
data:
type: object
properties:
token:
access_token:
type: string
user:
$ref: '../../openapi.yaml#/components/schemas/User'
example: eyJhbGciOiJI...
expires:
type: integer
example: 900
refresh_token:
type: string
example: yuOJkjdPXMd...

View File

@@ -3,6 +3,17 @@ post:
tags:
- Authentication
operationId: logout
requestBody:
content:
application/json:
schema:
type: object
required: [token]
properties:
refresh_token:
type: string
example: eyJ0eXAiOiJKV...
description: JWT access token you want to logout.
responses:
'200':
description: Request successful
"200":
description: Request successful

View File

@@ -8,16 +8,12 @@ post:
application/json:
schema:
type: object
required: [ email ]
required: [email]
properties:
email:
type: string
example: admin@example.com
description: Email address of the user you're requesting a reset for.
reset_url:
type: string
example: https://mydomain.com/passwordreset
description: Provide a custom reset url which the link in the Email will lead to. The reset token will be passed as a parameter.
responses:
'401':
$ref: '../../openapi.yaml#/components/responses/UnauthorizedError'
"401":
$ref: "../../#/components/responses/UnauthorizedError"

View File

@@ -8,7 +8,7 @@ post:
application/json:
schema:
type: object
required: [ token, password ]
required: [token, password]
properties:
token:
type: string
@@ -20,5 +20,5 @@ post:
format: password
description: New password for the user.
responses:
'401':
$ref: '../../openapi.yaml#/components/responses/UnauthorizedError'
"401":
$ref: "../../#/components/responses/UnauthorizedError"

View File

@@ -8,26 +8,31 @@ post:
application/json:
schema:
type: object
required: [ token ]
required: [token]
properties:
refresh_token:
type: string
example: eyJ0eXAiOiJKV...
description: JWT access token you want to refresh. This token can't be expired.
responses:
'200':
"200":
description: Successful request
content:
application/json:
schema:
type: object
properties:
public:
type: boolean
data:
type: object
properties:
token:
access_token:
type: string
'401':
$ref: '../../openapi.yaml#/components/responses/UnauthorizedError'
example: eyJhbGciOiJI...
expires:
type: integer
example: 900
refresh_token:
type: string
example: Gy-caJMpmGTA...
"401":
$ref: "../../#/components/responses/UnauthorizedError"

View File

@@ -10,7 +10,7 @@ get:
required: true
schema:
type: string
- $ref: '../../openapi.yaml#/components/parameters/Mode'
- $ref: "../../#/components/parameters/Mode"
- name: redirect_url
in: query
required: true
@@ -18,7 +18,7 @@ get:
schema:
type: string
responses:
'200':
"200":
description: Successful request
content:
application/json:
@@ -32,5 +32,5 @@ get:
properties:
token:
type: string
'401':
$ref: '../../openapi.yaml#/components/responses/UnauthorizedError'
"401":
$ref: "../../#/components/responses/UnauthorizedError"

View File

@@ -4,7 +4,7 @@ get:
operationId: sso
description: List the SSO providers.
responses:
'200':
"200":
description: Successful request
content:
application/json:
@@ -18,5 +18,5 @@ get:
example: ["github", "facebook"]
items:
type: string
'401':
$ref: '../../openapi.yaml#/components/responses/UnauthorizedError'
"401":
$ref: "../../#/components/responses/UnauthorizedError"

View File

@@ -2,84 +2,123 @@ get:
description: Retrieves the details of a single collection.
operationId: getCollection
parameters:
- $ref: '../../openapi.yaml#/components/parameters/Meta'
- $ref: "../../#/components/parameters/Meta"
responses:
'200':
"200":
content:
application/json:
schema:
type: object
properties:
data:
$ref: '../../openapi.yaml#/components/schemas/Collection'
$ref: "../../#/components/schemas/Collection"
description: Successful request
'401':
$ref: '../../openapi.yaml#/components/responses/UnauthorizedError'
'404':
$ref: '../../openapi.yaml#/components/responses/NotFoundError'
"401":
$ref: "../../#/components/responses/UnauthorizedError"
"404":
$ref: "../../#/components/responses/NotFoundError"
tags:
- Collections
- Collections
patch:
description: Update an existing collection.
operationId: updateCollection
parameters:
- $ref: '../../openapi.yaml#/components/parameters/Meta'
- $ref: "../../#/components/parameters/Meta"
requestBody:
content:
application/json:
schema:
type: object
properties:
note:
type: string
description: A note describing the collection.
hidden:
type: boolean
description: Whether or not the collection is hidden from the navigation in the admin app.
single:
type: string
description: Whether or not the collection is treated as a single record.
managed:
type: string
description: If Directus is tracking and managing this collection currently.
icon:
type: string
description: Name of a Google Material Design Icon that's assigned to this collection.
translation:
meta:
description: Metadata of the collection.
type: object
description: Key value pairs of how to show this collection's name in different languages in the admin app.
properties:
icon:
description: Name of a Google Material Design Icon that's assigned to this collection.
type: string
example: people
nullable: true
note:
description: A note describing the collection.
type: string
example: null
nullable: true
display_template:
description: Text representation of how items from this collection are shown across the system.
type: string
example: null
nullable: true
hidden:
description: Whether or not the collection is hidden from the navigation in the admin app.
type: boolean
example: false
singleton:
description: Whether or not the collection is treated as a single object.
type: boolean
example: false
translation:
description: Key value pairs of how to show this collection's name in different languages in the admin app.
type: string
example: null
nullable: true
archive_field:
description: What field holds the archive value.
type: string
example: null
nullable: true
archive_app_filter:
description: What value to use for "archived" items.
type: string
example: null
nullable: true
archive_value:
description: What value to use to "unarchive" items.
type: string
example: null
nullable: true
unarchive_value:
description: Whether or not to show the "archived" filter.
type: string
example: null
nullable: true
sort_field:
description: The sort field in the collection.
type: string
example: null
nullable: true
responses:
'200':
"200":
content:
application/json:
schema:
type: object
properties:
data:
$ref: '../../openapi.yaml#/components/schemas/Collection'
$ref: "../../#/components/schemas/Collection"
description: Successful request
'401':
$ref: '../../openapi.yaml#/components/responses/UnauthorizedError'
'404':
$ref: '../../openapi.yaml#/components/responses/NotFoundError'
"401":
$ref: "../../#/components/responses/UnauthorizedError"
"404":
$ref: "../../#/components/responses/NotFoundError"
tags:
- Collections
- Collections
delete:
description: "Delete an existing collection. Warning: This will delete the whole collection, including the items within. Proceed with caution."
operationId: deleteCollection
responses:
'200':
"200":
description: Successful request
'401':
$ref: '../../openapi.yaml#/components/responses/UnauthorizedError'
'404':
$ref: '../../openapi.yaml#/components/responses/NotFoundError'
"401":
$ref: "../../#/components/responses/UnauthorizedError"
"404":
$ref: "../../#/components/responses/NotFoundError"
tags:
- Collections
- Collections
parameters:
- name: collection
- name: id
in: path
required: true
description: The unique name of the collection.
description: Unique identifier of the collection.
schema:
type: string
type: string

View File

@@ -2,11 +2,11 @@ get:
description: Returns a list of the collections available in the project.
operationId: getCollections
parameters:
- $ref: '../../openapi.yaml#/components/parameters/Offset'
- $ref: '../../openapi.yaml#/components/parameters/Single'
- $ref: '../../openapi.yaml#/components/parameters/Meta'
- $ref: "../../#/components/parameters/Offset"
- $ref: "../../#/components/parameters/Single"
- $ref: "../../#/components/parameters/Meta"
responses:
'200':
"200":
description: Successful request
content:
application/json:
@@ -16,63 +16,100 @@ get:
data:
type: array
items:
$ref: '../../openapi.yaml#/components/schemas/Collection'
'401':
$ref: '../../openapi.yaml#/components/responses/UnauthorizedError'
'404':
$ref: '../../openapi.yaml#/components/responses/NotFoundError'
$ref: "../../#/components/schemas/Collection"
"401":
$ref: "../../#/components/responses/UnauthorizedError"
"404":
$ref: "../../#/components/responses/NotFoundError"
tags:
- Collections
- Collections
post:
description: Create a new collection in Directus.
operationId: createCollection
parameters:
- $ref: '../../openapi.yaml#/components/parameters/Meta'
- $ref: "../../#/components/parameters/Meta"
requestBody:
content:
application/json:
schema:
type: object
required: [ collection, fields ]
required: [collection, fields]
properties:
collection:
type: string
description: Unique name of the collection.
example: my_collection
fields:
type: object
type: array
description: The fields contained in this collection. See the fields reference for more information. Each individual field requires field, type, and interface to be provided.
note:
type: string
description: A note describing the collection.
hidden:
type: string
description: Whether or not the collection is hidden from the navigation in the admin app.
single:
type: string
description: Whether or not the collection is treated as a single record.
managed:
type: string
description: If Directus is tracking and managing this collection currently.
items:
type: object
icon:
type: string
description: Name of a Google Material Design Icon that's assigned to this collection.
translation:
type: string
example: people
nullable: true
note:
description: A note describing the collection.
type: string
example: null
nullable: true
display_template:
description: Text representation of how items from this collection are shown across the system.
type: string
example: null
nullable: true
hidden:
description: Whether or not the collection is hidden from the navigation in the admin app.
type: boolean
example: false
singleton:
description: Whether or not the collection is treated as a single object.
type: boolean
example: false
translation:
description: Key value pairs of how to show this collection's name in different languages in the admin app.
type: string
example: null
nullable: true
archive_field:
description: What field holds the archive value.
type: string
example: null
nullable: true
archive_app_filter:
description: What value to use for "archived" items.
type: string
example: null
nullable: true
archive_value:
description: What value to use to "unarchive" items.
type: string
example: null
nullable: true
unarchive_value:
description: Whether or not to show the "archived" filter.
type: string
example: null
nullable: true
sort_field:
description: The sort field in the collection.
type: string
example: null
nullable: true
responses:
'200':
"200":
content:
application/json:
schema:
type: object
properties:
data:
$ref: '../../openapi.yaml#/components/schemas/Collection'
$ref: "../../#/components/schemas/Collection"
description: Successful request
'401':
$ref: '../../openapi.yaml#/components/responses/UnauthorizedError'
'404':
$ref: '../../openapi.yaml#/components/responses/NotFoundError'
"401":
$ref: "../../#/components/responses/UnauthorizedError"
"404":
$ref: "../../#/components/responses/NotFoundError"
tags:
- Collections
- Collections

View File

@@ -0,0 +1,21 @@
get:
description: List all installed custom displays.
operationId: getDisplays
responses:
"200":
content:
application/json:
schema:
type: object
properties:
data:
type: array
items:
type: object
description: Successful request
"401":
$ref: "../../#/components/responses/UnauthorizedError"
"404":
$ref: "../../#/components/responses/NotFoundError"
tags:
- Extensions

View File

@@ -2,7 +2,7 @@ get:
description: List all installed custom interfaces.
operationId: getInterfaces
responses:
'200':
"200":
description: Successful request
content:
application/json:
@@ -13,9 +13,9 @@ get:
type: array
items:
type: object
'401':
$ref: '../../openapi.yaml#/components/responses/UnauthorizedError'
'404':
$ref: '../../openapi.yaml#/components/responses/NotFoundError'
"401":
$ref: "../../#/components/responses/UnauthorizedError"
"404":
$ref: "../../#/components/responses/NotFoundError"
tags:
- Extensions
- Extensions

View File

@@ -2,7 +2,7 @@ get:
description: List all installed custom layouts.
operationId: getLayouts
responses:
'200':
"200":
content:
application/json:
schema:
@@ -13,9 +13,9 @@ get:
items:
type: object
description: Successful request
'401':
$ref: '../../openapi.yaml#/components/responses/UnauthorizedError'
'404':
$ref: '../../openapi.yaml#/components/responses/NotFoundError'
"401":
$ref: "../../#/components/responses/UnauthorizedError"
"404":
$ref: "../../#/components/responses/NotFoundError"
tags:
- Extensions
- Extensions

View File

@@ -2,7 +2,7 @@ get:
description: List all installed custom modules.
operationId: getModules
responses:
'200':
"200":
content:
application/json:
schema:
@@ -13,9 +13,9 @@ get:
items:
type: object
description: Successful request
'401':
$ref: '../../openapi.yaml#/components/responses/UnauthorizedError'
'404':
$ref: '../../openapi.yaml#/components/responses/NotFoundError'
"401":
$ref: "../../#/components/responses/UnauthorizedError"
"404":
$ref: "../../#/components/responses/NotFoundError"
tags:
- Extensions
- Extensions

View File

@@ -2,7 +2,7 @@ get:
description: Retrieves the details of a single field in a given collection.
operationId: getCollectionField
responses:
'200':
"200":
description: Successful request
content:
application/json:
@@ -10,13 +10,13 @@ get:
type: object
properties:
data:
$ref: '../../openapi.yaml#/components/schemas/Field'
'401':
$ref: '../../openapi.yaml#/components/responses/UnauthorizedError'
'404':
$ref: '../../openapi.yaml#/components/responses/NotFoundError'
$ref: "../../#/components/schemas/Field"
"401":
$ref: "../../#/components/responses/UnauthorizedError"
"404":
$ref: "../../#/components/responses/NotFoundError"
tags:
- Fields
- Fields
patch:
description: Update an existing field.
operationId: updateField
@@ -24,68 +24,172 @@ patch:
content:
application/json:
schema:
properties:
datatype:
description: SQL datatype of the column that corresponds to this field.
type: string
auto_increment:
description: If the value in this field is auto incremented. Only applies to integer type fields.
type: boolean
group:
description: What field group this field is part of.
type: string
hidden_browse:
description: If this field should be hidden from the item browse (listing) page.
type: boolean
hidden_detail:
description: If this field should be hidden from the item detail (edit) page.
type: boolean
interface:
description: What interface is used in the admin app to edit the value for this field.
type: string
locked:
description: If the field can be altered by the end user. Directus system fields have this value set to `true`.
type: boolean
note:
description: A user provided note for the field. Will be rendered alongside the interface on the edit page.
type: string
options:
description: Options for the interface that's used. This format is based on the individual interface.
type: object
primary_key:
description: If this field is the primary key of the collection.
type: boolean
readonly:
description: Prevents the user from editing the value in the field.
type: boolean
required:
description: If this field requires a value.
type: boolean
signed:
description: If the value is signed or not. Only applies to integer type fields.
type: boolean
sort:
description: Sort order of this field on the edit page of the admin app.
type: integer
translation:
description: 'Key value pair of `<locale>: <translation>` that allows the user to change the displayed name of the field in the admin app.'
type: object
unique:
description: If the value of this field should be unique within the collection.
type: boolean
validation:
description: User provided regex that will be used in the API to validate incoming values.
type: string
width:
description: Width of the field on the edit form.
type: string
enum: [half, half-left, half-right, full, fill]
length:
description: Length of the field. Will be used in SQL to set the length property of the colummn. Requirement of this attribute depends on the provided datatype.
type: integer
type: object
properties:
field:
description: Unique name of the field. Field name is unique within the collection.
example: id
type: string
type:
description: Directus specific data type. Used to cast values in the API.
example: integer
type: string
schema:
description: The schema info.
type: object
properties:
name:
description: The name of the field.
example: title
type: string
table:
description: The collection of the field.
example: posts
type: string
type:
description: The type of the field.
example: string
type: string
default_value:
description: The default value of the field.
example: null
type: string
nullable: true
max_length:
description: The max length of the field.
example: null
type: integer
nullable: true
is_nullable:
description: If the field is nullable.
example: false
type: boolean
is_primary_key:
description: If the field is primary key.
example: false
type: boolean
has_auto_increment:
description: If the field has auto increment.
example: false
type: boolean
foreign_key_column:
description: Related column from the foreign key constraint.
example: null
type: string
nullable: true
foreign_key_table:
description: Related table from the foreign key constraint.
example: null
type: string
nullable: true
comment:
description: Comment as saved in the database.
example: null
type: string
nullable: true
schema:
description: Database schema (pg only).
example: public
type: string
foreign_key_schema:
description: Related schema from the foreign key constraint (pg only).
example: null
type: string
nullable: true
meta:
description: The meta info.
type: object
nullable: true
properties:
id:
description: Unique identifier for the field in the `directus_fields` collection.
example: 3
type: integer
collection:
description: Unique name of the collection this field is in.
example: posts
type: string
field:
description: Unique name of the field. Field name is unique within the collection.
example: title
type: string
special:
description: Transformation flag for field
example: null
type: array
items:
type: string
nullable: true
interface:
description:
What interface is used in the admin app to edit the value for this
field.
example: primary-key
type: string
nullable: true
options:
description:
Options for the interface that's used. This format is based on the
individual interface.
example: null
type: object
nullable: true
display:
description: What display is used in the admin app to display the value for this field.
example: null
type: string
nullable: true
display_options:
description: Options for the display that's used. This format is based on the individual display.
example: null
type: object
nullable: true
locked:
description:
If the field can be altered by the end user. Directus system fields
have this value set to `true`.
example: true
type: boolean
readonly:
description: Prevents the user from editing the value in the field.
example: false
type: boolean
hidden:
description: If this field should be hidden.
example: true
type: boolean
sort:
description: Sort order of this field on the edit page of the admin app.
example: 1
type: integer
nullable: true
width:
description: Width of the field on the edit form.
example: null
type: string
nullable: true
enum: [half, half-left, half-right, full, fill, null]
group:
description: What field group this field is part of.
example: null
type: integer
nullable: true
translation:
description:
"Key value pair of `<locale>: <translation>` that allows the user
to change the displayed name of the field in the admin app."
example: null
type: object
nullable: true
note:
description:
A user provided note for the field. Will be rendered alongside the
interface on the edit page.
example: ""
type: string
nullable: true
responses:
'200':
"200":
description: Successful request
content:
application/json:
@@ -93,26 +197,25 @@ patch:
type: object
properties:
data:
$ref: '../../openapi.yaml#/components/schemas/Field'
'401':
$ref: '../../openapi.yaml#/components/responses/UnauthorizedError'
'404':
$ref: '../../openapi.yaml#/components/responses/NotFoundError'
$ref: "../../#/components/schemas/Field"
"401":
$ref: "../../#/components/responses/UnauthorizedError"
"404":
$ref: "../../#/components/responses/NotFoundError"
tags:
- Fields
- Fields
delete:
description: Delete an existing field.
operationId: deleteField
responses:
'200':
"200":
description: Successful request
'401':
$ref: '../../openapi.yaml#/components/responses/UnauthorizedError'
'404':
$ref: '../../openapi.yaml#/components/responses/NotFoundError'
"401":
$ref: "../../#/components/responses/UnauthorizedError"
"404":
$ref: "../../#/components/responses/NotFoundError"
tags:
- Fields
- Fields
parameters:
- name: collection
in: path
@@ -120,9 +223,9 @@ parameters:
schema:
type: string
required: true
- name: field
- name: id
in: path
description: The unique name of the field.
description: Unique identifier of the field.
schema:
type: string
required: true
required: true

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