* Create a single new collection
(payload: RawCollection, opts?: FieldMutationOptions)
| 63 | * Create a single new collection |
| 64 | */ |
| 65 | async createOne(payload: RawCollection, opts?: FieldMutationOptions): Promise<string> { |
| 66 | if (this.accountability && this.accountability.admin !== true) { |
| 67 | throw new ForbiddenError(); |
| 68 | } |
| 69 | |
| 70 | if (!('collection' in payload)) throw new InvalidPayloadError({ reason: `"collection" is required` }); |
| 71 | |
| 72 | if (typeof payload.collection !== 'string' || payload.collection === '') { |
| 73 | throw new InvalidPayloadError({ reason: `"collection" must be a non-empty string` }); |
| 74 | } |
| 75 | |
| 76 | if (payload.collection.startsWith('directus_')) { |
| 77 | throw new InvalidPayloadError({ reason: `Collections can't start with "directus_"` }); |
| 78 | } |
| 79 | |
| 80 | if (payload.collection.includes('/')) { |
| 81 | throw new InvalidPayloadError({ reason: `Collection name can't contain "/"` }); |
| 82 | } |
| 83 | |
| 84 | if (payload.schema && payload.meta && (!('status' in payload.meta) || payload.meta.status === 'active')) { |
| 85 | await getEntitlementManager().assert('collections', { adding: 1, knex: this.knex }); |
| 86 | } |
| 87 | |
| 88 | payload.collection = await this.helpers.schema.parseCollectionName(payload.collection); |
| 89 | |
| 90 | const nestedActionEvents: ActionEventParams[] = []; |
| 91 | |
| 92 | try { |
| 93 | const existingCollections: string[] = [ |
| 94 | ...((await this.knex.select('collection').from('directus_collections'))?.map(({ collection }) => collection) ?? |
| 95 | []), |
| 96 | ...Object.keys(this.schema.collections), |
| 97 | ]; |
| 98 | |
| 99 | if (existingCollections.includes(payload.collection)) { |
| 100 | throw new InvalidPayloadError({ reason: `Collection "${payload.collection}" already exists` }); |
| 101 | } |
| 102 | |
| 103 | const attemptConcurrentIndex = Boolean(opts?.attemptConcurrentIndex); |
| 104 | |
| 105 | // Create the collection/fields in a transaction so it'll be reverted in case of errors or |
| 106 | // permission problems. This might not work reliably in MySQL, as it doesn't support DDL in |
| 107 | // transactions. |
| 108 | await transaction(this.knex, async (trx) => { |
| 109 | if (payload.schema) { |
| 110 | if ('fields' in payload && !Array.isArray(payload.fields)) { |
| 111 | throw new InvalidPayloadError({ reason: `"fields" must be an array` }); |
| 112 | } |
| 113 | |
| 114 | /** |
| 115 | * Directus heavily relies on the primary key of a collection, so we have to make sure that |
| 116 | * every collection that is created has a primary key. If no primary key field is created |
| 117 | * while making the collection, we default to an auto incremented id named `id` |
| 118 | */ |
| 119 | |
| 120 | const injectedPrimaryKeyField: RawField = { |
| 121 | field: 'id', |
| 122 | type: 'integer', |
no test coverage detected