From 9bcff8ea886d1426255e75d4442cc90f36dc9d88 Mon Sep 17 00:00:00 2001 From: rijkvanzanten Date: Fri, 17 Jul 2020 17:08:22 -0400 Subject: [PATCH] Add seeds and db install --- package.json | 4 +- src/cli/create/drivers.ts | 4 +- src/cli/create/index.ts | 13 +- src/cli/create/install-db.ts | 53 ++++ src/cli/create/questions.ts | 15 +- src/database/seeds/01-create-tables.ts | 194 +++++++++++++ .../seeds/02-add-foreign-key-constraints.ts | 48 ++++ src/database/seeds/03-add-system-rows.ts | 254 ++++++++++++++++++ src/database/seeds/activity.ts | 19 -- src/database/seeds/presets.ts | 19 -- 10 files changed, 575 insertions(+), 48 deletions(-) create mode 100644 src/cli/create/install-db.ts create mode 100644 src/database/seeds/01-create-tables.ts create mode 100644 src/database/seeds/02-add-foreign-key-constraints.ts create mode 100644 src/database/seeds/03-add-system-rows.ts delete mode 100644 src/database/seeds/activity.ts delete mode 100644 src/database/seeds/presets.ts diff --git a/package.json b/package.json index d68ade057c..71787858d5 100644 --- a/package.json +++ b/package.json @@ -9,8 +9,8 @@ "scripts": { "start": "NODE_ENV=production node dist/server.js", "build": "rimraf dist && tsc -b && copyfiles \"src/**/*.*\" -e \"src/**/*.ts\" -u 1 dist", - "dev": "LOG_LEVEL=trace ts-node-dev --files src/server.ts --clear --watch \"src/**/*.ts\" --rs --transpile-only | pino-colada", - "cli": "ts-node --script-mode --transpile-only src/cli/index.ts" + "dev": "NODE_ENV=development LOG_LEVEL=trace ts-node-dev --files src/server.ts --clear --watch \"src/**/*.ts\" --rs --transpile-only | pino-colada", + "cli": "NODE_ENV=development ts-node --script-mode --transpile-only src/cli/index.ts" }, "repository": { "type": "git", diff --git a/src/cli/create/drivers.ts b/src/cli/create/drivers.ts index 9ffed8cf2c..2790245aaa 100644 --- a/src/cli/create/drivers.ts +++ b/src/cli/create/drivers.ts @@ -6,8 +6,8 @@ export const drivers = { mssql: 'Microsoft SQL Server', }; -export function getDriverForClient(client: string) { +export function getDriverForClient(client: string): keyof typeof drivers { for (const [key, value] of Object.entries(drivers)) { - if (value === client) return key; + if (value === client) return key as keyof typeof drivers; } } diff --git a/src/cli/create/index.ts b/src/cli/create/index.ts index 0f4433d3d7..a166548f78 100644 --- a/src/cli/create/index.ts +++ b/src/cli/create/index.ts @@ -5,6 +5,8 @@ import { resolve } from 'path'; import { databaseQuestions } from './questions'; import { drivers, getDriverForClient } from './drivers'; +import installDB, { Credentials } from './install-db'; + export default async function create(directory: string, options: Record) { const path = resolve(directory); checkRequirements(); @@ -42,10 +44,17 @@ export default async function create(directory: string, options: Record question({ client })) + const credentials: Credentials = await inquirer.prompt( + databaseQuestions[client].map((question: Function) => question({ client })) ); + try { + await installDB(client, credentials); + } catch (error) { + console.log(`${chalk.red('Database Error')}: Couln't install the database:`); + console.log(error.message); + } + /** @todo * - See if you can connect to DB * - Install Directus system stuff into DB diff --git a/src/cli/create/install-db.ts b/src/cli/create/install-db.ts new file mode 100644 index 0000000000..ea65ff9875 --- /dev/null +++ b/src/cli/create/install-db.ts @@ -0,0 +1,53 @@ +import knex, { Config } from 'knex'; + +export type Credentials = { + filename?: string; + host?: string; + port?: number; + database?: string; + username?: string; + password?: string; +}; +export default async function installDB( + client: 'sqlite3' | 'mysql' | 'pg' | 'oracledb' | 'mssql', + credentials: Credentials +): Promise { + let connection: Config['connection'] = {}; + + if (client === 'sqlite3') { + const { filename } = credentials; + + connection = { + filename: filename, + }; + } else { + const { host, port, database, username, password } = credentials as Credentials; + + connection = { + host: host, + port: port, + database: database, + user: username, + password: password, + }; + } + + const db = knex({ + client: client, + connection: connection, + seeds: + process.env.NODE_ENV === 'development' + ? { + extension: 'ts', + directory: './src/database/seeds/', + } + : { + extension: 'js', + directory: './dist/database/seeds/', + }, + }); + + await db.seed.run(); + + db.destroy(); +} diff --git a/src/cli/create/questions.ts b/src/cli/create/questions.ts index 1403d0afaa..3b6dc1226d 100644 --- a/src/cli/create/questions.ts +++ b/src/cli/create/questions.ts @@ -28,6 +28,13 @@ const port = ({ client }) => ({ }, }); +const database = () => ({ + type: 'input', + name: 'database', + message: 'Database Name:', + default: 'directus', +}); + const username = () => ({ type: 'input', name: 'username', @@ -43,8 +50,8 @@ const password = () => ({ export const databaseQuestions = { sqlite3: [filename], - mysql: [host, port, username, password], - pg: [host, port, username, password], - oracledb: [host, port, username, password], - mssql: [host, port, username, password], + mysql: [host, port, database, username, password], + pg: [host, port, database, username, password], + oracledb: [host, port, database, username, password], + mssql: [host, port, database, username, password], }; diff --git a/src/database/seeds/01-create-tables.ts b/src/database/seeds/01-create-tables.ts new file mode 100644 index 0000000000..afffb41d0b --- /dev/null +++ b/src/database/seeds/01-create-tables.ts @@ -0,0 +1,194 @@ +import * as Knex from 'knex'; + +export async function seed(knex: Knex): Promise { + await knex.schema.dropTableIfExists('directus_activity'); + await knex.schema.createTable('directus_activity', (table) => { + table.increments().notNullable(); + table.string('action', 45).notNullable(); + table.uuid('action_by'); + table.timestamp('action_on').defaultTo(knex.fn.now()).notNullable(); + table.string('ip', 50).notNullable(); + table.string('user_agent').notNullable(); + table.string('collection', 64).notNullable(); + table.string('item').notNullable(); + table.text('comment'); + }); + + await knex.schema.dropTableIfExists('directus_collections'); + await knex.schema.createTable('directus_collections', (table) => { + table.string('collection').primary(); + table.boolean('hidden').notNullable().defaultTo(false); + table.boolean('single').notNullable().defaultTo(false); + table.string('icon', 30); + table.string('note', 255); + table.json('translation'); + }); + + await knex.schema.dropTableIfExists('directus_fields'); + await knex.schema.createTable('directus_fields', (table) => { + table.increments().notNullable(); + table.string('collection', 64).notNullable(); + table.string('field', 64).notNullable(); + table.string('special', 64); + table.string('interface', 64); + table.json('options'); + table.string('display', 64); + table.json('display_options'); + table.boolean('locked').notNullable().defaultTo(false); + table.boolean('required').notNullable().defaultTo(false); + table.boolean('readonly').notNullable().defaultTo(false); + table.boolean('hidden_detail').notNullable().defaultTo(false); + table.boolean('hidden_browse').notNullable().defaultTo(false); + table.integer('sort'); + table.string('width', 30); + table.integer('group'); + table.json('translation'); + }); + + await knex.schema.dropTableIfExists('directus_files'); + await knex.schema.createTable('directus_files', (table) => { + table.uuid('id').primary().notNullable(); + table.string('storage', 50).notNullable(); + table.string('filename_disk', 255).notNullable(); + table.string('filename_download', 255).notNullable(); + table.string('title', 255); + table.string('type', 255); + table.uuid('uploaded_by'); + table.timestamp('uploaded_on').notNullable().defaultTo(knex.fn.now()); + table.string('charset', 50); + table.integer('filesize'); + table.integer('width'); + table.integer('height'); + table.integer('duration'); + table.string('embed', 200); + table.uuid('folder'); + table.text('description'); + table.text('location'); + table.text('tags'); + table.json('metadata'); + }); + + await knex.schema.dropTableIfExists('directus_folders'); + await knex.schema.createTable('directus_folders', (table) => { + table.uuid('id').primary().notNullable(); + table.string('name', 255).notNullable(); + table.uuid('parent_folder'); + }); + + await knex.schema.dropTableIfExists('directus_permissions'); + await knex.schema.createTable('directus_permissions', (table) => { + table.increments(); + table.uuid('role'); + table.string('collection', 64).notNullable(); + table.string('operation', 10).notNullable(); + table.json('permissions').notNullable().defaultTo('{}'); + table.json('presets'); + table.text('fields'); + table.integer('limit'); + }); + + await knex.schema.dropTableIfExists('directus_presets'); + await knex.schema.createTable('directus_presets', (table) => { + table.increments().notNullable(); + table.string('title'); + table.uuid('user'); + table.string('collection', 64).notNullable(); + table.string('search_query', 100); + table.json('filters'); + table.string('view_type', 100).notNullable(); + table.json('view_query'); + table.json('view_options'); + table.uuid('role'); + }); + + await knex.schema.dropTableIfExists('directus_relations'); + await knex.schema.createTable('directus_relations', (table) => { + table.increments(); + table.string('collection_many', 64).notNullable(); + table.string('field_many', 64).notNullable(); + table.string('primary_many', 64); + table.string('collection_one', 64); + table.string('field_one', 64); + table.string('primary_one', 64); + table.string('junction_field', 64); + }); + + await knex.schema.dropTableIfExists('directus_revisions'); + await knex.schema.createTable('directus_revisions', (table) => { + table.increments(); + table.integer('activity').notNullable(); + table.string('collection', 64).notNullable(); + table.string('item', 255).notNullable(); + table.json('data'); + table.json('delta'); + table.integer('parent'); + }); + + await knex.schema.dropTableIfExists('directus_roles'); + await knex.schema.createTable('directus_roles', (table) => { + table.uuid('id').primary(); + table.string('name', 100).notNullable(); + table.text('description'); + table.text('ip_whitelist'); + table.boolean('enforce_2fa').notNullable().defaultTo(false); + table.json('module_listing'); + table.json('collection_listing'); + table.boolean('admin').notNullable().defaultTo(false); + table.boolean('app_access').notNullable().defaultTo(true); + }); + + await knex.schema.dropTableIfExists('directus_sessions'); + await knex.schema.createTable('directus_sessions', (table) => { + table.increments(); + table.uuid('user').notNullable(); + table.timestamp('expires').notNullable(); + table.string('ip', 255); + table.string('user_agent', 255); + }); + + await knex.schema.dropTableIfExists('directus_settings'); + await knex.schema.createTable('directus_settings', (table) => { + table.increments(); + table.string('project_name', 100); + table.string('project_url', 255); + table.string('project_color', 10); + table.json('asset_shortcuts'); + table.string('asset_generation', 15); + table.uuid('project_foreground'); + table.uuid('project_background'); + + /** + * @todo extend with all settings + */ + }); + + await knex.schema.dropTableIfExists('directus_users'); + await knex.schema.createTable('directus_users', (table) => { + table.uuid('id').primary(); + table.string('status', 16).notNullable().defaultTo('draft'); + table.string('first_name', 50); + table.string('last_name', 50); + table.string('email', 128).notNullable(); + table.string('password', 255); + table.string('token', 255); + table.string('timezone', 32).notNullable().defaultTo('America/New_York'); + table.string('locale', 8); + table.uuid('avatar'); + table.string('company', 255); + table.string('title', 255); + table.timestamp('last_access_on'); + table.string('last_page', 100); + table.string('2fa_secret', 255); + table.string('theme', 20); + table.uuid('role'); + }); + + await knex.schema.dropTableIfExists('directus_webhooks'); + await knex.schema.createTable('directus_webhooks', (table) => { + /** + * @todo + * Figure out final webhooks schema + */ + table.increments(); + }); +} diff --git a/src/database/seeds/02-add-foreign-key-constraints.ts b/src/database/seeds/02-add-foreign-key-constraints.ts new file mode 100644 index 0000000000..c737008722 --- /dev/null +++ b/src/database/seeds/02-add-foreign-key-constraints.ts @@ -0,0 +1,48 @@ +import * as Knex from 'knex'; + +export async function seed(knex: Knex): Promise { + await knex.schema.table('directus_activity', (table) => { + table.foreign('collection').references('collection').inTable('directus_collections'); + table.foreign('action_by').references('id').inTable('directus_users'); + }); + + await knex.schema.table('directus_fields', (table) => { + table.foreign('collection').references('collection').inTable('directus_collections'); + }); + + await knex.schema.table('directus_files', (table) => { + table.foreign('folder').references('id').inTable('directus_folders'); + }); + + await knex.schema.table('directus_folders', (table) => { + table.foreign('parent_folder').references('id').inTable('directus_folders'); + }); + + await knex.schema.table('directus_permissions', (table) => { + table.foreign('role').references('id').inTable('directus_roles'); + table.foreign('collection').references('collection').inTable('directus_collections'); + }); + + await knex.schema.table('directus_presets', (table) => { + table.foreign('user').references('id').inTable('directus_users'); + table.foreign('collection').references('collection').inTable('directus_collections'); + }); + + await knex.schema.table('directus_relations', (table) => { + table.foreign('collection_many').references('collection').inTable('directus_collections'); + table.foreign('collection_one').references('collection').inTable('directus_collections'); + }); + + await knex.schema.table('directus_revisions', (table) => { + table.foreign('collection').references('collection').inTable('directus_collections'); + }); + + await knex.schema.table('directus_sessions', (table) => { + table.foreign('user').references('id').inTable('directus_users'); + }); + + await knex.schema.table('directus_settings', (table) => { + table.foreign('project_foreground').references('id').inTable('directus_files'); + table.foreign('project_background').references('id').inTable('directus_files'); + }); +} diff --git a/src/database/seeds/03-add-system-rows.ts b/src/database/seeds/03-add-system-rows.ts new file mode 100644 index 0000000000..f962126d02 --- /dev/null +++ b/src/database/seeds/03-add-system-rows.ts @@ -0,0 +1,254 @@ +import * as Knex from 'knex'; +import { v4 as uuidv4 } from 'uuid'; + +const systemData = [ + { + table: 'directus_collections', + rows: [ + { + collection: 'directus_activity', + hidden: false, + single: false, + icon: null, + note: null, + translation: null, + }, + { + collection: 'directus_collections', + hidden: false, + single: false, + icon: null, + note: null, + translation: null, + }, + { + collection: 'directus_fields', + hidden: false, + single: false, + icon: null, + note: null, + translation: null, + }, + { + collection: 'directus_files', + hidden: false, + single: false, + icon: null, + note: null, + translation: null, + }, + { + collection: 'directus_folders', + hidden: false, + single: false, + icon: null, + note: null, + translation: null, + }, + { + collection: 'directus_permissions', + hidden: false, + single: false, + icon: null, + note: null, + translation: null, + }, + { + collection: 'directus_presets', + hidden: false, + single: false, + icon: null, + note: null, + translation: null, + }, + { + collection: 'directus_relations', + hidden: false, + single: false, + icon: null, + note: null, + translation: null, + }, + { + collection: 'directus_revisions', + hidden: false, + single: false, + icon: null, + note: null, + translation: null, + }, + { + collection: 'directus_roles', + hidden: false, + single: false, + icon: null, + note: null, + translation: null, + }, + { + collection: 'directus_sessions', + hidden: false, + single: false, + icon: null, + note: null, + translation: null, + }, + { + collection: 'directus_settings', + hidden: false, + single: false, + icon: null, + note: null, + translation: null, + }, + { + collection: 'directus_users', + hidden: false, + single: false, + icon: null, + note: null, + translation: null, + }, + { + collection: 'directus_webhooks', + hidden: false, + single: false, + icon: null, + note: null, + translation: null, + }, + ], + }, + { + table: 'directus_fields', + rows: [ + /** + * @todo add final system fields setup for admin app + */ + ], + }, + { + table: 'directus_relations', + rows: [ + { + collection_many: 'directus_activity', + field_many: 'action_by', + primary_many: 'id', + collection_one: 'directus_users', + field_one: null, + primary_one: 'id', + junction_field: null, + }, + { + collection_many: 'directus_fields', + field_many: 'collection', + primary_many: 'collection', + collection_one: 'directus_collections', + field_one: 'fields', + primary_one: 'id', + junction_field: null, + }, + { + collection_many: 'directus_files', + field_many: 'folder', + primary_many: 'id', + collection_one: 'directus_folders', + field_one: null, + primary_one: 'id', + junction_field: null, + }, + { + collection_many: 'directus_files', + field_many: 'uploaded_by', + primary_many: 'id', + collection_one: 'directus_users', + field_one: null, + primary_one: 'id', + junction_field: null, + }, + { + collection_many: 'directus_folders', + field_many: 'parent_folder', + primary_many: 'id', + collection_one: 'directus_folders', + field_one: null, + primary_one: 'id', + junction_field: null, + }, + { + collection_many: 'directus_presets', + field_many: 'user', + primary_many: 'id', + collection_one: 'directus_users', + field_one: null, + primary_one: 'id', + junction_field: null, + }, + { + collection_many: 'directus_revisions', + field_many: 'activity', + primary_many: 'id', + collection_one: 'directus_activity', + field_one: null, + primary_one: 'id', + junction_field: null, + }, + { + collection_many: 'directus_users', + field_many: 'avatar', + primary_many: 'id', + collection_one: 'directus_files', + field_one: null, + primary_one: 'id', + junction_field: null, + }, + { + collection_many: 'directus_users', + field_many: 'role', + primary_many: 'id', + collection_one: 'directus_roles', + field_one: null, + primary_one: 'id', + junction_field: null, + }, + ], + }, + { + table: 'directus_roles', + rows: [ + { + id: uuidv4(), + name: 'Admin', + description: null, + ip_whitelist: null, + enforce_2fa: false, + module_listing: null, + collection_listing: null, + admin: true, + app_access: true, + }, + ], + }, + { + table: 'directus_settings', + rows: [ + { + id: 1, + project_name: 'Directus', + project_url: null, + project_color: '#2d6cc0', + asset_shortcuts: [], + asset_generation: 'all', + project_foreground: null, + project_background: null, + }, + ], + }, +]; + +export async function seed(knex: Knex): Promise { + for (const { table, rows } of systemData) { + console.log(table, rows); + await knex(table).insert(rows); + } +} diff --git a/src/database/seeds/activity.ts b/src/database/seeds/activity.ts deleted file mode 100644 index a5d0126790..0000000000 --- a/src/database/seeds/activity.ts +++ /dev/null @@ -1,19 +0,0 @@ -import * as Knex from 'knex'; - -export async function seed(knex: Knex): Promise { - await knex.schema.dropTableIfExists('directus_activity'); - - await knex.schema.createTable('directus_activity', (table) => { - table.increments().notNullable(); - table.string('action', 45).notNullable(); - table.uuid('action_by').nullable(); - // table.foreign('action_by').references('directus_users.id'); - table.timestamp('action_on').defaultTo(knex.fn.now()).notNullable(); - table.string('ip', 50).notNullable(); - table.string('user_agent').notNullable(); - table.string('collection', 64).notNullable(); - // table.foreign('collection').references('directus_collections.collection'); - table.string('item').notNullable(); - table.text('comment').nullable(); - }); -} diff --git a/src/database/seeds/presets.ts b/src/database/seeds/presets.ts deleted file mode 100644 index be352b605b..0000000000 --- a/src/database/seeds/presets.ts +++ /dev/null @@ -1,19 +0,0 @@ -import * as Knex from 'knex'; - -export async function seed(knex: Knex): Promise { - await knex.schema.dropTableIfExists('directus_presets'); - - await knex.schema.createTable('directus_presets', (table) => { - table.increments().notNullable(); - table.string('title').nullable(); - table.uuid('user').nullable(); - table.string('collection', 64).notNullable(); - // table.foreign('collection').references('directus_collections.collection'); - table.string('search_query', 100).nullable(); - table.json('filters').nullable(); - table.string('view_type', 100).notNullable(); - table.json('view_query').nullable(); - table.json('view_options').nullable(); - table.uuid('role').nullable(); - }); -}